useragents.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. # -*- coding: utf-8 -*-
  2. """
  3. werkzeug.useragents
  4. ~~~~~~~~~~~~~~~~~~~
  5. This module provides a helper to inspect user agent strings. This module
  6. is far from complete but should work for most of the currently available
  7. browsers.
  8. :copyright: 2007 Pallets
  9. :license: BSD-3-Clause
  10. """
  11. import re
  12. class UserAgentParser(object):
  13. """A simple user agent parser. Used by the `UserAgent`."""
  14. platforms = (
  15. (" cros ", "chromeos"),
  16. ("iphone|ios", "iphone"),
  17. ("ipad", "ipad"),
  18. (r"darwin|mac|os\s*x", "macos"),
  19. ("win", "windows"),
  20. (r"android", "android"),
  21. ("netbsd", "netbsd"),
  22. ("openbsd", "openbsd"),
  23. ("freebsd", "freebsd"),
  24. ("dragonfly", "dragonflybsd"),
  25. ("(sun|i86)os", "solaris"),
  26. (r"x11|lin(\b|ux)?", "linux"),
  27. (r"nintendo\s+wii", "wii"),
  28. ("irix", "irix"),
  29. ("hp-?ux", "hpux"),
  30. ("aix", "aix"),
  31. ("sco|unix_sv", "sco"),
  32. ("bsd", "bsd"),
  33. ("amiga", "amiga"),
  34. ("blackberry|playbook", "blackberry"),
  35. ("symbian", "symbian"),
  36. )
  37. browsers = (
  38. ("googlebot", "google"),
  39. ("msnbot", "msn"),
  40. ("yahoo", "yahoo"),
  41. ("ask jeeves", "ask"),
  42. (r"aol|america\s+online\s+browser", "aol"),
  43. (r"opera|opr", "opera"),
  44. ("edge", "edge"),
  45. ("chrome|crios", "chrome"),
  46. ("seamonkey", "seamonkey"),
  47. ("firefox|firebird|phoenix|iceweasel", "firefox"),
  48. ("galeon", "galeon"),
  49. ("safari|version", "safari"),
  50. ("webkit", "webkit"),
  51. ("camino", "camino"),
  52. ("konqueror", "konqueror"),
  53. ("k-meleon", "kmeleon"),
  54. ("netscape", "netscape"),
  55. (r"msie|microsoft\s+internet\s+explorer|trident/.+? rv:", "msie"),
  56. ("lynx", "lynx"),
  57. ("links", "links"),
  58. ("Baiduspider", "baidu"),
  59. ("bingbot", "bing"),
  60. ("mozilla", "mozilla"),
  61. )
  62. _browser_version_re = r"(?:%s)[/\sa-z(]*(\d+[.\da-z]+)?"
  63. _language_re = re.compile(
  64. r"(?:;\s*|\s+)(\b\w{2}\b(?:-\b\w{2}\b)?)\s*;|"
  65. r"(?:\(|\[|;)\s*(\b\w{2}\b(?:-\b\w{2}\b)?)\s*(?:\]|\)|;)"
  66. )
  67. def __init__(self):
  68. self.platforms = [(b, re.compile(a, re.I)) for a, b in self.platforms]
  69. self.browsers = [
  70. (b, re.compile(self._browser_version_re % a, re.I))
  71. for a, b in self.browsers
  72. ]
  73. def __call__(self, user_agent):
  74. for platform, regex in self.platforms: # noqa: B007
  75. match = regex.search(user_agent)
  76. if match is not None:
  77. break
  78. else:
  79. platform = None
  80. for browser, regex in self.browsers: # noqa: B007
  81. match = regex.search(user_agent)
  82. if match is not None:
  83. version = match.group(1)
  84. break
  85. else:
  86. browser = version = None
  87. match = self._language_re.search(user_agent)
  88. if match is not None:
  89. language = match.group(1) or match.group(2)
  90. else:
  91. language = None
  92. return platform, browser, version, language
  93. class UserAgent(object):
  94. """Represents a user agent. Pass it a WSGI environment or a user agent
  95. string and you can inspect some of the details from the user agent
  96. string via the attributes. The following attributes exist:
  97. .. attribute:: string
  98. the raw user agent string
  99. .. attribute:: platform
  100. the browser platform. ``None`` if not recognized.
  101. The following platforms are currently recognized:
  102. - `aix`
  103. - `amiga`
  104. - `android`
  105. - `blackberry`
  106. - `bsd`
  107. - `chromeos`
  108. - `dragonflybsd`
  109. - `freebsd`
  110. - `hpux`
  111. - `ipad`
  112. - `iphone`
  113. - `irix`
  114. - `linux`
  115. - `macos`
  116. - `netbsd`
  117. - `openbsd`
  118. - `sco`
  119. - `solaris`
  120. - `symbian`
  121. - `wii`
  122. - `windows`
  123. .. attribute:: browser
  124. the name of the browser. ``None`` if not recognized.
  125. The following browsers are currently recognized:
  126. - `aol` *
  127. - `ask` *
  128. - `baidu` *
  129. - `bing` *
  130. - `camino`
  131. - `chrome`
  132. - `edge`
  133. - `firefox`
  134. - `galeon`
  135. - `google` *
  136. - `kmeleon`
  137. - `konqueror`
  138. - `links`
  139. - `lynx`
  140. - `mozilla`
  141. - `msie`
  142. - `msn`
  143. - `netscape`
  144. - `opera`
  145. - `safari`
  146. - `seamonkey`
  147. - `webkit`
  148. - `yahoo` *
  149. (Browsers marked with a star (``*``) are crawlers.)
  150. .. attribute:: version
  151. the version of the browser. ``None`` if not recognized.
  152. .. attribute:: language
  153. the language of the browser. ``None`` if not recognized.
  154. """
  155. _parser = UserAgentParser()
  156. def __init__(self, environ_or_string):
  157. if isinstance(environ_or_string, dict):
  158. environ_or_string = environ_or_string.get("HTTP_USER_AGENT", "")
  159. self.string = environ_or_string
  160. self.platform, self.browser, self.version, self.language = self._parser(
  161. environ_or_string
  162. )
  163. def to_header(self):
  164. return self.string
  165. def __str__(self):
  166. return self.string
  167. def __nonzero__(self):
  168. return bool(self.browser)
  169. __bool__ = __nonzero__
  170. def __repr__(self):
  171. return "<%s %r/%s>" % (self.__class__.__name__, self.browser, self.version)