fix(download): M295 r1 —— 代理口令脱敏、回落可见、socks5 与错因有话说
复审 r1(p2-2items / fix-then-merge)两条 p2 都是本轮新开的「用户被骗」面,三条 p3 一并收。 p2-1(凭据泄漏面):`proxy_url` 允许 userinfo,而这个键在本仓就是凭据(schema 是 `AiPolicy::sensitive()`、`config get` 打 `<redacted>`、`config_secret_test.rs` 专门守它)。第一版把 manual 的原串整条写进 `summary()`(→ CLI stderr → CI 日志 / 截图)与解析失败文案,**不需要用户犯错**。 - `redact_proxy_url` 是唯一实现:`user:password@` → `user:***@`(用户名保留、口令打掉;只有用户名 没有口令 ⇒ 原样;路径/查询里的 `@` `:` 一律不碰,也不做任何 URL 归一)。 - 四个出口全过它:`describe()`(`summary()`/stderr)、`apply()` 的错误文案(`proxy_problem()`/ GUI 提示)、手写的 `Debug`(配置与引擎被打进日志时)、`problem()`。 - 刻意例外(写进 docs §5.3):设置页那个输入框仍显示文件原值——它是编辑面,回填掩码会在用户点 「应用」时把真口令冲掉。 p2-2(GUI 对「非法地址 ⇒ 回落直连」完全静默):`proxy_problem()` 的消费者只有 CLI。 - `ProxySetting::problem()` 是新增的**纯判定**(与 `apply` 共用 `manual_proxy`):宿主用它给提示, 不必造 client。两个宿主都调它,优先级写清:GUI 常驻(设置页「HTTP 代理」行第二段 `\n已回落到直连:<原因>`)、CLI 每次装配引擎一行 stderr。 - 取舍写进 docs §5.3:回落直连(而不是拒绝下载)不挡用户,代价是「以为走代理」,所以回落**必须** 可见;fail-closed 是产品决定,不在本轮。 p3-1:`socks5://` 在 `Proxy::all` 里返回 `Ok`,但本构建没开 socks feature ⇒ 以前静默变成「所有候选 源都失败了」。现在 `manual_proxy` 先自己 `Url::parse` 拿 scheme 做白名单,不支持的直接有界错误 (「本构建不支持 socks5 代理(只支持 http / https)」)⇒ 走已有的回落 + 告知路径。 p3-3:错因不再只有 `builder error`:`reason()` 沿 `source()` 链拼一句;宽口径只补到「少写 scheme」 为止(已带 `://` 不再套一层 `http://`,否则真错因会被掩盖成「解析成功」)。实测文案: `代理地址无法解析(http://127.0.0.1:99999):invalid port number`。 p3-2:两个测试文件已在 scope 里登记,本轮未动。 判据(都落在最终字符串 / 真正可见面上): - `a_password_in_the_proxy_url_never_reaches_a_user_visible_string`:生效值行 / 错误文案 / `Debug` 四处都不含口令子串且保留 `user:***@`;`a_proxy_url_without_a_password_is_left_byte_identical` 守「没口令的地址逐字不变」。 - `a_broken_proxy_url_is_shown_as_falling_back_to_direct`(presenter):设置页那一行含 `\n已回落到直连:`、带(脱敏后的)地址、不含口令,且与 `engine.proxy_problem()` 是同一串; 合规地址没有这条提示。 - `the_console_line_never_carries_the_proxy_password`(CLI):`announce` 那一行与回落提示都不含口令。 - `unsupported_schemes_and_bad_ports_explain_themselves`(core)+ `an_unsupported_proxy_scheme_is_ refused_with_a_readable_reason`(engine)。 - 变异 M23-M26 记在 docs §6(redact 返回原文 ⇒ 3 条红;删 `proxy_note` ⇒ GUI 提示红;白名单写 `if false` ⇒ socks 红;错因换回 `builder error` ⇒ 红)。 - 端到端(真二进制 `ull install 1.20.1 --dry-run`,`CARGO_TARGET_DIR` 同前):口令样本 `PWMUSTNOTLEAK` 在 stdout+stderr 里 **0 次**,两行都是 `user:***@`;socks5 那档打出 「本构建不支持 socks5 代理(只支持 http / https)」。 - 门禁:四 crate `--all-targets` check / 测试(3206 passed, 0 failed, 45 binaries)/ `clippy --all-targets -- -D warnings` / `fmt --all --check`,另 `check --workspace --all-targets`。
This commit is contained in:
1 parent
d584a2c42e
commit
c182d8d5c9
7 files changed
+584
-167
No files matched your search
@@ -80,6 +80,13 @@ pub(super) fn group(env: &HostEnv) -> SettingGroupView {
|
||||
let region_value = text(env, "download.region");
|
||||
let mirror_value = text(env, "download.mirror_preference");
|
||||
let proxy_value = text(env, "download.proxy_mode");
|
||||
// M295 r1(复审 p2-2):非法 / 本构建用不上的代理地址 ⇒ 引擎回落**直连**。这条提示是 GUI 侧
|
||||
// 唯一的可见面(CLI 打在 stderr)——没有它,用户以为流量在代理里、实际裸奔。
|
||||
// 判定与引擎同源(`ProxySetting::problem` = `apply` 的同一个 `manual_proxy`),文案已脱敏。
|
||||
let proxy_note = tuning
|
||||
.proxy
|
||||
.problem()
|
||||
.map(|problem| format!("已回落到直连:{problem}"));
|
||||
SettingGroupView::new("download", "下载与镜像(D7)")
|
||||
.with(SettingRowView::choice(
|
||||
unlucky_core::download::config::KEY_CONCURRENCY,
|
||||
@@ -149,10 +156,15 @@ pub(super) fn group(env: &HostEnv) -> SettingGroupView {
|
||||
))
|
||||
// M295:这一行只在 manual 档生效;别的档下地址被**忽略**(值仍留在文件里),地址解析不了
|
||||
// 时引擎回落直连并说明原因——三件事都写进文案,否则「填了没生效」又得靠用户猜。
|
||||
// r1 复审 p2-2:`proxy_note` 是**运行时**结论(这一档真的用不了),独占一行接在静态说明后面
|
||||
// ——「回落了却不说」等于让用户在以为走代理的情况下裸奔。
|
||||
.with(SettingRowView::text(
|
||||
"download.proxy_url",
|
||||
"HTTP 代理",
|
||||
"只在 manual 模式生效(其它模式忽略这段地址);manual 留空则用系统代理;地址写法不对时按直连处理,原值保留",
|
||||
&with_note(
|
||||
"只在 manual 模式生效(其它模式忽略这段地址);manual 留空则用系统代理;地址写法不对时按直连处理,原值保留".to_string(),
|
||||
proxy_note.as_deref(),
|
||||
),
|
||||
&text(env, "download.proxy_url"),
|
||||
))
|
||||
// M193:gh-proxy 三态。**留空与 off 是两件事**,描述里必须都写出来——用户最容易把
|
||||
|
||||
@@ -131,3 +131,70 @@ fn the_two_source_selection_keys_reach_the_tuning() {
|
||||
assert_eq!(tuning.mirror_preference, MirrorPreference::Official);
|
||||
std::fs::remove_dir_all(&dir).ok();
|
||||
}
|
||||
|
||||
/// **回落必须被用户看见**(r1 复审 p2-2 的 GUI 侧判据)。
|
||||
///
|
||||
/// 断的是**最终的文案**(设置页「HTTP 代理」那一行):出现「已回落到直连」+ 原因,独占一行,且
|
||||
/// 原因里没有口令;合规地址那一行**没有**这句(否则天天挂一条假警告)。
|
||||
///
|
||||
/// 同源证明:这一行的原因与引擎的 `proxy_problem()` 是同一串(宿主调 `ProxySetting::problem`,
|
||||
/// 引擎走 `apply`,两者共用 `manual_proxy` 那一个判定)。
|
||||
///
|
||||
/// 变异:删掉 `group()` 里的 `proxy_note` ⇒ 第一段红(文案里没有「已回落到直连」);让
|
||||
/// `describe`/错误文案回到未脱敏的原串 ⇒ 第三段红(`contains(SECRET)`)。
|
||||
#[test]
|
||||
fn a_broken_proxy_url_is_shown_as_falling_back_to_direct() {
|
||||
use unlucky_core::download::config::KEY_PROXY_URL;
|
||||
|
||||
const SECRET: &str = "PWMUSTNOTLEAK";
|
||||
let (dir, env) = env_with(
|
||||
"proxy-hint-broken",
|
||||
serde_json::json!({ "download": {
|
||||
"proxy_mode": "manual",
|
||||
"proxy_url": format!("http://user:{SECRET}@127.0.0.1:99999"),
|
||||
}}),
|
||||
);
|
||||
let group_view = group(&env);
|
||||
let url_row = row(&group_view, KEY_PROXY_URL);
|
||||
assert!(
|
||||
url_row.description.contains("\n已回落到直连:"),
|
||||
"用不了的代理地址必须在设置页说出来(独占一行):{:?}",
|
||||
url_row.description
|
||||
);
|
||||
assert!(
|
||||
url_row.description.contains("99999"),
|
||||
"提示里要带上用户写的那个地址(脱敏后):{}",
|
||||
url_row.description
|
||||
);
|
||||
assert!(
|
||||
!url_row.description.contains(SECRET),
|
||||
"提示里泄漏了代理口令:{}",
|
||||
url_row.description
|
||||
);
|
||||
|
||||
// 与引擎同源:宿主这一行说的就是引擎实际记录的那条原因。
|
||||
std::fs::create_dir_all(env.layout.cache()).expect("cache 目录");
|
||||
let problem = crate::host::engine::engine(&env)
|
||||
.proxy_problem()
|
||||
.map(str::to_string)
|
||||
.expect("引擎侧也该记录这条原因");
|
||||
assert!(
|
||||
url_row.description.contains(&problem),
|
||||
"设置页与引擎说的不是同一件事:{problem}"
|
||||
);
|
||||
std::fs::remove_dir_all(&dir).ok();
|
||||
|
||||
// 合规地址 ⇒ 不许有这条提示(假警告会让真的那条失效)。
|
||||
let (dir, env) = env_with(
|
||||
"proxy-hint-ok",
|
||||
serde_json::json!({ "download": { "proxy_mode": "manual", "proxy_url": "http://127.0.0.1:7890" } }),
|
||||
);
|
||||
let ok_group = group(&env);
|
||||
let ok_row = row(&ok_group, KEY_PROXY_URL);
|
||||
assert!(
|
||||
!ok_row.description.contains("已回落到直连"),
|
||||
"合规地址不该有回落提示:{}",
|
||||
ok_row.description
|
||||
);
|
||||
std::fs::remove_dir_all(&dir).ok();
|
||||
}
|
||||
Reference in new issue
Block a user