Skip to content

feat(settings): proxy for Filee's own downloads - #1411

Merged
KnifeLemon merged 2 commits into
mainfrom
feat/proxy-setting-879
Nov 12, 2026
Merged

KnifeLemon merged 2 commits into
mainfrom
feat/proxy-setting-879

Conversation

@KnifeLemon

Copy link
Copy Markdown
Owner

What users notice

Settings → General has a new Network card with a Proxy for Filee's downloads box (http://127.0.0.1:7890, socks5://host:1080, user:pass@ allowed). Engine downloads, update checks, OCR language downloads and links dragged from a browser go through it; other programs keep their direct connection, so there's no need to switch the whole system to a proxy just to reach GitHub (#879). An address that isn't a proxy shows a warning under the box and isn't saved; the last valid one stays in use. Empty = today's behaviour (Windows/system proxy, HTTPS_PROXY). The setting is in settings.json, so with portable mode (#1241) it lives in data/ next to the app. That was the config file the reporter asked for. The CLI (filee engines install, OCR languages) reads the same setting.

Accepted schemes are the ones .NET 10's SocketsHttpHandler supports: http, https, socks4, socks4a, socks5. curl-style socks5h is read as socks5; .NET's socks5 already lets the proxy resolve names. I checked these by hand against a local listener: socks5h itself throws NotSupportedException. A value with no scheme is taken as http://.

What changed

  • src/Filee.Core/Web/ProxySetting.cs (new): TryParse/validation, credentials split off the address, Describe for logs (scheme://host:port only), and Use(), which wraps HttpClient.DefaultProxy once in a SettingsWebProxy that reads the setting on every request. Every client that uses the default proxy now follows it with no per-client change: UpdateService (HttpClientHandler), EngineInstaller.CreateHttpClient (engines, OCR languages, upscale models) and WebDownloader. A change in Settings takes effect without a restart. When a proxy is set:
    • loopback, private/link-local/ULA addresses, localhost/.local/.internal names and single-label intranet names bypass it (Ollama, Filee Connect, a NAS). The Ollama/translation clients already use UseProxy = false and are unchanged.
    • the user name and password go to the handler as Credentials and are stripped from the proxy Uri, so they never show up in .NET's error messages (which WebDownloader displays) or in Filee's logs.
    • with the setting empty or invalid, GetProxy/IsBypassed/Credentials go to the original system proxy.
  • src/Filee.Core/Settings/AppSettings.cs: string? Proxy. It's nullable with no default, so there's no schema step (it stays at 83).
  • src/Filee.App/App.axaml.cs: ProxySetting.Use(() => store.Settings.Proxy) right after the settings load and before anything downloads. It logs the redacted proxy, or a warning if the stored value is invalid.
  • src/Filee.Cli/CliHost.cs: same call after store.Load().
  • src/Filee.App/ViewModels/Pages/GeneralPageViewModel.cs, Views/Pages/GeneralPage.axaml: the Network card.
  • src/Filee.App/Assets/i18n/*.json (all 22): general.network, general.proxy, general.proxy_hint, general.proxy_invalid, translated.
  • src/Filee.Core/Web/WebDownloader.cs, SSRF guard: the ConnectCallback already left proxy connections alone ("endpoint differs from the link"). For an https link through an http/https proxy, though, .NET first opens the connection for its own CONNECT request, whose RequestUri is the proxy. The guard took that for a direct link to 127.0.0.1 and refused it with error.web_local_address. I confirmed this with a probe and with the new test, which fails without the fix. Clash's system proxy mode at 127.0.0.1:7890 already broke https link drops today. Fix: a connection opened for a CONNECT request is treated as a proxy connection. WebDownloader only ever sends GET, so a CONNECT can only come from the handler's proxy logic, aimed at the proxy the user configured (Filee's setting or the system's). A link can't produce one. The link's own host names and IP literals are still checked on every hop (EnsurePublic), and direct connections still go only to checked addresses. Also added an internal WebDownloader(handler, userAgent, IWebProxy) constructor so tests can pass a proxy without touching the global one.
  • Docs: docs/ENGINES.md (one bullet under Optional engines), docs/PORTABLE.md (one sentence), docs/ARCHITECTURE.md (the CONNECT note in the WebDownloader paragraph).

Tests

All local, no real network:

  • ProxySettingTests (Core): accepted/refused addresses (path, query, no port for socks, bad port, ftp, junk), empty = none, credentials unescaped and never in the address or Describe, internet addresses use the set proxy, local/LAN/intranet addresses bypass it, empty/invalid setting falls back to the system proxy (including its credentials), a change applies on the next request. ProxySettingUseTests (non-parallel collection) checks that Use wraps DefaultProxy once and restores it afterwards.
  • WebDownloaderTests (Core), with a fake proxy on 127.0.0.1 and the shipped local-address guard:
    • an https link sends CONNECT files.example:443 to the proxy and isn't refused as local. This one fails without the fix.
    • an http link is fetched through the proxy.
    • with a proxy set, a link to 127.0.0.1 is still refused and the proxy never sees it.
  • ProxyPageTests (App): the box saves valid values trimmed, keeps the last valid value while the text is invalid, and saves empty as null.

Results:

  • dotnet test tests/Filee.Core.Tests --filter ProxySetting|WebDownloader|WebDrop|Settings: 124/124 passed.
  • dotnet test tests/Filee.App.Tests --filter ProxyPageTests|LocalizationTests|CloseActionTests|RenderTests|PortableModeUi: 730/731; the one failure is RenderTests.Engines_page_shows_versions_descriptions_and_download_speed, which trips on a Tesseract installed in C:\Program Files\Tesseract-OCR on this machine and has nothing to do with this change.
  • dotnet test tests/Filee.Cli.Tests: 117 passed, 1 skipped, 1 failed: Zip_to_iso_converts_archive_contents_without_manual_extraction ("No engine can do this conversion (zip → iso)"). That failure comes from the worktree, which has no engines/7zip folder, and has nothing to do with this change.
  • dotnet format --verify-no-changes is clean on Filee.Core, Filee.Cli, Filee.App, Filee.Core.Tests and Filee.App.Tests.

Not covered / to decide

  • I haven't tried it against a real Clash/v2ray proxy, and macOS and Linux are untested. The code isn't platform-specific.
  • With the setting empty, the wrapper hands every request to the system proxy through the public IWebProxy API. A Windows PAC script that returns several proxies (PROXY a; PROXY b; DIRECT) therefore uses only the first; .NET's internal multi-proxy failover isn't reachable from outside. Installing the wrapper only when a proxy is set would avoid that, but a proxy entered at runtime would then need a restart for clients that have already sent a request (the update checker sends one shortly after start).
  • The guard change allows a CONNECT to any proxy the handler picked, which includes the system/PAC proxy, not just Filee's setting. That fixes the same failure with the system proxy. Limiting it to SettingsWebProxy.Configured is a one-line change if you'd rather keep it strict.
  • As before with system proxies: when a download goes through a proxy, the proxy resolves the name, so a public-looking name that resolves to a LAN address can't be checked on Filee's side. Names and IP literals still are.

Closes #879. The portable half shipped in #1241; this is the proxy half.

🤖 Generated with Claude Code

Settings -> General -> Network takes a proxy address (http, https, socks4,
socks4a, socks5; socks5h is read as socks5) for engine downloads, update
checks, OCR languages and links dragged from a browser. It applies to Filee
alone, so a user who reaches GitHub only through a proxy doesn't have to send
every program through the system proxy. Empty keeps today's behaviour: the
system proxy and HTTPS_PROXY. The setting lives in settings.json, so a
portable copy keeps it in data/.

ProxySetting.Use wraps HttpClient.DefaultProxy once, in the app and the CLI,
and reads the setting for each request, so every client that uses the
default proxy follows it and a change applies without a restart. Addresses on
this computer and the local network always go direct. The password in a
proxy address is handed to the handler as credentials and never appears in
the proxy Uri or the logs.

WebDownloader's public-address guard took the handler's CONNECT tunnel to an
https site through an http proxy on 127.0.0.1 for a link to this computer and
refused it. A CONNECT is only ever sent by the handler to the configured
proxy (downloads send GET), so it is now left alone like other proxy
connections; the link's own names and numbers are still checked.

Closes #879

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the feature New features label Nov 12, 2026
The refused-link test never connects, so on a fast machine DisposeAsync
stops the listener before Task.Run reaches AcceptTcpClientAsync, which
then throws InvalidOperationException.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@KnifeLemon
KnifeLemon merged commit b973ffd into main Nov 12, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: 建议Windows版本能自定义可选引擎和软件设置的存储位置以及支持自定义设置代理

1 participant