proxy_watch/pac/android.rs
1//! `pac-android-native`: PAC through the framework's `java.net.ProxySelector` (Android only).
2//!
3//! Android evaluates PAC in a system service, and the selector the framework installs for a
4//! PAC configuration asks that service for each URL. So this resolver runs no script and
5//! downloads nothing: it asks the question `java.net` clients in the same process ask.
6
7use url::Url;
8
9use crate::config::{ProxyConfig, ProxyConfigSource};
10use crate::error::Error;
11use crate::mode::ProxyMode;
12use crate::resolve::ProxyStep;
13
14/// Resolves the [`Pac`](ProxyMode::Pac) configuration `ConnectivityManager` reports by
15/// asking `ProxySelector.getDefault().select(uri)`.
16///
17/// The selector answers from the system settings as they are at the call, not from the
18/// [`ProxyConfig`] passed in, so only a `Pac` mode that came from
19/// [`ConnectivityManager`](ProxyConfigSource::ConnectivityManager) is resolved, and only
20/// while the system still names the same PAC URL. The framework is reached the way reading the
21/// settings reaches it: through the `JavaVM` and `Context` the host gave `android::init`,
22/// or else the ones it registered with `ndk-context`.
23///
24/// [`PacPolicy`](crate::pac::PacPolicy) does not apply: the system service runs the script
25/// with real DNS.
26///
27/// The selector answers `DIRECT` when the PAC service is unreachable or fails, and when the
28/// script answers only with entries it cannot parse (it takes `DIRECT`, `PROXY` and `SOCKS`,
29/// not `HTTPS` or `SOCKS5`), so a [`Direct`](ProxyStep::Direct) from this resolver can stand
30/// for any of those.
31#[derive(Debug, Clone, Default)]
32pub struct AndroidPacResolver {
33 _private: (),
34}
35
36impl AndroidPacResolver {
37 /// A resolver.
38 #[must_use]
39 pub fn new() -> Self {
40 Self::default()
41 }
42
43 /// Like [`resolve_with_pac`](crate::resolve_with_pac), with the system PAC service
44 /// evaluating the script. A URL with no host is Direct in every PAC mode.
45 ///
46 /// Blocks on the framework's call into the PAC service.
47 ///
48 /// # Errors
49 ///
50 /// [`Error::Io`] when the framework cannot be reached, the system settings no longer
51 /// name the PAC URL `config` holds, or the process's default `ProxySelector` is not the
52 /// system's PAC selector (it is installed a moment after a switch to PAC, and an app
53 /// can replace it), [`Error::PacInvalidResult`] for an answer with no element a client
54 /// could use (an unusable element beside a usable one is skipped), and
55 /// [`resolve`](crate::resolve())'s errors for every other mode.
56 pub fn resolve_config(&self, config: &ProxyConfig, url: &Url) -> Result<Vec<ProxyStep>, Error> {
57 let mode = &config.effective;
58 match mode {
59 ProxyMode::Pac { .. } | ProxyMode::PacInline { .. } | ProxyMode::WpadAutoDetect
60 if !crate::endpoint::has_request_host(url) =>
61 {
62 Ok(vec![ProxyStep::Direct])
63 }
64 ProxyMode::Pac { url: pac_url, .. }
65 if config.source(ProxyConfigSource::ConnectivityManager) == Some(mode) =>
66 {
67 resolve(url, pac_url)
68 }
69 _ => crate::resolve::resolve(config, url),
70 }
71 }
72}
73
74// Limitation: no timeout of its own; the framework's binder call is the only bound. Run
75// it on a thread that may wait, and add a deadline if a PAC service is seen to hang.
76fn resolve(url: &Url, pac_url: &Url) -> Result<Vec<ProxyStep>, Error> {
77 let target = crate::pac::sanitize_url(url);
78 let (live, selected) =
79 crate::sys::android::select(&crate::sys::proxy_info::java_uri_text(&target))?;
80 // A selector answering for other settings would answer for a later snapshot.
81 let live = live.and_then(|live| Url::parse(&live).ok());
82 if live.as_ref() != Some(pac_url) {
83 return Err(Error::io(
84 "resolving PAC through ProxySelector",
85 std::io::Error::other("the system proxy settings no longer name this PAC script"),
86 ));
87 }
88 let Some(selected) = selected else {
89 return Err(Error::io(
90 "resolving PAC through ProxySelector",
91 std::io::Error::other(
92 "the default ProxySelector is not the system's PacProxySelector: the process \
93 has not installed it yet, or the app replaced it",
94 ),
95 ));
96 };
97 crate::sys::proxy_info::steps_from_selected(&selected)
98}