# -*- coding: utf-8 -*- """ werkzeug.useragents ~~~~~~~~~~~~~~~~~~~ This module provides a helper to inspect user agent strings. This module is far from complete but should work for most of the currently available browsers. :copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ import re class UserAgentParser(object): """A simple user agent parser. Used by the `UserAgent`.""" platforms = ( ('iphone|ios', 'iphone'), (r'darwin|mac|os\s*x', 'macos'), ('win', 'windows'), (r'android', 'android'), (r'x11|lin(\b|ux)?', 'linux'), ('(sun|i86)os', 'solaris'), (r'nintendo\s+wii', 'wii'), ('irix', 'irix'), ('hp-?ux', 'hpux'), ('aix', 'aix'), ('sco|unix_sv', 'sco'), ('bsd', 'bsd'), ('amiga', 'amiga') ) browsers = ( ('googlebot', 'google'), ('msnbot', 'msn'), ('yahoo', 'yahoo'), ('ask jeeves', 'ask'), (r'aol|america\s+online\s+browser', 'aol'), ('opera', 'opera'), ('chrome', 'chrome'), ('firefox|firebird|phoenix|iceweasel', 'firefox'), ('galeon', 'galeon'), ('safari', 'safari'), ('webkit', 'webkit'), ('camino', 'camino'), ('konqueror', 'konqueror'), ('k-meleon', 'kmeleon'), ('netscape', 'netscape'), (r'msie|microsoft\s+internet\s+explorer', 'msie'), ('lynx', 'lynx'), ('links', 'links'), ('seamonkey|mozilla', 'seamonkey') ) _browser_version_re = r'(?:%s)[/\sa-z(]*(\d+[.\da-z]+)?(?i)' _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): self.platforms = [(b, re.compile(a, re.I)) for a, b in self.platforms] self.browsers = [(b, re.compile(self._browser_version_re % a)) for a, b in self.browsers] def __call__(self, user_agent): for platform, regex in self.platforms: match = regex.search(user_agent) if match is not None: break else: platform = None for browser, regex in self.browsers: match = regex.search(user_agent) 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 class UserAgent(object): """Represents a user agent. Pass it a WSGI environment or a user agent string and you can inspect some of the details from the user agent string via the attributes. The following attributes exist: .. attribute:: string the raw user agent string .. attribute:: platform the browser platform. The following platforms are currently recognized: - `aix` - `amiga` - `android` - `bsd` - `hpux` - `iphone` - `irix` - `linux` - `macos` - `sco` - `solaris` - `wii` - `windows` .. attribute:: browser the name of the browser. The following browsers are currently recognized: - `aol` * - `ask` * - `camino` - `chrome` - `firefox` - `galeon` - `google` * - `kmeleon` - `konqueror` - `links` - `lynx` - `msie` - `msn` - `netscape` - `opera` - `safari` - `seamonkey` - `webkit` - `yahoo` * (Browsers maked with a star (``*``) are crawlers.) .. attribute:: version the version of the browser .. attribute:: language the language of the browser """ _parser = UserAgentParser() def __init__(self, environ_or_string): if isinstance(environ_or_string, dict): environ_or_string = environ_or_string.get('HTTP_USER_AGENT', '') self.string = environ_or_string self.platform, self.browser, self.version, self.language = \ self._parser(environ_or_string) def to_header(self): return self.string def __str__(self): return self.string def __nonzero__(self): return bool(self.browser) def __repr__(self): return '<%s %r/%s>' % ( self.__class__.__name__, self.browser, self.version ) # conceptionally this belongs in this module but because we want to lazily # load the user agent module (which happens in wrappers.py) we have to import # it afterwards. The class itself has the module set to this module so # pickle, inspect and similar modules treat the object as if it was really # implemented here. from werkzeug.wrappers import UserAgentMixin