pywurfl.algorithms.wurfl.handlers

Package pywurfl :: Package algorithms :: Package wurfl :: Module handlers |
[hide private]
[frames] | no frames]

# Source Code for Module pywurfl.algorithms.wurfl.handlers

      1  # pywurfl - Wireless Universal Resource File Tools in Python
      2  # Copyright (C) 2006-2011 Armand Lynch
      3  #
      4  # This library is free software; you can redistribute it and/or modify it
      5  # under the terms of the GNU Lesser General Public License as published by the
      6  # Free Software Foundation; either version 2.1 of the License, or (at your
      7  # option) any later version.
      8  #
      9  # This library is distributed in the hope that it will be useful, but WITHOUT
     10  # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
     11  # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
     12  # details.
     13  #
     14  # You should have received a copy of the GNU Lesser General Public License
     15  # along with this library; if not, write to the Free Software Foundation, Inc.,
     16  # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
     17  #
     18  # Armand Lynch <lyncha@users.sourceforge.net>
     19
     20  __doc__ = """
     21  This module contains the supporting classes for the Two Step Analysis user agent
     22  algorithm that is used as the primary way to match user agents with the Java API
     23  for the WURFL.
     24
     25  A description of the way the following source is intended to work can be found
     26  within the source for the original Java API implementation here:
     27  http://sourceforge.net/projects/wurfl/files/WURFL Java API/
     28
     29  The original Java code is GPLd and Copyright (c) WURFL-Pro srl
     30  """
     31
     32  __author__ = "Armand Lynch <lyncha@users.sourceforge.net>"
     33  __copyright__ = "Copyright 2011, Armand Lynch"
     34  __license__ = "LGPL"
     35  __url__ = "http://celljam.net/"
     36  __version__ = "1.2.1"
     37
     38  import re
     39
     40  from pywurfl.algorithms.wurfl.utils import (first_semi_colon, first_slash,
     41                                              first_space, is_mobile_browser,
     42                                              second_slash, third_space)
     43  from pywurfl.algorithms.wurfl.utils import indexof_or_length as iol
     44  from pywurfl.algorithms.wurfl import normalizers
     45  from pywurfl.algorithms.wurfl.strategies import ld_match, ris_match
     46
     47
     48 -class AbstractMatcher(object):
     49      user_agent_map = {}
     50
     51 -    def __init__(self, normalizer=normalizers.generic):
     52          self.normalizer = normalizer
     53          self.known_user_agents = set()
     54
     55 -    def add(self, user_agent, wurfl_id):
     56          self.known_user_agents.add(user_agent)
     57          self.user_agent_map[user_agent] = wurfl_id
     58
     59      @property
     60 -    def user_agents(self):
     61          return sorted(self.known_user_agents)
     62
     63 -    def can_handle(self, user_agent):
     64          raise NotImplementedError
     65
     66 -    def __call__(self, user_agent):
     67          normalized_user_agent = self.normalizer(user_agent)
     68          devid = self.conclusive_match(normalized_user_agent)
     69          if not devid or devid == u"generic":
     70              devid = self.recovery_match(normalized_user_agent)
     71          if not devid or devid == u"generic":
     72              devid = self.catch_all_recovery_match(user_agent)
     73          return devid
     74
     75 -    def conclusive_match(self, user_agent):
     76          match = self.find_matching_ua(user_agent)
     77          #print "%s -> conclusive_match -> %s" % (user_agent, match)
     78          devid = self.user_agent_map.get(match, u"generic")
     79          return devid
     80
     81 -    def find_matching_ua(self, user_agent):
     82          tolerance = first_slash(user_agent)
     83          match = self.ris_matcher(user_agent, tolerance)
     84          #print "AbstractMatcher %s -> f_m_ua -> %s" % (user_agent, match)
     85          return match
     86
     87 -    def recovery_match(self, user_agent):
     88          return u"generic"
     89
     90      recovery_map = (
     91          # Openwave
     92          (u"UP.Browser/7.2", u"opwv_v72_generic"),
     93          (u"UP.Browser/7", u"opwv_v7_generic"),
     94          (u"UP.Browser/6.2", u"opwv_v62_generic"),
     95          (u"UP.Browser/6", u"opwv_v6_generic"),
     96          (u"UP.Browser/5", u"upgui_generic"),
     97          (u"UP.Browser/4", u"uptext_generic"),
     98          (u"UP.Browser/3", u"uptext_generic"),
     99
    100          # Series 60
    101          (u"Series60", u"nokia_generic_series60"),
    102
    103          # Access/Net Front
    104          (u"NetFront/3.0", u"generic_netfront_ver3"),
    105          (u"ACS-NF/3.0", u"generic_netfront_ver3"),
    106          (u"NetFront/3.1", u"generic_netfront_ver3_1"),
    107          (u"ACS-NF/3.1", u"generic_netfront_ver3_1"),
    108          (u"NetFront/3.2", u"generic_netfront_ver3_2"),
    109          (u"ACS-NF/3.2", u"generic_netfront_ver3_2"),
    110          (u"NetFront/3.3", u"generic_netfront_ver3_3"),
    111          (u"ACS-NF/3.3", u"generic_netfront_ver3_3"),
    112          (u"NetFront/3.4", u"generic_netfront_ver3_4"),
    113          (u"NetFront/3.5", u"generic_netfront_ver3_5"),
    114          (u"NetFront/4.0", u"generic_netfront_ver4"),
    115          (u"NetFront/4.1", u"generic_netfront_ver4_1"),
    116
    117          # Windows CE
    118          (u"Windows CE", u"generic_ms_mobile_browser_ver1"),
    119
    120          # web browsers?
    121          (u"Mozilla/4.0", u"generic_web_browser"),
    122          (u"Mozilla/5.0", u"generic_web_browser"),
    123          (u"Mozilla/6.0", u"generic_web_browser"),
    124
    125          # Generic XHTML
    126          (u"Mozilla/", u"generic_xhtml"),
    127          (u"ObigoInternetBrowser/Q03C", u"generic_xhtml"),
    128          (u"AU-MIC/2", u"generic_xhtml"),
    129          (u"AU-MIC-", u"generic_xhtml"),
    130          (u"AU-OBIGO/", u"generic_xhtml"),
    131          (u"Obigo/Q03", u"generic_xhtml"),
    132          (u"Obigo/Q04", u"generic_xhtml"),
    133          (u"ObigoInternetBrowser/2", u"generic_xhtml"),
    134          (u"Teleca Q03B1", u"generic_xhtml"),
    135
    136          # Opera Mini
    137          (u"Opera Mini/1", u"browser_opera_mini_release1"),
    138          (u"Opera Mini/2", u"browser_opera_mini_release2"),
    139          (u"Opera Mini/3", u"browser_opera_mini_release3"),
    140          (u"Opera Mini/4", u"browser_opera_mini_release4"),
    141          (u"Opera Mini/5", u"browser_opera_mini_release5"),
    142
    143          # DoCoMo
    144          (u"DoCoMo", u"docomo_generic_jap_ver1"),
    145          (u"KDDI", u"docomo_generic_jap_ver1"))
    146
    147 -    def catch_all_recovery_match(self, user_agent):
    148
    149          match = u"generic"
    150          for partial_agent, wdevice in self.recovery_map:
    151              if partial_agent in user_agent:
    152                  match = wdevice
    153                  break
    154          return match
    155
    156 -    def ris_matcher(self, user_agent, tolerance):
    157          return ris_match(self.user_agents, user_agent, tolerance)
    158
    159 -    def ld_matcher(self, user_agent, tolerance):
    160          return ld_match(self.user_agents, user_agent, tolerance)
    161
    162
    163 -class AlcatelMatcher(AbstractMatcher):
    164 -    def can_handle(self, user_agent):
    165          return (user_agent.startswith(u"Alcatel") or
    166                  user_agent.startswith(u"ALCATEL"))
    167
    168
    169 -class AndroidMatcher(AbstractMatcher):
    170
    171      androids = {}
    172      androids[u""] = u"generic_android"
    173      androids[u"1_5"] = u"generic_android_ver1_5"
    174      androids[u"1_6"] = u"generic_android_ver1_6"
    175      androids[u"2_0"] = u"generic_android_ver2"
    176      androids[u"2_1"] = u"generic_android_ver2_1"
    177      androids[u"2_2"] = u"generic_android_ver2_2"
    178
    179      android_os_re = re.compile(r".*Android[\s/](\d)\.(\d)")
    180
    181 -    def can_handle(self, user_agent):
    182          return user_agent.startswith(u"Mozilla") and u"Android" in user_agent
    183
    184 -    def find_matching_ua(self, user_agent):
    185          tolerance = iol(user_agent, u" ",
    186                          start_index=iol(user_agent, u"Android"))
    187          match = self.ris_matcher(user_agent, tolerance)
    188          #print "AndroidMatcher %s -> f_m_ua -> %s" % (user_agent, match)
    189          return match
    190
    191 -    def recovery_match(self, user_agent):
    192          if u"Froyo" in user_agent:
    193              return u"generic_android_ver2_2"
    194          return self.androids.get(self.android_os_version(user_agent),
    195                                   u"generic_android")
    196
    197 -    def android_os_version(self, user_agent):
    198          match = self.android_os_re.match(user_agent)
    199          if match:
    200              return u"%s_%s" % (match.group(1), match.group(2))
    201
    202
    203 -class AOLMatcher(AbstractMatcher):
    204 -    def can_handle(self, user_agent):
    205          return not is_mobile_browser(user_agent) and u"AOL" in user_agent
    206
    207
    208 -class AppleMatcher(AbstractMatcher):
    209      APPLE_LD_TOLERANCE = 5
    210
    211 -    def can_handle(self, user_agent):
    212          return (u"iPhone" in user_agent or u"iPod" in user_agent or u"iPad" in
    213                  user_agent)
    214
    215 -    def find_matching_ua(self, user_agent):
    216          if user_agent.startswith(u"Apple"):
    217              tolerance = third_space(user_agent)
    218          else:
    219              tolerance = first_semi_colon(user_agent)
    220          match = self.ris_matcher(user_agent, tolerance)
    221          #print "AppleMatcher %s -> f_m_ua -> %s" % (user_agent, match)
    222          return match
    223
    224 -    def recovery_match(self, user_agent):
    225          if u"iPad" in user_agent:
    226              return "apple_ipad_ver1"
    227          if u"iPod" in user_agent:
    228              return u"apple_ipod_touch_ver1"
    229          return u"apple_iphone_ver1"
    230
    231
    232 -class BenQMatcher(AbstractMatcher):
    233 -    def can_handle(self, user_agent):
    234          return user_agent.startswith(u"BENQ") or user_agent.startswith(u"BenQ")
    235
    236
    237 -class BlackberryMatcher(AbstractMatcher):
    238      blackberries = {}
    239      blackberries[u"2."] = u"blackberry_generic_ver2"
    240      blackberries[u"3.2"] = u"blackberry_generic_ver3_sub2"
    241      blackberries[u"3.3"] = u"blackberry_generic_ver3_sub30"
    242      blackberries[u"3.5"] = u"blackberry_generic_ver3_sub50"
    243      blackberries[u"3.6"] = u"blackberry_generic_ver3_sub60"
    244      blackberries[u"3.7"] = u"blackberry_generic_ver3_sub70"
    245      blackberries[u"4.1"] = u"blackberry_generic_ver4_sub10"
    246      blackberries[u"4.2"] = u"blackberry_generic_ver4_sub20"
    247      blackberries[u"4.3"] = u"blackberry_generic_ver4_sub30"
    248      blackberries[u"4.5"] = u"blackberry_generic_ver4_sub50"
    249      blackberries[u"4.6"] = u"blackberry_generic_ver4_sub60"
    250      blackberries[u"4.7"] = u"blackberry_generic_ver4_sub70"
    251      blackberries[u"4."] = u"blackberry_generic_ver4"
    252      blackberries[u"5."] = u"blackberry_generic_ver5"
    253      blackberries[u"6."] = u"blackberry_generic_ver6"
    254
    255      blackberry_os_re = re.compile(r".*Black[Bb]erry[^/\s]+/(\d\.\d)")
    256
    257 -    def can_handle(self, user_agent):
    258          return u"BlackBerry" in user_agent or u"Blackberry" in user_agent
    259
    260 -    def recovery_match(self, user_agent):
    261          match = u"generic"
    262          version = self.blackberry_os_version(user_agent)
    263          if version:
    264              match = self.blackberries.get(version, u"generic")
    265              if match == u"generic":
    266                  match = self.blackberries.get(version[:-1], u"generic")
    267          return match
    268
    269 -    def blackberry_os_version(self, user_agent):
    270          match = self.blackberry_os_re.match(user_agent)
    271          if match:
    272              return match.group(1)
    273
    274
    275 -class BotMatcher(AbstractMatcher):
    276      bots = (u"bot", u"crawler", u"spider", u"novarra", u"transcoder",
    277              u"yahoo! searchmonkey", u"yahoo! slurp", u"feedfetcher-google",
    278              u"toolbar", u"mowser", u"mediapartners-google", u"azureus",
    279              u"inquisitor", u"baiduspider", u"baidumobaider", u"indy library",
    280              u"slurp", u"crawl", u"wget", u"ucweblient", u"snoopy",
    281              u"mozfdsilla", u"ask jeeves", u"jeeves/teoma", u"mechanize",
    282              u"http client", u"servicemonitor", u"httpunit", u"hatena",
    283              u"ichiro")
    284
    285      BOT_TOLERANCE = 4
    286
    287 -    def can_handle(self, user_agent):
    288          user_agent = user_agent.lower()
    289          for bot in self.bots:
    290              if bot in user_agent:
    291                  return True
    292          return False
    293
    294 -    def find_matching_ua(self, user_agent):
    295          match = self.ld_matcher(user_agent, self.BOT_TOLERANCE)
    296          return match
    297
    298 -    def recovery_match(self, user_agent):
    299          return u"generic_web_crawler"
    300
    301
    302 -class CatchAllMatcher(AbstractMatcher):
    303      MOZILLA_LD_TOLERANCE = 4
    304
    305 -    def can_handle(self, user_agent):
    306          return True
    307
    308 -    def find_matching_ua(self, user_agent):
    309          if user_agent.startswith(u"Mozilla"):
    310              if user_agent.startswith(u"Mozilla/4"):
    311                  match = ld_match(self.extract_uas(u"Mozilla/4"), user_agent,
    312                                   self.MOZILLA_LD_TOLERANCE)
    313              elif user_agent.startswith(u"Mozilla/5"):
    314                  match = ld_match(self.extract_uas(u"Mozilla/5"), user_agent,
    315                                   self.MOZILLA_LD_TOLERANCE)
    316              else:
    317                  match = ld_match(self.extract_uas(u"Mozilla"), user_agent,
    318                                   self.MOZILLA_LD_TOLERANCE)
    319          else:
    320              match = super(CatchAllMatcher, self).find_matching_ua(user_agent)
    321          #print "CatchAllMatcher %s -> f_m_ua -> %s" % (user_agent, match)
    322          return match
    323
    324 -    def extract_uas(self, start):
    325          return (x for x in self.user_agents if x.startswith(start))
    326
    327
    328 -class ChromeMatcher(AbstractMatcher):
    329 -    def can_handle(self, user_agent):
    330          return not is_mobile_browser(user_agent) and u"Chrome" in user_agent
    331
    332
    333 -class DoCoMoMatcher(AbstractMatcher):
    334 -    def can_handle(self, user_agent):
    335          return user_agent.startswith(u"DoCoMo")
    336
    337 -    def find_matching_ua(self, user_agent):
    338          return u""
    339
    340 -    def recovery_match(self, user_agent):
    341          if user_agent.startswith(u"DoCoMo/2"):
    342              return u"docomo_generic_jap_ver2"
    343          return u"docomo_generic_jap_ver1"
    344
    345
    346 -class FirefoxMatcher(AbstractMatcher):
    347 -    def can_handle(self, user_agent):
    348          return not is_mobile_browser(user_agent) and u"Firefox" in user_agent
    349
    350
    351 -class GrundigMatcher(AbstractMatcher):
    352 -    def can_handle(self, user_agent):
    353          return (user_agent.startswith(u"Grundig") or
    354                  user_agent.startswith(u"GRUNDIG"))
    355
    356
    357 -class HTCMatcher(AbstractMatcher):
    358 -    def can_handle(self, user_agent):
    359          return user_agent.startswith(u"HTC") or u"XV6875.1" in user_agent
    360
    361
    362 -class KDDIMatcher(AbstractMatcher):
    363 -    def can_handle(self, user_agent):
    364          return u"KDDI" in user_agent
    365
    366 -    def find_matching_ua(self, user_agent):
    367          if user_agent.startswith(u"KDDI/"):
    368              tolerance = second_slash(user_agent)
    369          elif user_agent.startswith(u"KDDI"):
    370              tolerance = first_slash(user_agent)
    371          else:
    372              tolerance = iol(user_agent, ")")
    373          match = self.ris_matcher(user_agent, tolerance)
    374          #print "KDDIMatcher %s -> f_m_ua -> %s" % (user_agent, match)
    375          return match
    376
    377 -    def recovery_match(self, user_agent):
    378          if u"Opera" in user_agent:
    379              return u"opera"
    380          return u"opwv_v62_generic"
    381
    382
    383 -class KonquerorMatcher(AbstractMatcher):
    384 -    def can_handle(self, user_agent):
    385          return not is_mobile_browser(user_agent) and u"Konqueror" in user_agent
    386
    387
    388 -class KyoceraMatcher(AbstractMatcher):
    389 -    def can_handle(self, user_agent):
    390          return (user_agent.startswith(u"kyocera") or
    391                  user_agent.startswith(u"QC-") or
    392                  user_agent.startswith(u"KWC-"))
    393
    394
    395 -class LGMatcher(AbstractMatcher):
    396 -    def can_handle(self, user_agent):
    397          return (user_agent.startswith(u"lg") or u"LG-" in user_agent or
    398                  u"LGE" in user_agent)
    399
    400 -    def find_matching_ua(self, user_agent):
    401          tolerance = iol(user_agent, u"/",
    402                          start_index=user_agent.upper().index(u"LG"))
    403          match = self.ris_matcher(user_agent, tolerance)
    404          return match
    405
    406
    407 -class LGUPLUSMatcher(AbstractMatcher):
    408      lgpluses = (
    409          (u"generic_lguplus_rexos_facebook_browser",
    410              (u"Windows NT 5", u"POLARIS")),
    411          (u"generic_lguplus_rexos_webviewer_browser",
    412              (u"Windows NT 5",)),
    413          (u"generic_lguplus_winmo_facebook_browser",
    414              (u"Windows CE", u"POLARIS")),
    415          (u"generic_lguplus_android_webkit_browser",
    416              (u"Android", u"AppleWebKit")))
    417
    418 -    def can_handle(self, user_agent):
    419          return u"lgtelecom" in user_agent or u"LGUPLUS" in user_agent
    420
    421 -    def conclusive_match(self, user_agent):
    422          return u"generic"
    423
    424 -    def recovery_match(self, user_agent):
    425          for wid, searches in self.lgpluses:
    426              for search in searches:
    427                  if search not in user_agent:
    428                      break
    429              else:
    430                  return wid
    431          return u"generic_lguplus"
    432
    433
    434 -class MaemoMatcher(AbstractMatcher):
    435 -    def can_handle(self, user_agent):
    436          return u"Maemo " in user_agent
    437
    438 -    def find_matching_ua(self, user_agent):
    439          tolerance = first_space(user_agent)
    440          match = self.ris_matcher(user_agent, tolerance)
    441          return match
    442
    443
    444 -class MitsubishiMatcher(AbstractMatcher):
    445 -    def can_handle(self, user_agent):
    446          return user_agent.startswith(u"Mitsu")
    447
    448
    449 -class MotorolaMatcher(AbstractMatcher):
    450      MOTOROLA_TOLERANCE = 5
    451
    452 -    def can_handle(self, user_agent):
    453          return (user_agent.startswith(u"Mot-") or
    454                  u"MOT-" in user_agent or
    455                  u"Motorola" in user_agent)
    456
    457 -    def find_matching_ua(self, user_agent):
    458          if (user_agent.startswith(u"Mot-") or user_agent.startswith(u"MOT-") or
    459              user_agent.startswith(u"Motorola")):
    460              match = super(MotorolaMatcher, self).find_matching_ua(user_agent)
    461          else:
    462              match = self.ld_matcher(user_agent, self.MOTOROLA_TOLERANCE)
    463          #print "MotorolaMatcher %s -> f_m_ua -> %s" % (user_agent, match)
    464          return match
    465
    466 -    def recovery_match(self, user_agent):
    467          match = u"generic"
    468          if u"MIB/2.2" in user_agent or u"MIB/BER2.2" in user_agent:
    469              match = u"mot_mib22_generic"
    470          return match
    471
    472
    473 -class MSIEMatcher(AbstractMatcher):
    474 -    def can_handle(self, user_agent):
    475          return (not is_mobile_browser(user_agent) and
    476                  user_agent.startswith(u"Mozilla") and
    477                  u"MSIE" in user_agent)
    478
    479
    480 -class NecMatcher(AbstractMatcher):
    481      NEC_LD_TOLERANCE = 2
    482
    483 -    def can_handle(self, user_agent):
    484          return user_agent.startswith(u"NEC") or user_agent.startswith(u"KGT")
    485
    486 -    def find_matching_ua(self, user_agent):
    487          if user_agent.startswith(u"NEC"):
    488              match = super(NecMatcher, self).find_matching_ua(user_agent)
    489          else:
    490              match = self.ld_matcher(user_agent, self.NEC_LD_TOLERANCE)
    491          #print "NecMatcher %s -> f_m_ua -> %s" % (user_agent, match)
    492          return match
    493
    494
    495 -class NokiaMatcher(AbstractMatcher):
    496 -    def can_handle(self, user_agent):
    497          return u"Nokia" in user_agent
    498
    499 -    def find_matching_ua(self, user_agent):
    500          tol1 = iol(user_agent, u"/", start_index=user_agent.index(u"Nokia"))
    501          tol2 = iol(user_agent, u" ", start_index=user_agent.index(u"Nokia"))
    502          tolerance = tol1 if tol1 < tol2 else tol2
    503          #print "NokiaMatcher tolerance %s" % tolerance
    504          match = self.ris_matcher(user_agent, tolerance)
    505          #print "NokiaMatcher %s -> f_m_ua -> %s" % (user_agent, match)
    506          return match
    507
    508 -    def recovery_match(self, user_agent):
    509          match = u"generic"
    510          if u"Series60" in user_agent:
    511              match = u"nokia_generic_series60"
    512          elif u"Series80" in user_agent:
    513              match = u"nokia_generic_series80"
    514          return match
    515
    516
    517 -class OperaMatcher(AbstractMatcher):
    518      OPERA_TOLERANCE = 1
    519
    520      operas = {}
    521      operas["7"] = u"opera_7"
    522      operas["8"] = u"opera_8"
    523      operas["9"] = u"opera_9"
    524      operas["10"] = u"opera_10"
    525
    526      opera_re = re.compile(r".*Opera[\s/](\d+).*")
    527
    528 -    def can_handle(self, user_agent):
    529          return not is_mobile_browser(user_agent) and u"Opera" in user_agent
    530
    531 -    def find_matching_ua(self, user_agent):
    532          match = self.ld_matcher(user_agent, self.OPERA_TOLERANCE)
    533          #print "OperaMatcher %s -> f_m_ua -> %s" % (user_agent, match)
    534          return match
    535
    536 -    def recovery_match(self, user_agent):
    537          match = self.opera_re.match(user_agent)
    538          if match:
    539              return self.operas.get(match.group(1), u"opera")
    540          return u"opera"
    541
    542
    543 -class OperaMiniMatcher(AbstractMatcher):
    544 -    def can_handle(self, user_agent):
    545          return u"Opera Mini" in user_agent
    546
    547 -    def recovery_match(self, user_agent):
    548          match = u""
    549          if u"Opera Mini/1" in user_agent:
    550              match = u"browser_opera_mini_release1"
    551          elif u"Opera Mini/2" in user_agent:
    552              match = u"browser_opera_mini_release2"
    553          elif u"Opera Mini/3" in user_agent:
    554              match = u"browser_opera_mini_release3"
    555          elif u"Opera Mini/4" in user_agent:
    556              match = u"browser_opera_mini_release4"
    557          elif u"Opera Mini/5" in user_agent:
    558              match = u"browser_opera_mini_release5"
    559          return match
    560
    561
    562 -class PanasonicMatcher(AbstractMatcher):
    563 -    def can_handle(self, user_agent):
    564          return user_agent.startswith(u"Panasonic")
    565
    566
    567 -class PantechMatcher(AbstractMatcher):
    568      PANTECH_LD_TOLERANCE = 4
    569
    570 -    def can_handle(self, user_agent):
    571          return (user_agent.startswith(u"Pantech") or
    572                  user_agent.startswith(u"PT-") or
    573                  user_agent.startswith(u"PANTECH") or
    574                  user_agent.startswith(u"PG-"))
    575
    576 -    def find_matching_ua(self, user_agent):
    577          if user_agent.startswith(u"Pantech"):
    578              match = self.ld_matcher(user_agent, self.PANTECH_LD_TOLERANCE)
    579          else:
    580              match = super(PantechMatcher, self).find_matching_ua(user_agent)
    581          #print "PantechMatcher %s -> f_m_ua -> %s" % (user_agent, match)
    582          return match
    583
    584
    585 -class PhilipsMatcher(AbstractMatcher):
    586 -    def can_handle(self, user_agent):
    587          return (user_agent.startswith(u"Philips") or
    588                  user_agent.startswith(u"PHILIPS"))
    589
    590
    591 -class PortalmmmMatcher(AbstractMatcher):
    592 -    def can_handle(self, user_agent):
    593          return user_agent.startswith(u"portalmmm")
    594
    595 -    def find_matching_ua(self, user_agent):
    596          return u""
    597
    598
    599 -class QtekMatcher(AbstractMatcher):
    600 -    def can_handle(self, user_agent):
    601          return user_agent.startswith(u"Qtek")
    602
    603
    604 -class SafariMatcher(AbstractMatcher):
    605 -    def can_handle(self, user_agent):
    606          return (not is_mobile_browser(user_agent) and
    607                  user_agent.startswith(u"Mozilla") and
    608                  u"Safari" in user_agent)
    609
    610 -    def recovery_match(self, user_agent):
    611          if u"Macintosh" in user_agent or u"Windows" in user_agent:
    612              match =  u"generic_web_browser"
    613          else:
    614              match = u"generic"
    615          return match
    616
    617
    618 -class SagemMatcher(AbstractMatcher):
    619 -    def can_handle(self, user_agent):
    620          return (user_agent.startswith(u"Sagem") or
    621                  user_agent.startswith(u"SAGEM"))
    622
    623
    624 -class SamsungMatcher(AbstractMatcher):
    625      SAMSUNGS = [u"SEC-", u"SAMSUNG-", u"SCH", u"Samsung", u"SPH", u"SGH",
    626                  u"SAMSUNG/"]
    627
    628 -    def can_handle(self, user_agent):
    629          return (u"Samsung/SGH" in user_agent or
    630                  u"Samsung" in user_agent or
    631                  user_agent.startswith(u"SEC-") or
    632                  user_agent.startswith(u"SAMSUNG") or
    633                  user_agent.startswith(u"SPH") or
    634                  user_agent.startswith(u"SGH") or
    635                  user_agent.startswith(u"SCH"))
    636
    637 -    def find_matching_ua(self, user_agent):
    638          for sams in self.SAMSUNGS:
    639              if sams in user_agent:
    640                  tol1 = iol(user_agent, u"/", start_index=user_agent.index(sams))
    641                  tol2 = iol(user_agent, u" ", start_index=user_agent.index(sams))
    642                  tolerance = tol1 if tol1 < tol2 else tol2
    643                  break
    644          else:
    645              tolerance = len(user_agent)
    646
    647          match = self.ris_matcher(user_agent, tolerance)
    648          #print "SamsungMatcher %s -> f_m_ua -> %s" % (user_agent, match)
    649          return match
    650
    651
    652 -class SanyoMatcher(AbstractMatcher):
    653 -    def can_handle(self, user_agent):
    654          return (user_agent.startswith(u"Sanyo") or
    655                  user_agent.startswith(u"SANYO"))
    656
    657
    658 -class SharpMatcher(AbstractMatcher):
    659 -    def can_handle(self, user_agent):
    660          return (user_agent.startswith(u"Sharp") or
    661                  user_agent.startswith(u"SHARP"))
    662
    663
    664 -class SiemensMatcher(AbstractMatcher):
    665 -    def can_handle(self, user_agent):
    666          return user_agent.startswith(u"SIE-")
    667
    668
    669 -class SonyEricssonMatcher(AbstractMatcher):
    670 -    def can_handle(self, user_agent):
    671          return u"SonyEricsson" in user_agent
    672
    673 -    def find_matching_ua(self, user_agent):
    674          if user_agent.startswith(u"SonyEricsson"):
    675              match = super(SonyEricssonMatcher, self).find_matching_ua(user_agent)
    676          else:
    677              tolerance = second_slash(user_agent)
    678              match = self.ris_matcher(user_agent, tolerance)
    679          #print "SonyEricssonMatcher %s -> f_m_ua -> %s" % (user_agent, match)
    680          return match
    681
    682
    683 -class SPVMatcher(AbstractMatcher):
    684 -    def can_handle(self, user_agent):
    685          return u"SPV" in user_agent
    686
    687 -    def find_matching_ua(self, user_agent):
    688          tolerance = iol(user_agent, u";", start_index=iol(user_agent, u"SPV"))
    689          match = self.ris_matcher(user_agent, tolerance)
    690          return match
    691
    692
    693 -class ToshibaMatcher(AbstractMatcher):
    694 -    def can_handle(self, user_agent):
    695          return user_agent.startswith(u"Toshiba")
    696
    697
    698 -class VodafoneMatcher(AbstractMatcher):
    699 -    def can_handle(self, user_agent):
    700          return user_agent.startswith(u"Vodafone")
    701
    702 -    def find_matching_ua(self, user_agent):
    703          tolerance = iol(user_agent, u"/", 3)
    704          match = self.ris_matcher(user_agent, tolerance)
    705          #print "VodafoneMatcher %s -> f_m_ua -> %s" % (user_agent, match)
    706          return match
    707
    708
    709 -class WindowsCEMatcher(AbstractMatcher):
    710      WINDOWS_CE_TOLERANCE = 3
    711
    712 -    def can_handle(self, user_agent):
    713          return (u"Mozilla/" in user_agent and (u"Windows CE" in user_agent or
    714                                                 u"WindowsCE" in user_agent or
    715                                                 u"ZuneWP7" in user_agent))
    716
    717 -    def find_matching_ua(self, user_agent):
    718          match = self.ld_matcher(user_agent, self.WINDOWS_CE_TOLERANCE)
    719          return match
    720
    721 -    def recovery_match(self, user_agent):
    722          return u"generic_ms_mobile_browser_ver1"
    723
    724
    725  handlers = [NokiaMatcher(),
    726              LGUPLUSMatcher(),
    727              AndroidMatcher(normalizers.android),
    728              SonyEricssonMatcher(),
    729              MotorolaMatcher(),
    730              BlackberryMatcher(),
    731              SiemensMatcher(),
    732              SagemMatcher(),
    733              SamsungMatcher(),
    734              PanasonicMatcher(),
    735              NecMatcher(),
    736              QtekMatcher(),
    737              MitsubishiMatcher(),
    738              PhilipsMatcher(),
    739              LGMatcher(normalizers.lg),
    740              AppleMatcher(),
    741              KyoceraMatcher(),
    742              AlcatelMatcher(),
    743              SharpMatcher(),
    744              SanyoMatcher(),
    745              BenQMatcher(),
    746              PantechMatcher(),
    747              ToshibaMatcher(),
    748              GrundigMatcher(),
    749              HTCMatcher(),
    750              BotMatcher(),
    751              SPVMatcher(),
    752              WindowsCEMatcher(),
    753              PortalmmmMatcher(),
    754              DoCoMoMatcher(),
    755              KDDIMatcher(),
    756              VodafoneMatcher(),
    757              OperaMiniMatcher(),
    758              MaemoMatcher(normalizers.maemo),
    759              ChromeMatcher(normalizers.chrome),
    760              AOLMatcher(),
    761              OperaMatcher(),
    762              KonquerorMatcher(normalizers.konqueror),
    763              SafariMatcher(normalizers.safari),
    764              FirefoxMatcher(normalizers.firefox),
    765              MSIEMatcher(normalizers.msie),
    766              CatchAllMatcher()]
    767