Skip to main content

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}