Skip to main content

proxy_watch/
parse.rs

1//! Pure parsers for OS proxy string formats.
2//!
3//! List parsers drop malformed *elements* and keep the rest. Dropping a scheme endpoint
4//! without recording it would be fail-open; bypass drops are fail-closed → `rejected`.
5//!
6//! # Writing a bypass list
7//!
8//! Every store has one, no two of them read it the same way, and the differences decide
9//! which hosts go direct. What an entry means to this crate depends on the list it is
10//! written in: it is the store's own reading, except where a cell says the two differ:
11//!
12//! | Entry | `no_proxy`, KDE `NoProxyFor` | Windows `ProxyOverride` | macOS `ExceptionsList` | GNOME `ignore-hosts` |
13//! |---|---|---|---|---|
14//! | separator between entries | `,` alone: a `;` leaves the two names one dead rule, and an entry still holding a space is rejected | `;`, `,` or whitespace | the array is the separator | the array is the separator |
15//! | `example.com` | that host **and everything under it** | that host alone | that host alone | that host **and everything under it** |
16//! | `.example.com`, `*.example.com` | the subdomains, not `example.com` itself | `*.example.com` is the subdomains; `.example.com` is **rejected**: Windows has no reading for a leading `.` (below) | the subdomains | the same rule as the bare name: the domain *and* its subdomains |
17//! | a `*` anywhere else (`192.168.*`) | a glob here, which is Chromium's reading; Go and libproxy take it as literal text no host carries | a glob | syntax only as a trailing `.*`, which misses the bare host a Mac's own `name.*` still reaches; anything else is rejected | never syntax; rejected |
18//! | a bare `*` | every destination direct | every destination direct | rejected: it matches no host on a Mac | rejected, same reason |
19//! | `10.0.0.0/8` | the network | **rejected**: Windows has no `/` in this grammar, and one entry holding it turns the proxy off for every destination (below); write the range as `10.*` | the network | the network |
20//! | `example.com:8080` | holds the entry to that port | holds it | **rejected**: macOS compares the whole entry to the host name, so a port kills it | holds it, but a portless `http://` URL is asked about with port 0 and will not meet it |
21//! | `<local>`, `<-loopback>` | read | read | read | **rejected**: GLib reads neither and compares the token to a host so named |
22//! | an entry ending in a dot (`example.com.`) | the dot is the DNS root and comes off; KDE: **rejected**; KIO compares the text | **rejected** | the dot comes off, as CFNetwork reads it | **rejected** |
23//! | a trailing dot on the destination (`example.com.`) | shed, so `example.com` matches it; KDE: kept, so it does not | kept | shed | kept |
24//! | no entry at all | `localhost`, `127.0.0.0/8`, `::1`, `*.localhost` and link-local go direct; KDE: nothing does | `localhost`, `loopback`, `127.0.0.1`, `::1` and link-local go direct | `localhost`, `127.0.0.1` and `::1` go direct while the dictionary has a bypass key; with none, nothing does | nothing goes direct |
25//! | a space at either end | trimmed | trimmed | **kept**, and no host carries it, so the entry is rejected | trailing trimmed, leading kept and rejected |
26//!
27//! A rejected entry lands in
28//! [`BypassRules::rejected`](crate::BypassRules::rejected) with the reason, so a rule that
29//! does nothing is not mistaken for a live one.
30//!
31//! Windows readers differ on entries starting with `.`: WinINet refuses the whole list,
32//! while WinHTTP and the registry keep it but proxy the subdomains. Write `*.name`.
33//! A `/` makes the registry reading send every destination direct. Both spellings are
34//! rejected here while neighboring entries stay live.
35//!
36//! A bare name changes meaning between the first column
37//! and the next two, so `contoso.com` in `no_proxy` covers `api.contoso.com` and the same
38//! text in `ProxyOverride` does not. And `<local>` / `<-loopback>` are read out of every
39//! list but GNOME's so the sources share one vocabulary, while WinINet is the only resolver
40//! that acts on both and KDE's KIO acts on `<local>` alone: a macOS store spelling one gets
41//! a bypass from this crate and none from the machine.
42//! `the_local_token_bypasses_nothing_here` in `tests/mac_exceptions_list.rs` measures that.
43//! A GNOME entry spelling one is rejected, because GLib reads it as a host name.
44//!
45//! The last row is [`BypassRules::implicit`](crate::BypassRules::implicit): the
46//! destinations each resolver sends direct when no entry names them. The Windows and macOS
47//! cells are measured against WinINet and CFNetwork; the GNOME and KDE cells are read off
48//! GLib's and KIO's matchers, which have no such set.
49
50use std::collections::HashMap;
51
52use crate::bypass::{BypassDialect, BypassRules, ImplicitBypass};
53use crate::diagnostic::{RejectedValue, RejectionKind, RejectionSource};
54use crate::endpoint::{ProxyEndpoint, ProxyEntry, ProxyScheme, Scheme};
55use crate::error::Error;
56use crate::mode::ProxyMode;
57
58/// The port assumed when a Windows `ProxyServer` entry omits one.
59pub const WINDOWS_DEFAULT_PORT: u16 = 80;
60
61// Go's `httpproxy` and libproxy's `config-kde.c` split on `,` alone. KF5 KIO's `revmatch`
62// (`kprotocolmanager.cpp`) and Chromium's `proxy_config_service_linux.cc` also split at a
63// space, which KDE's dialog cannot write; a space-only list is one entry here, refused.
64// `;` is not a separator: every supplier's reader keeps a `;` list whole as one dead rule,
65// and splitting it would send direct hosts they all proxy.
66const LIST_SEPARATORS: [char; 1] = [','];
67
68// The same, plus whitespace, for the Windows bypass list only.
69//
70// WinHTTP documents `lpszProxyBypass` as "one or more server names separated by
71// semicolons or whitespace" (`WINHTTP_PROXY_INFO`), and Chromium reads the string
72// `WinHttpGetIEProxyConfigForCurrentUser` returns with
73// `base::StringTokenizer(proxy_bypass, ";, \t\n\r")`
74// (`ProxyConfigServiceWin::SetFromIEConfig`). Splitting on `;` and `,` alone would turn
75// `ProxyOverride = "*.corp.example intranet"` into one `Domain` pattern that no host can
76// match, with nothing in [`BypassRules::rejected`] to say so.
77//
78// [`no_proxy`] keeps the narrower set: Go's `httpproxy`, its reference, splits on `,`
79// alone. An entry that still contains whitespace there is caught by
80// `HostPattern::parse`, so it is recorded rather than dead.
81const WINDOWS_BYPASS_SEPARATORS: [char; 6] = [';', ',', ' ', '\t', '\r', '\n'];
82
83// Characters that separate entries in the Windows `ProxyServer` string.
84//
85// The same `WINHTTP_PROXY_INFO` page: "The proxy server list contains one or more of the
86// following strings separated by semicolons or whitespace." No `,`: that character is in
87// [`LIST_SEPARATORS`] because environment variables and GNOME put it there, and neither
88// writes this key. Named rather than spelled inline at the split so that the reason a
89// server list and a bypass list disagree about one character sits next to both.
90const WINDOWS_SERVER_SEPARATORS: [char; 5] = [';', ' ', '\t', '\r', '\n'];
91
92/// Parse Windows `ProxyServer` ([`WINHTTP_CURRENT_USER_IE_PROXY_CONFIG`](https://learn.microsoft.com/en-us/windows/win32/api/winhttp/ns-winhttp-winhttp_current_user_ie_proxy_config)).
93///
94/// Bare `host:port` → [`Scheme::All`]; or `http=`/`https=`/`ftp=`/`socks=`/`all=`.
95/// Missing port → the port of the token's own `scheme://` when it has one, else
96/// [`WINDOWS_DEFAULT_PORT`] (1080 for bare `socks=`). Empty scheme →
97/// [`ProxyEntry::Disabled`]. Bad tokens skipped; recorded on [`windows_manual`]. A scheme
98/// named twice keeps the last token that was not skipped, so a bad last spelling leaves the
99/// earlier one standing. A `socks=` entry also fills every scheme the string left
100/// unset; the last two examples below are that rule and its limit.
101///
102/// ```
103/// # use proxy_watch::{parse, ProxyScheme, Scheme};
104/// let map = parse::proxy_server("http=h:8080;https=h");
105/// assert_eq!(map[&Scheme::Http].endpoint().unwrap().port, 8080);
106/// assert_eq!(map[&Scheme::Https].endpoint().unwrap().port, 80);
107///
108/// let map = parse::proxy_server("socks=127.0.0.1:1080");
109/// assert_eq!(map[&Scheme::Http].endpoint().unwrap().authority(), "127.0.0.1:1080");
110/// assert_eq!(
111///     map[&Scheme::Http].endpoint().unwrap().scheme_hint,
112///     Some(ProxyScheme::Socks4)
113/// );
114/// let map = parse::proxy_server("http=h1:8080;socks=127.0.0.1:1080");
115/// assert_eq!(map[&Scheme::Http].endpoint().unwrap().authority(), "h1:8080");
116/// ```
117pub fn proxy_server(spec: &str) -> HashMap<Scheme, ProxyEntry> {
118    proxy_server_with_rejected(spec).0
119}
120
121// [`proxy_server`] plus the redacted original text of every token it silently drops.
122//
123// This is what [`windows_manual`] calls instead of [`proxy_server`], so that the dropped
124// tokens end up recorded on the [`ProxyMode::Manual`](crate::ProxyMode::Manual) it
125// returns.
126fn proxy_server_with_rejected(spec: &str) -> (HashMap<Scheme, ProxyEntry>, Vec<RejectedValue>) {
127    let mut map = HashMap::new();
128    let mut rejected = Vec::new();
129    for token in spec.split(WINDOWS_SERVER_SEPARATORS) {
130        let token = token.trim();
131        if token.is_empty() {
132            continue;
133        }
134        match split_scheme_key(token) {
135            Some((key, value)) => {
136                let Some(scheme) = Scheme::from_name(key) else {
137                    // Record unknown scheme keys so a dropped proxy cannot silently
138                    // become `ProxyMode::Direct`.
139                    crate::trace::warning!(
140                        "skipping a ProxyServer token with an unrecognised scheme key"
141                    );
142                    rejected.push(RejectedValue::new(
143                        RejectionKind::UnknownProxyScheme,
144                        RejectionSource::ProxyServer,
145                        token,
146                    ));
147                    continue;
148                };
149                if value.trim().is_empty() {
150                    map.insert(scheme, ProxyEntry::Disabled);
151                } else {
152                    let default_port = if scheme == Scheme::Socks {
153                        ProxyScheme::Socks4.default_port()
154                    } else {
155                        WINDOWS_DEFAULT_PORT
156                    };
157                    match ProxyEndpoint::parse(value, default_port) {
158                        Ok(mut endpoint) => {
159                            // `socks=h:1080`, with no `scheme://` of its own, is read as
160                            // SOCKS4 and not SOCKS4a; [`ProxyScheme`]'s own doc carries the
161                            // Microsoft table that says so. Set here, not in
162                            // `ProxyScheme::from_str`, which reads a *URI* scheme, where
163                            // the same word means SOCKS5 instead.
164                            if scheme == Scheme::Socks && endpoint.scheme_hint.is_none() {
165                                endpoint.scheme_hint = Some(ProxyScheme::Socks4);
166                            }
167                            map.insert(scheme, ProxyEntry::Use(endpoint));
168                        }
169                        Err(err) => {
170                            warn_dropped_proxy_server_token(&err);
171                            // `socks=` losing its own scheme also loses the fallback
172                            // `apply_socks_catch_all` would have run below: a valid SOCKS
173                            // entry fills every scheme with no token of its own, so a
174                            // malformed one drops exactly what that fallback would have
175                            // covered, not just `socks://` itself.
176                            let affected = if scheme == Scheme::Socks {
177                                Scheme::All
178                            } else {
179                                scheme
180                            };
181                            rejected.push(
182                                RejectedValue::new(
183                                    RejectionKind::InvalidProxyEndpoint,
184                                    RejectionSource::ProxyServer,
185                                    token,
186                                )
187                                .for_scheme(Some(affected)),
188                            );
189                        }
190                    }
191                }
192            }
193            None => match ProxyEndpoint::parse(token, WINDOWS_DEFAULT_PORT) {
194                Ok(endpoint) => {
195                    map.insert(Scheme::All, ProxyEntry::Use(endpoint));
196                }
197                Err(err) => {
198                    warn_dropped_proxy_server_token(&err);
199                    rejected.push(
200                        RejectedValue::new(
201                            RejectionKind::InvalidProxyEndpoint,
202                            RejectionSource::ProxyServer,
203                            token,
204                        )
205                        .for_scheme(Some(Scheme::All)),
206                    );
207                }
208            },
209        }
210    }
211    apply_socks_catch_all(&mut map);
212    (map, rejected)
213}
214
215// See `proxy_server`: an explicit `all=` answer blocks the SOCKS fallback, including
216// a disabled entry. A rejected `all=` leaves a gap that SOCKS fills.
217fn apply_socks_catch_all(map: &mut HashMap<Scheme, ProxyEntry>) {
218    if map.contains_key(&Scheme::All) {
219        return;
220    }
221    let Some(ProxyEntry::Use(endpoint)) = map.get(&Scheme::Socks) else {
222        return;
223    };
224    let endpoint = endpoint.clone();
225    for scheme in [Scheme::Http, Scheme::Https, Scheme::Ftp, Scheme::All] {
226        map.entry(scheme)
227            .or_insert_with(|| ProxyEntry::Use(endpoint.clone()));
228    }
229}
230
231// Log-only sink for a `ProxyServer` token [`proxy_server`] could not parse.
232//
233// `err` is only read inside the `WARN` line below, so without the `tracing` feature it
234// would otherwise be flagged unused.
235#[cfg_attr(not(feature = "tracing"), allow(unused_variables))]
236fn warn_dropped_proxy_server_token(err: &Error) {
237    crate::trace::warning!(
238        error = %crate::trace::SafeError(err),
239        "skipping an unparseable ProxyServer token"
240    );
241}
242
243// Split `http=host:port` into `("http", "host:port")`. Only an `=` before the first `:`,
244// `/`, `?` or `#` separates a key, so a password's or query's `=` stays in the endpoint
245// text for `ProxyEndpoint::parse`, which cuts the authority at the same characters.
246fn split_scheme_key(token: &str) -> Option<(&str, &str)> {
247    let eq = token.find('=')?;
248    let boundary = token.find([':', '/', '?', '#']).unwrap_or(token.len());
249    if eq < boundary {
250        Some((&token[..eq], &token[eq + 1..]))
251    } else {
252        None
253    }
254}
255
256/// Parse a Windows `ProxyOverride` registry value into [`BypassRules`].
257///
258/// Like [`no_proxy`], but entries are separated by `;`, `,` or whitespace, as WinHTTP
259/// documents, and **a bare name matches that name alone** rather than the domain under it:
260/// `contoso.com` here does not bypass `api.contoso.com`. Write `*.contoso.com` for the
261/// subdomains, which is also what Windows asks for; `.contoso.com` is rejected rather than
262/// read, because no measured reader grants it. A CIDR entry is rejected too, because
263/// Windows answers a `/` with the whole list: write the range as the wildcard Windows does
264/// read, `10.*`. `<local>` → [`HostPattern::Local`](crate::HostPattern::Local) and
265/// `<-loopback>` (IE9+) →
266/// [`HostPattern::SubtractImplicit`](crate::HostPattern::SubtractImplicit) are read by
267/// [`no_proxy`] as well as by this one, the way Chromium reads them ("we allow it on all
268/// platforms and interpret it the same way", `proxy_host_matching_rules.cc:113`). Malformed
269/// entries skipped into [`BypassRules::rejected`].
270///
271/// An IPv4 address entry is read only as four decimal octets. WinINet and WinHTTP compare
272/// an address entry with the destination as text, so `012.1.2.3` there is not `10.1.2.3` in
273/// either position; such an entry is rejected, where [`no_proxy`] reads it as the address a
274/// URL parser would. Limitation: every entry point reads the destination folded (a `Url` or
275/// a `Host` already holds `012.1.2.3` as `10.1.2.3`, and
276/// [`matches_authority`](crate::BypassRules::matches_authority) parses its text the same
277/// way), so an entry `10.1.2.3` or `10.*` reports a bypass for a destination written
278/// `012.1.2.3`, which Windows hands to the proxy.
279///
280/// The bare-name rule is measured against WinINet and WinHTTP. The reimplementations this
281/// crate reads alongside, Chromium and libproxy, answer the question differently, so
282/// neither could settle it. The readings are the rows of
283/// `a_bare_name_in_a_windows_list_is_the_one_host` in `tests/bypass.rs`.
284///
285/// The implicit set is measured the same way, and is [`ImplicitBypass::WinInet`]:
286/// `localhost`, `loopback`, `127.0.0.1`, `::1` and the link-local ranges. It excludes the
287/// rest of `127.0.0.0/8`, `*.localhost` and IPv4-mapped spellings. WinHTTP agrees on the
288/// link-local ranges and bypasses all of `127.0.0.0/8` even under `<-loopback>`. The store
289/// is WinINet's, so this follows WinINet, and a WinHTTP client is therefore told "proxy"
290/// for loopback it sends direct. The rows are
291/// `wininet_implicit_bypass_is_the_measured_six` in `tests/bypass.rs`.
292///
293/// ```
294/// # use proxy_watch::parse;
295/// let rules = parse::proxy_override("<local>;*.contoso.com;<-loopback>");
296/// assert!(rules.excludes_simple_hostnames());
297/// assert!(!rules.bypass_loopback());
298/// assert!(rules.matches_authority("www.contoso.com"));
299///
300/// // Whitespace separates too, so this is two rules and not one dead one.
301/// let rules = parse::proxy_override("*.contoso.com intranet");
302/// assert!(rules.matches_authority("www.contoso.com"));
303/// assert!(rules.matches_authority("intranet"));
304///
305/// // A bare name is the host itself. The same text in `no_proxy` takes the subdomains.
306/// let rules = parse::proxy_override("contoso.com");
307/// assert!(rules.matches_authority("contoso.com"));
308/// assert!(!rules.matches_authority("api.contoso.com"));
309/// assert!(parse::no_proxy("contoso.com").matches_authority("api.contoso.com"));
310///
311/// // A `/` is recorded, not read. `no_proxy` takes the same text as a mask.
312/// let rules = parse::proxy_override("10.0.0.0/8");
313/// assert!(!rules.matches_authority("10.1.2.3"));
314/// assert_eq!(rules.rejected.len(), 1);
315/// assert!(parse::no_proxy("10.0.0.0/8").matches_authority("10.1.2.3"));
316/// ```
317pub fn proxy_override(spec: &str) -> BypassRules {
318    bypass_entries_in(
319        spec.split(WINDOWS_BYPASS_SEPARATORS.as_slice()),
320        BypassDialect::Windows,
321    )
322}
323
324/// Parse `no_proxy` into [`BypassRules`]: `*`, CIDR, domains, `:port`, and a localhost
325/// bypass that stays on unless an entry clears it. Entries are separated by `,` alone, not
326/// whitespace and not `;`, which is what Go's `httpproxy` does and what KDE's readers do
327/// with `NoProxyFor`. Malformed → [`BypassRules::rejected`]. An entry
328/// that repeats one already in [`BypassRules::patterns`] is dropped, first spelling kept,
329/// so the list can be shorter than the string had entries.
330///
331/// Go's `httpproxy` uses the same separator, but this crate's matching follows
332/// Chromium's `ProxyHostMatchingRules` where the two readers differ.
333///
334/// The Windows tokens `<local>` and `<-loopback>` are read here too, which Go does not do;
335/// Chromium feeds the `no_proxy` environment variable through the same
336/// `ProxyHostMatchingRules::ParseFromString` as any other bypass list, and that is where
337/// the tokens are recognised. See [`proxy_override`], which differs in its separators and
338/// in reading a bare name as the one host rather than as the domain under it.
339///
340/// Two further departures from Go follow from reading `*` as Chromium's glob rather than
341/// Go's optional prefix. A star is matched against the destination's text, so `*.*.*.1`
342/// bypasses `10.0.0.1`, where Go answers no domain entry at all against an address
343/// destination (`httpproxy/proxy.go:367-369`). An entry whose body ends in a label that
344/// reads as a number (`.example.123`) goes to [`BypassRules::rejected`]. This is
345/// unreachable for the special schemes these lists are written about, but a
346/// `custom://a.example.123/` it would have matched is proxied instead. That URL matters
347/// because `resolve` sends an unknown scheme to the catch-all entry rather than declining
348/// it.
349///
350/// An IPv4 address written other than as four decimal octets is read the URL way, as
351/// Chromium's `IPAddress::AssignFromIPLiteral` reads it, in an address entry and in a CIDR
352/// block whose address has four parts: `012.1.2.3` is `10.1.2.3` and `012.1.2.0/24` is
353/// `10.1.2.0/24`. A block with a short-form address (`192.168.1/24`) is an error. Limitation: curl and Go read neither
354/// as an address, so under them the entry matches nothing, and this answers `Direct` for a
355/// destination those two proxy.
356///
357/// ```
358/// # use proxy_watch::parse;
359/// let rules = parse::no_proxy("<local>,<-loopback>");
360/// assert!(rules.excludes_simple_hostnames());
361/// assert!(!rules.bypass_loopback());
362/// ```
363pub fn no_proxy(spec: &str) -> BypassRules {
364    bypass_entries_in(
365        spec.split(LIST_SEPARATORS.as_slice()),
366        BypassDialect::Suffix,
367    )
368}
369
370// KDE's `NoProxyFor` (and the variable it names under `ProxyType = 4`): `no_proxy`'s
371// separators and grammar, KIO's comparison.
372#[cfg_attr(not(all(target_os = "linux", feature = "linux-kde")), allow(dead_code))]
373pub(crate) fn kde_no_proxy_for(spec: &str) -> BypassRules {
374    bypass_entries_in(spec.split(LIST_SEPARATORS.as_slice()), BypassDialect::Kde)
375}
376
377// Fold a list's entries into one rule set, each read in its own dialect.
378//
379// Takes an iterator rather than a string because not every source is one: GNOME's
380// `ignore-hosts` arrives as a GSettings `as`, where the array *is* the delimiter and no
381// character in an element is one.
382pub(crate) fn bypass_entries_in<'a>(
383    entries: impl Iterator<Item = &'a str>,
384    dialect: BypassDialect,
385) -> BypassRules {
386    let mut rules = BypassRules::new();
387    // Each store's resolver has its own implicit set; `Suffix` is the environment variable's
388    // and KDE's dialect, and KDE's reader sets its own after this returns.
389    rules.implicit = match dialect {
390        BypassDialect::Suffix => ImplicitBypass::Broad,
391        BypassDialect::Windows => ImplicitBypass::WinInet,
392        BypassDialect::MacOs => ImplicitBypass::CfNetwork,
393        BypassDialect::Gnome | BypassDialect::Kde => ImplicitBypass::Empty,
394    };
395    // Only the environment variable's readers shed a destination's trailing dot and read
396    // an IPv4-mapped destination as the address it maps; every store's resolver compares
397    // what was written.
398    rules.strip_trailing_dot = dialect == BypassDialect::Suffix;
399    rules.ipv4_mapped_as_ipv4 = dialect == BypassDialect::Suffix;
400    if dialect == BypassDialect::Gnome {
401        rules.push_gnome_entries(entries);
402        rules.dedup_patterns();
403        return rules;
404    }
405    for entry in entries {
406        // Trimmed here and not left to `HostPattern::parse_in`, which trims only what it
407        // parses: `push_entry_in` puts the string it was given into `BypassRules::rejected`,
408        // and an entry quoted back with the separator's whitespace still on it is not the
409        // entry anyone wrote.
410        rules.push_entry_in(dialect.trim(entry), dialect);
411    }
412    rules.dedup_patterns();
413    rules
414}
415
416/// Assemble a [`ProxyMode::Manual`] from the two Windows registry strings.
417///
418/// [`proxy_server`] + [`proxy_override`]; the returned manual mode records dropped server
419/// tokens. Empty server, no rejects →
420/// [`ProxyMode::Direct`]; reject-only stays `Manual` so rejects are not lost.
421///
422/// ```
423/// # use proxy_watch::parse;
424/// let mode = parse::windows_manual("http=h:8080;https=h:-1", "");
425/// assert_eq!(mode.rejected().unwrap()[0].redacted_input(), "https=h:-1");
426/// // The well formed `http=` entry survives alongside the dropped `https=` one.
427/// assert!(mode.endpoint_for(proxy_watch::Scheme::Http).is_some());
428/// ```
429pub fn windows_manual(server: &str, override_list: &str) -> ProxyMode {
430    let (per_scheme, rejected) = proxy_server_with_rejected(server);
431    if per_scheme.is_empty() && rejected.is_empty() {
432        return ProxyMode::Direct;
433    }
434    ProxyMode::manual(per_scheme, proxy_override(override_list)).with_rejected(rejected)
435}