Skip to content

Bypass rules

proxylib.bypass

Proxy bypass rules: which request URLs go DIRECT, in each OS's own syntax.

BypassRules.parse(text, dialect=...) reads a bypass list written for NO_PROXY (curl), the Windows ProxyOverride setting (WinINet), the macOS ExceptionsList, GNOME/MATE ignore-hosts (GLib) or KDE NoProxyFor; rules.matches(url) says whether url bypasses the proxy.

Rule kinds, in every dialect unless noted:

  • a host name: exact match, or (no_proxy/kde, and any entry written with a leading . or *.) the name and every subdomain;
  • a glob with * and ? ([ is literal), matched case-insensitively against the host;
  • an IP network in CIDR form (10.0.0.0/8, abbreviated 169.254/16, 2001:db8::/32), matched against IP-literal hosts only;
  • an optional :port after a host, matched against the request's port (the scheme's default port when the URL has none);
  • <local>: a host name with no dot (WinINet meaning);
  • <subnet>: a host that resolves to a loopback or link-local address or to an address on the same network as a local interface;
  • <-loopback>: turns off the implicit loopback bypass;
  • * as the whole list: every host.

localhost, *.localhost, 127.0.0.0/8 and ::1 always bypass unless <-loopback> is given, as in Chromium and Go.

Dialect = str module-attribute

One of "no_proxy", "wininet", "macos", "gsettings", "kde".

__all__ = ('BypassRules', 'Dialect') module-attribute

BypassRules(rules=(), *, text=(), loopback=True, reversed=False)

An immutable list of bypass rules; see the module docstring.

rules.matches(url) is true when url should go DIRECT. a | b combines two lists; bool(rules) is false for an empty list (which still bypasses loopback unless <-loopback> was given).

Source code in src/proxylib/bypass.py
162
163
164
165
166
167
168
169
170
171
172
173
def __init__(
    self,
    rules: "Iterable[_Rule]" = (),
    *,
    text: "Tuple[Tuple[str, str], ...]" = (),
    loopback: bool = True,
    reversed: bool = False,
) -> None:
    object.__setattr__(self, "_rules", tuple(rules))
    object.__setattr__(self, "_text", tuple(text))
    object.__setattr__(self, "_loopback", loopback)
    object.__setattr__(self, "_reversed", reversed)

NONE instance-attribute

__slots__ = ('_rules', '_text', '_loopback', '_reversed') class-attribute instance-attribute

__bool__()

Source code in src/proxylib/bypass.py
183
184
def __bool__(self) -> bool:
    return bool(self._rules)

__eq__(other)

Source code in src/proxylib/bypass.py
186
187
188
189
190
191
192
193
def __eq__(self, other: object) -> bool:
    if not isinstance(other, BypassRules):
        return NotImplemented
    return (self._text, self._loopback, self._reversed) == (
        other._text,
        other._loopback,
        other._reversed,
    )

__hash__()

Source code in src/proxylib/bypass.py
195
196
def __hash__(self) -> int:
    return hash((self._text, self._loopback, self._reversed))

__or__(other)

Source code in src/proxylib/bypass.py
198
199
200
201
202
203
204
205
206
207
def __or__(self, other: "BypassRules") -> "BypassRules":
    if not isinstance(other, BypassRules):
        return NotImplemented
    if self._reversed or other._reversed:
        raise ValueError("a reversed (KDE ReversedException) list cannot be combined")
    return BypassRules(
        self._rules + other._rules,
        text=self._text + other._text,
        loopback=self._loopback and other._loopback,
    )

__repr__()

Source code in src/proxylib/bypass.py
178
179
180
181
def __repr__(self) -> str:
    flags = "" if self._loopback else ", loopback=False"
    flags += ", reversed=True" if self._reversed else ""
    return f"BypassRules({[entry for _, entry in self._text]!r}{flags})"

__setattr__(name, value)

Source code in src/proxylib/bypass.py
175
176
def __setattr__(self, name: str, value: object) -> None:
    raise AttributeError("BypassRules is immutable")

matches(url)

Whether url should go DIRECT. A URL with no host never matches.

Source code in src/proxylib/bypass.py
261
262
263
264
265
266
267
268
269
def matches(self, url: str) -> bool:
    """Whether ``url`` should go DIRECT. A URL with no host never matches."""
    target = _target(url)
    if target is None:
        return False
    if self._loopback and _is_loopback(target):
        return True
    listed = any(rule(target) for rule in self._rules)
    return not listed if self._reversed else listed

parse(text, *, dialect='no_proxy', reversed=False) classmethod

Parse a bypass list written in dialect. text is the list as one string, or its entries. Unparsable entries are skipped with one warning naming them. reversed=True (KDE ReversedException) makes the list name the hosts that use the proxy.

Source code in src/proxylib/bypass.py
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
@classmethod
def parse(
    cls,
    text: "Union[str, Iterable[str], None]",
    *,
    dialect: Dialect = "no_proxy",
    reversed: bool = False,
) -> "BypassRules":
    """Parse a bypass list written in ``dialect``. ``text`` is the list as
    one string, or its entries. Unparsable entries are skipped with one
    warning naming them. ``reversed=True`` (KDE ``ReversedException``)
    makes the list name the hosts that use the proxy."""
    if dialect not in _DIALECTS:
        raise ValueError(f"unknown bypass dialect {dialect!r}; expected one of {_DIALECTS}")
    if reversed and dialect != "kde":
        raise ValueError("reversed= applies to the kde dialect only")
    if text is None:
        entries: "List[str]" = []
    elif isinstance(text, str):
        entries = [e for e in re.split(_SEPARATORS[dialect], text.strip()) if e]
    else:
        entries = [e.strip() for e in text if e and e.strip()]

    rules: "List[_Rule]" = []
    kept: "List[Tuple[str, str]]" = []
    bad: "List[str]" = []
    loopback = True
    whole_star = entries == ["*"]
    for entry in entries:
        token = entry.lower()
        if token == "<-loopback>":
            loopback = False
            kept.append((dialect, entry))
            continue
        if token == "*" and not whole_star:
            warnings.warn(
                "bypass entry '*' among other entries: "
                + ("ignored, as curl and requests do" if dialect == "no_proxy" else "it matches every host"),
                stacklevel=2,
            )
            if dialect == "no_proxy":
                continue
        rule = _parse_entry(entry, dialect)
        if rule is None:
            bad.append(entry)
            continue
        rules.append(rule)
        kept.append((dialect, entry))
    if bad:
        warnings.warn(f"ignoring unparsable bypass entries: {bad!r}", stacklevel=2)
    return cls(rules, text=tuple(kept), loopback=loopback, reversed=reversed)