123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- import re
- import typing as t
- import warnings
- from .user_agent import UserAgent as _BaseUserAgent
- if t.TYPE_CHECKING:
- from _typeshed.wsgi import WSGIEnvironment
- class _UserAgentParser:
- platform_rules: t.ClassVar[t.Iterable[t.Tuple[str, str]]] = (
- (" cros ", "chromeos"),
- ("iphone|ios", "iphone"),
- ("ipad", "ipad"),
- (r"darwin\b|mac\b|os\s*x", "macos"),
- ("win", "windows"),
- (r"android", "android"),
- ("netbsd", "netbsd"),
- ("openbsd", "openbsd"),
- ("freebsd", "freebsd"),
- ("dragonfly", "dragonflybsd"),
- ("(sun|i86)os", "solaris"),
- (r"x11\b|lin(\b|ux)?", "linux"),
- (r"nintendo\s+wii", "wii"),
- ("irix", "irix"),
- ("hp-?ux", "hpux"),
- ("aix", "aix"),
- ("sco|unix_sv", "sco"),
- ("bsd", "bsd"),
- ("amiga", "amiga"),
- ("blackberry|playbook", "blackberry"),
- ("symbian", "symbian"),
- )
- browser_rules: t.ClassVar[t.Iterable[t.Tuple[str, str]]] = (
- ("googlebot", "google"),
- ("msnbot", "msn"),
- ("yahoo", "yahoo"),
- ("ask jeeves", "ask"),
- (r"aol|america\s+online\s+browser", "aol"),
- (r"opera|opr", "opera"),
- ("edge|edg", "edge"),
- ("chrome|crios", "chrome"),
- ("seamonkey", "seamonkey"),
- ("firefox|firebird|phoenix|iceweasel", "firefox"),
- ("galeon", "galeon"),
- ("safari|version", "safari"),
- ("webkit", "webkit"),
- ("camino", "camino"),
- ("konqueror", "konqueror"),
- ("k-meleon", "kmeleon"),
- ("netscape", "netscape"),
- (r"msie|microsoft\s+internet\s+explorer|trident/.+? rv:", "msie"),
- ("lynx", "lynx"),
- ("links", "links"),
- ("Baiduspider", "baidu"),
- ("bingbot", "bing"),
- ("mozilla", "mozilla"),
- )
- _browser_version_re = r"(?:{pattern})[/\sa-z(]*(\d+[.\da-z]+)?"
- _language_re = re.compile(
- r"(?:;\s*|\s+)(\b\w{2}\b(?:-\b\w{2}\b)?)\s*;|"
- r"(?:\(|\[|;)\s*(\b\w{2}\b(?:-\b\w{2}\b)?)\s*(?:\]|\)|;)"
- )
- def __init__(self) -> None:
- self.platforms = [(b, re.compile(a, re.I)) for a, b in self.platform_rules]
- self.browsers = [
- (b, re.compile(self._browser_version_re.format(pattern=a), re.I))
- for a, b in self.browser_rules
- ]
- def __call__(
- self, user_agent: str
- ) -> t.Tuple[t.Optional[str], t.Optional[str], t.Optional[str], t.Optional[str]]:
- platform: t.Optional[str]
- browser: t.Optional[str]
- version: t.Optional[str]
- language: t.Optional[str]
- for platform, regex in self.platforms: # noqa: B007
- match = regex.search(user_agent)
- if match is not None:
- break
- else:
- platform = None
- # Except for Trident, all browser key words come after the last ')'
- last_closing_paren = 0
- if (
- not re.compile(r"trident/.+? rv:", re.I).search(user_agent)
- and ")" in user_agent
- and user_agent[-1] != ")"
- ):
- last_closing_paren = user_agent.rindex(")")
- for browser, regex in self.browsers: # noqa: B007
- match = regex.search(user_agent[last_closing_paren:])
- if match is not None:
- version = match.group(1)
- break
- else:
- browser = version = None
- match = self._language_re.search(user_agent)
- if match is not None:
- language = match.group(1) or match.group(2)
- else:
- language = None
- return platform, browser, version, language
- # It wasn't public, but users might have imported it anyway, show a
- # warning if a user created an instance.
- class UserAgentParser(_UserAgentParser):
- """A simple user agent parser. Used by the `UserAgent`.
- .. deprecated:: 2.0
- Will be removed in Werkzeug 2.1. Use a dedicated parser library
- instead.
- """
- def __init__(self) -> None:
- warnings.warn(
- "'UserAgentParser' is deprecated and will be removed in"
- " Werkzeug 2.1. Use a dedicated parser library instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- super().__init__()
- class _deprecated_property(property):
- def __init__(self, fget: t.Callable[["_UserAgent"], t.Any]) -> None:
- super().__init__(fget)
- self.message = (
- "The built-in user agent parser is deprecated and will be"
- f" removed in Werkzeug 2.1. The {fget.__name__!r} property"
- " will be 'None'. Subclass 'werkzeug.user_agent.UserAgent'"
- " and set 'Request.user_agent_class' to use a different"
- " parser."
- )
- def __get__(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
- warnings.warn(self.message, DeprecationWarning, stacklevel=3)
- return super().__get__(*args, **kwargs)
- # This is what Request.user_agent returns for now, only show warnings on
- # attribute access, not creation.
- class _UserAgent(_BaseUserAgent):
- _parser = _UserAgentParser()
- def __init__(self, string: str) -> None:
- super().__init__(string)
- info = self._parser(string)
- self._platform, self._browser, self._version, self._language = info
- @_deprecated_property
- def platform(self) -> t.Optional[str]: # type: ignore
- return self._platform
- @_deprecated_property
- def browser(self) -> t.Optional[str]: # type: ignore
- return self._browser
- @_deprecated_property
- def version(self) -> t.Optional[str]: # type: ignore
- return self._version
- @_deprecated_property
- def language(self) -> t.Optional[str]: # type: ignore
- return self._language
- # This is what users might be importing, show warnings on create.
- class UserAgent(_UserAgent):
- """Represents a parsed user agent header value.
- This uses a basic parser to try to extract some information from the
- header.
- :param environ_or_string: The header value to parse, or a WSGI
- environ containing the header.
- .. deprecated:: 2.0
- Will be removed in Werkzeug 2.1. Subclass
- :class:`werkzeug.user_agent.UserAgent` (note the new module
- name) to use a dedicated parser instead.
- .. versionchanged:: 2.0
- Passing a WSGI environ is deprecated and will be removed in 2.1.
- """
- def __init__(self, environ_or_string: "t.Union[str, WSGIEnvironment]") -> None:
- if isinstance(environ_or_string, dict):
- warnings.warn(
- "Passing an environ to 'UserAgent' is deprecated and"
- " will be removed in Werkzeug 2.1. Pass the header"
- " value string instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- string = environ_or_string.get("HTTP_USER_AGENT", "")
- else:
- string = environ_or_string
- warnings.warn(
- "The 'werkzeug.useragents' module is deprecated and will be"
- " removed in Werkzeug 2.1. The new base API is"
- " 'werkzeug.user_agent.UserAgent'.",
- DeprecationWarning,
- stacklevel=2,
- )
- super().__init__(string)
|