Файли
l0ng-aiтаClaude 9d4e778c7b feat(settings): HTTP Proxy settings for the IDE's outgoing connections (#194)
* feat(settings): HTTP Proxy settings for the IDE's outgoing connections

Add Appearance & Behavior › System Settings › HTTP Proxy with three modes:
No proxy, Auto-detect proxy settings (the default; environment proxy as
before) and Manual proxy configuration (HTTP or SOCKS, host and port, a
comma-separated "No proxy for" list with `*` wildcards, optional proxy
authentication). "Check connection…" GETs a URL with the settings as edited
and reports the status or the error.

- ide-core: `ProxySettings` plus routing (no-proxy globs, loopback always
  direct, credentials percent-encoded), the current proxy for the process,
  curl arguments and child-process variables.
- ide-http: `client::build` / `client::shared` is the one HTTP client
  constructor; the HTTP Client requests and the update check go through it.
- ide-tools: curl downloads (tools, JDKs, debug adapters, agent registry,
  remote server) follow the setting.
- A manual proxy reaches child processes (language servers, debug adapters,
  terminals, git) as HTTP_PROXY / HTTPS_PROXY / ALL_PROXY / NO_PROXY.
- The proxy password is kept in the owner-only saved passwords file
  (`proxy-password.json` in the app data directory), not in settings.toml.

Co-Authored-By: Claude <[email protected]>

* fix(host): Allow the Unix-only post-start test helpers on Windows

Co-Authored-By: Claude <[email protected]>

---------

Co-authored-by: Claude <[email protected]>
2026-10-29 10:43:30 +08:00

75 рядки
2.9 KiB
Rust
Неформатований Постійне посилання Анотація Історія

Цей файл містить неоднозначні символи Юнікоду
Цей файл містить символи Юнікоду, які можна сплутати з іншими символами. Якщо ви вважаєте, що це зроблено навмисно, можете сміливо ігнорувати це попередження. Щоб показати їх, скористайтеся кнопкою Escape.
//! System Settings › HTTP Proxy: applied on write, the password kept out of settings.toml.
use ide_core::NullSink;
use ide_core::proxy_settings::{ProxyMode, ProxyRoute, proxy};
use ide_host::{Host, HostConfig, ops};
use ide_workspace::SettingsScope;
use std::sync::Arc;
fn host(base: &std::path::Path) -> Arc<Host> {
Arc::new(Host::new(HostConfig {
config_dir: base.join("config"),
data_dir: base.join("data"),
languages: Arc::new(ide_language::LanguageRegistry::load(None).unwrap()),
syntax: Arc::default(),
sink_for: Box::new(|_| Arc::new(NullSink)),
owner: |w| w,
runtime: tokio::runtime::Handle::current(),
project_environment: false,
}))
}
fn set(host: &Host, key: &str, value: &str) -> ide_core::settings::Settings {
ops::project::set_settings(host, "main", SettingsScope::Global, key, value).unwrap().settings
}
fn settings_files(base: &std::path::Path) -> String {
let mut text = String::new();
for entry in walk(&base.join("config")) {
text.push_str(&std::fs::read_to_string(entry).unwrap_or_default());
}
text
}
fn walk(dir: &std::path::Path) -> Vec<std::path::PathBuf> {
let mut out = Vec::new();
for entry in std::fs::read_dir(dir).into_iter().flatten().flatten() {
let path = entry.path();
if path.is_dir() { out.extend(walk(&path)) } else { out.push(path) }
}
out
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn manual_proxy_applies_and_the_password_is_saved_apart() {
let tmp = tempfile::tempdir().unwrap();
let host = host(tmp.path());
assert_eq!(proxy().mode, ProxyMode::Auto);
set(&host, "proxy.mode", "\"manual\"");
set(&host, "proxy.host", "\"proxy.corp\"");
set(&host, "proxy.port", "3128");
set(&host, "proxy.authenticate", "true");
set(&host, "proxy.login", "\"me\"");
let settings = set(&host, "proxy.password", "\"s3cret\"");
assert_eq!(settings.proxy.password, "s3cret", "the dialog sees the saved password");
assert_eq!(
proxy().route_for_url("https://example.com/"),
ProxyRoute::Via("http://me:[email protected]:3128".into())
);
assert!(!settings_files(tmp.path()).contains("s3cret"), "no password in settings.toml");
let saved = std::fs::read_to_string(tmp.path().join("data/proxy-password.json")).unwrap();
assert!(saved.contains("s3cret"));
assert_eq!(ops::project::settings(&host, "main").proxy.password, "s3cret");
// A whole-table write keeps the password out of the file too; an empty one forgets it.
let settings = set(&host, "proxy", r#"{"mode":"none","host":"proxy.corp","password":""}"#);
assert_eq!(settings.proxy.password, "");
assert!(!settings_files(tmp.path()).contains("password"));
assert_eq!(proxy().route_for_url("https://example.com/"), ProxyRoute::Direct);
set(&host, "proxy", r#"{"mode":"auto"}"#);
assert_eq!(proxy().mode, ProxyMode::Auto);
}