Source code for compass.utilities.url
"""Shared URL utilities for COMPASS web modules"""
from urllib.parse import quote, urlsplit, urlunsplit
from elm.web.utilities import clean_json_escaped_url
_PATH_SAFE_CHARS = "/:@-._~!$&'()*+,;=%"
_QUERY_SAFE_CHARS = "=&;%:@-._~!$&'()*+,;/?"
[docs]
class URLPartFilter:
"""Match URL parts with whitelist precedence"""
def __init__(self, blacklist=None, whitelist=None):
"""
Parameters
----------
blacklist : iterable of str, optional
URL parts that exclude matching URLs. By default, ``None``.
whitelist : iterable of str, optional
URL parts that override blacklist matches. By default,
``None``.
"""
self.blacklist = _normalize_url_parts(blacklist)
self.whitelist = _normalize_url_parts(whitelist)
[docs]
def blacklist_match(self, url):
"""Return the first blacklist match unless URL is whitelisted
Parameters
----------
url : str
URL string to check against the blacklist.
Returns
-------
str or None
The first matching blacklist part if found and not
whitelisted, otherwise ``None``.
"""
url = url.casefold()
if self.is_whitelisted(url):
return None
return next((part for part in self.blacklist if part in url), None)
[docs]
def is_whitelisted(self, url):
"""Check whether any whitelist part occurs in a URL
Parameters
----------
url : str
URL string to check against the whitelist.
Returns
-------
bool
``True`` if any whitelist part occurs in the URL, otherwise
``False``.
"""
url = url.casefold()
return any(part in url for part in self.whitelist)
[docs]
def sanitize_url(url):
"""Encode unsafe URL characters while preserving URL semantics
Parameters
----------
url : str
URL string that may include unsafe characters such as spaces.
Returns
-------
str
URL with path, query, and fragment percent-encoded.
"""
url = clean_json_escaped_url(url)
parsed = urlsplit(url)
path = quote(parsed.path, safe=_PATH_SAFE_CHARS)
query = quote(parsed.query, safe=_QUERY_SAFE_CHARS)
fragment = quote(parsed.fragment, safe="")
return urlunsplit((parsed.scheme, parsed.netloc, path, query, fragment))
[docs]
def base_website_url(url):
"""Return the scheme and netloc portion of a website URL
Parameters
----------
url : str
URL string that may include a path, query string, or fragment.
Returns
-------
str
Canonical website root URL consisting of the original scheme
and netloc with a trailing slash. If the URL is missing a
scheme or netloc, the input is returned unchanged.
"""
parsed = urlsplit(url)
if not parsed.scheme or not parsed.netloc:
return url
return urlunsplit((parsed.scheme, parsed.netloc, "/", "", ""))
[docs]
def normalize_domain(url):
"""Return a comparable domain string for a URL or empty string
Parameters
----------
url : str
URL string to extract the domain from.
Returns
-------
str
Normalized domain string, lowercased and without www prefix.
"""
parsed = urlsplit(url.strip())
domain = parsed.netloc or parsed.path.partition("/")[0]
domain = domain.partition("@")[2] or domain
domain = domain.partition(":")[0].casefold().strip()
if domain.startswith("www."):
return domain[4:]
return domain
def _normalize_url_parts(parts):
"""Normalize non-empty URL parts for matching"""
return [part.casefold() for part in parts or [] if part]