1   
   2   
   3   
   4  """ 
   5  This file is part of the web2py Web Framework 
   6  Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> 
   7  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
   8   
   9  Thanks to ga2arch for help with IS_IN_DB and IS_NOT_IN_DB on GAE 
  10  """ 
  11   
  12  import os 
  13  import re 
  14  import datetime 
  15  import time 
  16  import cgi 
  17  import urllib 
  18  import struct 
  19  import decimal 
  20  import unicodedata 
  21  from cStringIO import StringIO 
  22  from utils import simple_hash, web2py_uuid, DIGEST_ALG_BY_SIZE 
  23  from dal import FieldVirtual, FieldMethod 
  24   
  25  JSONErrors = (NameError, TypeError, ValueError, AttributeError, 
  26                KeyError) 
  27  try: 
  28      import json as simplejson 
  29  except ImportError: 
  30      from gluon.contrib import simplejson 
  31      from gluon.contrib.simplejson.decoder import JSONDecodeError 
  32      JSONErrors += (JSONDecodeError,) 
  33   
  34  __all__ = [ 
  35      'ANY_OF', 
  36      'CLEANUP', 
  37      'CRYPT', 
  38      'IS_ALPHANUMERIC', 
  39      'IS_DATE_IN_RANGE', 
  40      'IS_DATE', 
  41      'IS_DATETIME_IN_RANGE', 
  42      'IS_DATETIME', 
  43      'IS_DECIMAL_IN_RANGE', 
  44      'IS_EMAIL', 
  45      'IS_EMPTY_OR', 
  46      'IS_EXPR', 
  47      'IS_FLOAT_IN_RANGE', 
  48      'IS_IMAGE', 
  49      'IS_IN_DB', 
  50      'IS_IN_SET', 
  51      'IS_INT_IN_RANGE', 
  52      'IS_IPV4', 
  53      'IS_IPV6', 
  54      'IS_IPADDRESS', 
  55      'IS_LENGTH', 
  56      'IS_LIST_OF', 
  57      'IS_LOWER', 
  58      'IS_MATCH', 
  59      'IS_EQUAL_TO', 
  60      'IS_NOT_EMPTY', 
  61      'IS_NOT_IN_DB', 
  62      'IS_NULL_OR', 
  63      'IS_SLUG', 
  64      'IS_STRONG', 
  65      'IS_TIME', 
  66      'IS_UPLOAD_FILENAME', 
  67      'IS_UPPER', 
  68      'IS_URL', 
  69      'IS_JSON', 
  70  ] 
  71   
  72  try: 
  73      from globals import current 
  74      have_current = True 
  75  except ImportError: 
  76      have_current = False 
  80      if text is None: 
  81          return None 
  82      elif isinstance(text, (str, unicode)) and have_current: 
  83          if hasattr(current, 'T'): 
  84              return str(current.T(text)) 
  85      return str(text) 
   86   
  89      return (str(x[1]).upper() > str(y[1]).upper() and 1) or -1 
   90   
  93      """ 
  94      Root for all validators, mainly for documentation purposes. 
  95   
  96      Validators are classes used to validate input fields (including forms 
  97      generated from database tables). 
  98   
  99      Here is an example of using a validator with a FORM:: 
 100   
 101          INPUT(_name='a', requires=IS_INT_IN_RANGE(0, 10)) 
 102   
 103      Here is an example of how to require a validator for a table field:: 
 104   
 105          db.define_table('person', SQLField('name')) 
 106          db.person.name.requires=IS_NOT_EMPTY() 
 107   
 108      Validators are always assigned using the requires attribute of a field. A 
 109      field can have a single validator or multiple validators. Multiple 
 110      validators are made part of a list:: 
 111   
 112          db.person.name.requires=[IS_NOT_EMPTY(), IS_NOT_IN_DB(db, 'person.id')] 
 113   
 114      Validators are called by the function accepts on a FORM or other HTML 
 115      helper object that contains a form. They are always called in the order in 
 116      which they are listed. 
 117   
 118      Built-in validators have constructors that take the optional argument error 
 119      message which allows you to change the default error message. 
 120      Here is an example of a validator on a database table:: 
 121   
 122          db.person.name.requires=IS_NOT_EMPTY(error_message=T('fill this')) 
 123   
 124      where we have used the translation operator T to allow for 
 125      internationalization. 
 126   
 127      Notice that default error messages are not translated. 
 128      """ 
 129   
 136   
 138          raise NotImplementedError 
 139          return (value, None) 
   140   
 143      """ 
 144      example:: 
 145   
 146          INPUT(_type='text', _name='name', requires=IS_MATCH('.+')) 
 147   
 148      the argument of IS_MATCH is a regular expression:: 
 149   
 150          >>> IS_MATCH('.+')('hello') 
 151          ('hello', None) 
 152   
 153          >>> IS_MATCH('hell')('hello') 
 154          ('hello', None) 
 155   
 156          >>> IS_MATCH('hell.*', strict=False)('hello') 
 157          ('hello', None) 
 158   
 159          >>> IS_MATCH('hello')('shello') 
 160          ('shello', 'invalid expression') 
 161   
 162          >>> IS_MATCH('hello', search=True)('shello') 
 163          ('shello', None) 
 164   
 165          >>> IS_MATCH('hello', search=True, strict=False)('shellox') 
 166          ('shellox', None) 
 167   
 168          >>> IS_MATCH('.*hello.*', search=True, strict=False)('shellox') 
 169          ('shellox', None) 
 170   
 171          >>> IS_MATCH('.+')('') 
 172          ('', 'invalid expression') 
 173      """ 
 174   
 175 -    def __init__(self, expression, error_message='invalid expression', 
 176                   strict=False, search=False, extract=False, 
 177                   unicode=False): 
  178          if strict or not search: 
 179              if not expression.startswith('^'): 
 180                  expression = '^(%s)' % expression 
 181          if strict: 
 182              if not expression.endswith('$'): 
 183                  expression = '(%s)$' % expression 
 184          if unicode: 
 185              if not isinstance(expression,unicode): 
 186                  expression = expression.decode('utf8') 
 187              self.regex = re.compile(expression,re.UNICODE) 
 188          else: 
 189              self.regex = re.compile(expression) 
 190          self.error_message = error_message 
 191          self.extract = extract 
 192          self.unicode = unicode 
  193   
 195          if self.unicode and not isinstance(value,unicode): 
 196              match = self.regex.search(str(value).decode('utf8')) 
 197          else: 
 198              match = self.regex.search(str(value)) 
 199          if match is not None: 
 200              return (self.extract and match.group() or value, None) 
 201          return (value, translate(self.error_message)) 
   202   
 205      """ 
 206      example:: 
 207   
 208          INPUT(_type='text', _name='password') 
 209          INPUT(_type='text', _name='password2', 
 210                requires=IS_EQUAL_TO(request.vars.password)) 
 211   
 212      the argument of IS_EQUAL_TO is a string 
 213   
 214          >>> IS_EQUAL_TO('aaa')('aaa') 
 215          ('aaa', None) 
 216   
 217          >>> IS_EQUAL_TO('aaa')('aab') 
 218          ('aab', 'no match') 
 219      """ 
 220   
 221 -    def __init__(self, expression, error_message='no match'): 
  222          self.expression = expression 
 223          self.error_message = error_message 
  224   
 226          if value == self.expression: 
 227              return (value, None) 
 228          return (value, translate(self.error_message)) 
   229   
 232      """ 
 233      example:: 
 234   
 235          INPUT(_type='text', _name='name', 
 236              requires=IS_EXPR('5 < int(value) < 10')) 
 237   
 238      the argument of IS_EXPR must be python condition:: 
 239   
 240          >>> IS_EXPR('int(value) < 2')('1') 
 241          ('1', None) 
 242   
 243          >>> IS_EXPR('int(value) < 2')('2') 
 244          ('2', 'invalid expression') 
 245      """ 
 246   
 247 -    def __init__(self, expression, error_message='invalid expression', environment=None): 
  248          self.expression = expression 
 249          self.error_message = error_message 
 250          self.environment = environment or {} 
  251   
 253          if callable(self.expression): 
 254              return (value, self.expression(value)) 
 255           
 256          self.environment.update(value=value) 
 257          exec '__ret__=' + self.expression in self.environment 
 258          if self.environment['__ret__']: 
 259              return (value, None) 
 260          return (value, translate(self.error_message)) 
   261   
 264      """ 
 265      Checks if length of field's value fits between given boundaries. Works 
 266      for both text and file inputs. 
 267   
 268      Arguments: 
 269   
 270      maxsize: maximum allowed length / size 
 271      minsize: minimum allowed length / size 
 272   
 273      Examples:: 
 274   
 275          #Check if text string is shorter than 33 characters: 
 276          INPUT(_type='text', _name='name', requires=IS_LENGTH(32)) 
 277   
 278          #Check if password string is longer than 5 characters: 
 279          INPUT(_type='password', _name='name', requires=IS_LENGTH(minsize=6)) 
 280   
 281          #Check if uploaded file has size between 1KB and 1MB: 
 282          INPUT(_type='file', _name='name', requires=IS_LENGTH(1048576, 1024)) 
 283   
 284          >>> IS_LENGTH()('') 
 285          ('', None) 
 286          >>> IS_LENGTH()('1234567890') 
 287          ('1234567890', None) 
 288          >>> IS_LENGTH(maxsize=5, minsize=0)('1234567890')  # too long 
 289          ('1234567890', 'enter from 0 to 5 characters') 
 290          >>> IS_LENGTH(maxsize=50, minsize=20)('1234567890')  # too short 
 291          ('1234567890', 'enter from 20 to 50 characters') 
 292      """ 
 293   
 294 -    def __init__(self, maxsize=255, minsize=0, 
 295                   error_message='enter from %(min)g to %(max)g characters'): 
  296          self.maxsize = maxsize 
 297          self.minsize = minsize 
 298          self.error_message = error_message 
  299   
 301          if value is None: 
 302              length = 0 
 303              if self.minsize <= length <= self.maxsize: 
 304                  return (value, None) 
 305          elif isinstance(value, cgi.FieldStorage): 
 306              if value.file: 
 307                  value.file.seek(0, os.SEEK_END) 
 308                  length = value.file.tell() 
 309                  value.file.seek(0, os.SEEK_SET) 
 310              elif hasattr(value, 'value'): 
 311                  val = value.value 
 312                  if val: 
 313                      length = len(val) 
 314                  else: 
 315                      length = 0 
 316              if self.minsize <= length <= self.maxsize: 
 317                  return (value, None) 
 318          elif isinstance(value, str): 
 319              try: 
 320                  lvalue = len(value.decode('utf8')) 
 321              except: 
 322                  lvalue = len(value) 
 323              if self.minsize <= lvalue <= self.maxsize: 
 324                  return (value, None) 
 325          elif isinstance(value, unicode): 
 326              if self.minsize <= len(value) <= self.maxsize: 
 327                  return (value.encode('utf8'), None) 
 328          elif isinstance(value, (tuple, list)): 
 329              if self.minsize <= len(value) <= self.maxsize: 
 330                  return (value, None) 
 331          elif self.minsize <= len(str(value)) <= self.maxsize: 
 332              return (str(value), None) 
 333          return (value, translate(self.error_message) 
 334                  % dict(min=self.minsize, max=self.maxsize)) 
   335   
 337      """ 
 338      example:: 
 339          INPUT(_type='text', _name='name', 
 340              requires=IS_JSON(error_message="This is not a valid json input") 
 341   
 342          >>> IS_JSON()('{"a": 100}') 
 343          ({u'a': 100}, None) 
 344   
 345          >>> IS_JSON()('spam1234') 
 346          ('spam1234', 'invalid json') 
 347      """ 
 348   
 349 -    def __init__(self, error_message='invalid json'): 
  350          self.error_message = error_message 
  351   
 353          if value is None: 
 354              return None 
 355          try: 
 356              return (simplejson.loads(value), None) 
 357          except JSONErrors: 
 358              return (value, translate(self.error_message)) 
  359   
  364   
 367      """ 
 368      example:: 
 369   
 370          INPUT(_type='text', _name='name', 
 371                requires=IS_IN_SET(['max', 'john'],zero='')) 
 372   
 373      the argument of IS_IN_SET must be a list or set 
 374   
 375          >>> IS_IN_SET(['max', 'john'])('max') 
 376          ('max', None) 
 377          >>> IS_IN_SET(['max', 'john'])('massimo') 
 378          ('massimo', 'value not allowed') 
 379          >>> IS_IN_SET(['max', 'john'], multiple=True)(('max', 'john')) 
 380          (('max', 'john'), None) 
 381          >>> IS_IN_SET(['max', 'john'], multiple=True)(('bill', 'john')) 
 382          (('bill', 'john'), 'value not allowed') 
 383          >>> IS_IN_SET(('id1','id2'), ['first label','second label'])('id1') # Traditional way 
 384          ('id1', None) 
 385          >>> IS_IN_SET({'id1':'first label', 'id2':'second label'})('id1') 
 386          ('id1', None) 
 387          >>> import itertools 
 388          >>> IS_IN_SET(itertools.chain(['1','3','5'],['2','4','6']))('1') 
 389          ('1', None) 
 390          >>> IS_IN_SET([('id1','first label'), ('id2','second label')])('id1') # Redundant way 
 391          ('id1', None) 
 392      """ 
 393   
 394 -    def __init__( 
 395          self, 
 396          theset, 
 397          labels=None, 
 398          error_message='value not allowed', 
 399          multiple=False, 
 400          zero='', 
 401          sort=False, 
 402      ): 
  403          self.multiple = multiple 
 404          if isinstance(theset, dict): 
 405              self.theset = [str(item) for item in theset] 
 406              self.labels = theset.values() 
 407          elif theset and isinstance(theset, (tuple, list)) \ 
 408                  and isinstance(theset[0], (tuple, list)) and len(theset[0]) == 2: 
 409              self.theset = [str(item) for item, label in theset] 
 410              self.labels = [str(label) for item, label in theset] 
 411          else: 
 412              self.theset = [str(item) for item in theset] 
 413              self.labels = labels 
 414          self.error_message = error_message 
 415          self.zero = zero 
 416          self.sort = sort 
  417   
 419          if not self.labels: 
 420              items = [(k, k) for (i, k) in enumerate(self.theset)] 
 421          else: 
 422              items = [(k, self.labels[i]) for (i, k) in enumerate(self.theset)] 
 423          if self.sort: 
 424              items.sort(options_sorter) 
 425          if zero and not self.zero is None and not self.multiple: 
 426              items.insert(0, ('', self.zero)) 
 427          return items 
  428   
 430          if self.multiple: 
 431               
 432              if not value: 
 433                  values = [] 
 434              elif isinstance(value, (tuple, list)): 
 435                  values = value 
 436              else: 
 437                  values = [value] 
 438          else: 
 439              values = [value] 
 440          thestrset = [str(x) for x in self.theset] 
 441          failures = [x for x in values if not str(x) in thestrset] 
 442          if failures and self.theset: 
 443              if self.multiple and (value is None or value == ''): 
 444                  return ([], None) 
 445              return (value, translate(self.error_message)) 
 446          if self.multiple: 
 447              if isinstance(self.multiple, (tuple, list)) and \ 
 448                      not self.multiple[0] <= len(values) < self.multiple[1]: 
 449                  return (values, translate(self.error_message)) 
 450              return (values, None) 
 451          return (value, None) 
   452   
 453   
 454  regex1 = re.compile('\w+\.\w+') 
 455  regex2 = re.compile('%\((?P<name>[^\)]+)\)s') 
 459      """ 
 460      example:: 
 461   
 462          INPUT(_type='text', _name='name', 
 463                requires=IS_IN_DB(db, db.mytable.myfield, zero='')) 
 464   
 465      used for reference fields, rendered as a dropbox 
 466      """ 
 467   
 468 -    def __init__( 
 469          self, 
 470          dbset, 
 471          field, 
 472          label=None, 
 473          error_message='value not in database', 
 474          orderby=None, 
 475          groupby=None, 
 476          distinct=None, 
 477          cache=None, 
 478          multiple=False, 
 479          zero='', 
 480          sort=False, 
 481          _and=None, 
 482      ): 
  483          from dal import Table 
 484          if isinstance(field, Table): 
 485              field = field._id 
 486   
 487          if hasattr(dbset, 'define_table'): 
 488              self.dbset = dbset() 
 489          else: 
 490              self.dbset = dbset 
 491          (ktable, kfield) = str(field).split('.') 
 492          if not label: 
 493              label = '%%(%s)s' % kfield 
 494          if isinstance(label, str): 
 495              if regex1.match(str(label)): 
 496                  label = '%%(%s)s' % str(label).split('.')[-1] 
 497              ks = regex2.findall(label) 
 498              if not kfield in ks: 
 499                  ks += [kfield] 
 500              fields = ks 
 501          else: 
 502              ks = [kfield] 
 503              fields = 'all' 
 504          self.fields = fields 
 505          self.label = label 
 506          self.ktable = ktable 
 507          self.kfield = kfield 
 508          self.ks = ks 
 509          self.error_message = error_message 
 510          self.theset = None 
 511          self.orderby = orderby 
 512          self.groupby = groupby 
 513          self.distinct = distinct 
 514          self.cache = cache 
 515          self.multiple = multiple 
 516          self.zero = zero 
 517          self.sort = sort 
 518          self._and = _and 
  519   
 521          if self._and: 
 522              self._and.record_id = id 
  523   
 525          table = self.dbset.db[self.ktable] 
 526          if self.fields == 'all': 
 527              fields = [f for f in table] 
 528          else: 
 529              fields = [table[k] for k in self.fields] 
 530          ignore = (FieldVirtual,FieldMethod) 
 531          fields = filter(lambda f:not isinstance(f,ignore), fields) 
 532          if self.dbset.db._dbname != 'gae': 
 533              orderby = self.orderby or reduce(lambda a, b: a | b, fields) 
 534              groupby = self.groupby 
 535              distinct = self.distinct 
 536              dd = dict(orderby=orderby, groupby=groupby, 
 537                        distinct=distinct, cache=self.cache, 
 538                        cacheable=True) 
 539              records = self.dbset(table).select(*fields, **dd) 
 540          else: 
 541              orderby = self.orderby or \ 
 542                  reduce(lambda a, b: a | b, ( 
 543                      f for f in fields if not f.name == 'id')) 
 544              dd = dict(orderby=orderby, cache=self.cache, cacheable=True) 
 545              records = self.dbset(table).select(table.ALL, **dd) 
 546          self.theset = [str(r[self.kfield]) for r in records] 
 547          if isinstance(self.label, str): 
 548              self.labels = [self.label % r for r in records] 
 549          else: 
 550              self.labels = [self.label(r) for r in records] 
  551   
 560   
 562          table = self.dbset.db[self.ktable] 
 563          field = table[self.kfield] 
 564          if self.multiple: 
 565              if self._and: 
 566                  raise NotImplementedError 
 567              if isinstance(value, list): 
 568                  values = value 
 569              elif value: 
 570                  values = [value] 
 571              else: 
 572                  values = [] 
 573              if isinstance(self.multiple, (tuple, list)) and \ 
 574                      not self.multiple[0] <= len(values) < self.multiple[1]: 
 575                  return (values, translate(self.error_message)) 
 576              if self.theset: 
 577                  if not [v for v in values if not v in self.theset]: 
 578                      return (values, None) 
 579              else: 
 580                  from dal import GoogleDatastoreAdapter 
 581   
 582                  def count(values, s=self.dbset, f=field): 
 583                      return s(f.belongs(map(int, values))).count() 
  584                  if isinstance(self.dbset.db._adapter, GoogleDatastoreAdapter): 
 585                      range_ids = range(0, len(values), 30) 
 586                      total = sum(count(values[i:i + 30]) for i in range_ids) 
 587                      if total == len(values): 
 588                          return (values, None) 
 589                  elif count(values) == len(values): 
 590                      return (values, None) 
 591          elif self.theset: 
 592              if str(value) in self.theset: 
 593                  if self._and: 
 594                      return self._and(value) 
 595                  else: 
 596                      return (value, None) 
 597          else: 
 598              if self.dbset(field == value).count(): 
 599                  if self._and: 
 600                      return self._and(value) 
 601                  else: 
 602                      return (value, None) 
 603          return (value, translate(self.error_message)) 
  604   
 607      """ 
 608      example:: 
 609   
 610          INPUT(_type='text', _name='name', requires=IS_NOT_IN_DB(db, db.table)) 
 611   
 612      makes the field unique 
 613      """ 
 614   
 615 -    def __init__( 
 616          self, 
 617          dbset, 
 618          field, 
 619          error_message='value already in database or empty', 
 620          allowed_override=[], 
 621          ignore_common_filters=False, 
 622      ): 
  623   
 624          from dal import Table 
 625          if isinstance(field, Table): 
 626              field = field._id 
 627   
 628          if hasattr(dbset, 'define_table'): 
 629              self.dbset = dbset() 
 630          else: 
 631              self.dbset = dbset 
 632          self.field = field 
 633          self.error_message = error_message 
 634          self.record_id = 0 
 635          self.allowed_override = allowed_override 
 636          self.ignore_common_filters = ignore_common_filters 
  637   
 640   
 642          if isinstance(value,unicode): 
 643              value = value.encode('utf8') 
 644          else: 
 645              value = str(value) 
 646          if not value.strip(): 
 647              return (value, translate(self.error_message)) 
 648          if value in self.allowed_override: 
 649              return (value, None) 
 650          (tablename, fieldname) = str(self.field).split('.') 
 651          table = self.dbset.db[tablename] 
 652          field = table[fieldname] 
 653          subset = self.dbset(field == value, 
 654                              ignore_common_filters=self.ignore_common_filters) 
 655          id = self.record_id 
 656          if isinstance(id, dict): 
 657              fields = [table[f] for f in id] 
 658              row = subset.select(*fields, **dict(limitby=(0, 1), orderby_on_limitby=False)).first() 
 659              if row and any(str(row[f]) != str(id[f]) for f in id): 
 660                  return (value, translate(self.error_message)) 
 661          else: 
 662              row = subset.select(table._id, field, limitby=(0, 1), orderby_on_limitby=False).first() 
 663              if row and str(row.id) != str(id): 
 664                  return (value, translate(self.error_message)) 
 665          return (value, None) 
   666   
 669      """ 
 670      Determine that the argument is (or can be represented as) an int, 
 671      and that it falls within the specified range. The range is interpreted 
 672      in the Pythonic way, so the test is: min <= value < max. 
 673   
 674      The minimum and maximum limits can be None, meaning no lower or upper limit, 
 675      respectively. 
 676   
 677      example:: 
 678   
 679          INPUT(_type='text', _name='name', requires=IS_INT_IN_RANGE(0, 10)) 
 680   
 681          >>> IS_INT_IN_RANGE(1,5)('4') 
 682          (4, None) 
 683          >>> IS_INT_IN_RANGE(1,5)(4) 
 684          (4, None) 
 685          >>> IS_INT_IN_RANGE(1,5)(1) 
 686          (1, None) 
 687          >>> IS_INT_IN_RANGE(1,5)(5) 
 688          (5, 'enter an integer between 1 and 4') 
 689          >>> IS_INT_IN_RANGE(1,5)(5) 
 690          (5, 'enter an integer between 1 and 4') 
 691          >>> IS_INT_IN_RANGE(1,5)(3.5) 
 692          (3, 'enter an integer between 1 and 4') 
 693          >>> IS_INT_IN_RANGE(None,5)('4') 
 694          (4, None) 
 695          >>> IS_INT_IN_RANGE(None,5)('6') 
 696          (6, 'enter an integer less than or equal to 4') 
 697          >>> IS_INT_IN_RANGE(1,None)('4') 
 698          (4, None) 
 699          >>> IS_INT_IN_RANGE(1,None)('0') 
 700          (0, 'enter an integer greater than or equal to 1') 
 701          >>> IS_INT_IN_RANGE()(6) 
 702          (6, None) 
 703          >>> IS_INT_IN_RANGE()('abc') 
 704          ('abc', 'enter an integer') 
 705      """ 
 706   
 707 -    def __init__( 
 708          self, 
 709          minimum=None, 
 710          maximum=None, 
 711          error_message=None, 
 712      ): 
  713          self.minimum = self.maximum = None 
 714          if minimum is None: 
 715              if maximum is None: 
 716                  self.error_message = translate( 
 717                      error_message or 'enter an integer') 
 718              else: 
 719                  self.maximum = int(maximum) 
 720                  if error_message is None: 
 721                      error_message = \ 
 722                          'enter an integer less than or equal to %(max)g' 
 723                  self.error_message = translate( 
 724                      error_message) % dict(max=self.maximum - 1) 
 725          elif maximum is None: 
 726              self.minimum = int(minimum) 
 727              if error_message is None: 
 728                  error_message = \ 
 729                      'enter an integer greater than or equal to %(min)g' 
 730              self.error_message = translate( 
 731                  error_message) % dict(min=self.minimum) 
 732          else: 
 733              self.minimum = int(minimum) 
 734              self.maximum = int(maximum) 
 735              if error_message is None: 
 736                  error_message = 'enter an integer between %(min)g and %(max)g' 
 737              self.error_message = translate(error_message) \ 
 738                  % dict(min=self.minimum, max=self.maximum - 1) 
  739   
 741          try: 
 742              fvalue = float(value) 
 743              value = int(value) 
 744              if value != fvalue: 
 745                  return (value, self.error_message) 
 746              if self.minimum is None: 
 747                  if self.maximum is None or value < self.maximum: 
 748                      return (value, None) 
 749              elif self.maximum is None: 
 750                  if value >= self.minimum: 
 751                      return (value, None) 
 752              elif self.minimum <= value < self.maximum: 
 753                      return (value, None) 
 754          except ValueError: 
 755              pass 
 756          return (value, self.error_message) 
   757   
 760      s = str(number) 
 761      if not '.' in s: 
 762          s += '.00' 
 763      else: 
 764          s += '0' * (2 - len(s.split('.')[1])) 
 765      return s 
  766   
 769      """ 
 770      Determine that the argument is (or can be represented as) a float, 
 771      and that it falls within the specified inclusive range. 
 772      The comparison is made with native arithmetic. 
 773   
 774      The minimum and maximum limits can be None, meaning no lower or upper limit, 
 775      respectively. 
 776   
 777      example:: 
 778   
 779          INPUT(_type='text', _name='name', requires=IS_FLOAT_IN_RANGE(0, 10)) 
 780   
 781          >>> IS_FLOAT_IN_RANGE(1,5)('4') 
 782          (4.0, None) 
 783          >>> IS_FLOAT_IN_RANGE(1,5)(4) 
 784          (4.0, None) 
 785          >>> IS_FLOAT_IN_RANGE(1,5)(1) 
 786          (1.0, None) 
 787          >>> IS_FLOAT_IN_RANGE(1,5)(5.25) 
 788          (5.25, 'enter a number between 1 and 5') 
 789          >>> IS_FLOAT_IN_RANGE(1,5)(6.0) 
 790          (6.0, 'enter a number between 1 and 5') 
 791          >>> IS_FLOAT_IN_RANGE(1,5)(3.5) 
 792          (3.5, None) 
 793          >>> IS_FLOAT_IN_RANGE(1,None)(3.5) 
 794          (3.5, None) 
 795          >>> IS_FLOAT_IN_RANGE(None,5)(3.5) 
 796          (3.5, None) 
 797          >>> IS_FLOAT_IN_RANGE(1,None)(0.5) 
 798          (0.5, 'enter a number greater than or equal to 1') 
 799          >>> IS_FLOAT_IN_RANGE(None,5)(6.5) 
 800          (6.5, 'enter a number less than or equal to 5') 
 801          >>> IS_FLOAT_IN_RANGE()(6.5) 
 802          (6.5, None) 
 803          >>> IS_FLOAT_IN_RANGE()('abc') 
 804          ('abc', 'enter a number') 
 805      """ 
 806   
 807 -    def __init__( 
 808          self, 
 809          minimum=None, 
 810          maximum=None, 
 811          error_message=None, 
 812          dot='.' 
 813      ): 
  814          self.minimum = self.maximum = None 
 815          self.dot = dot 
 816          if minimum is None: 
 817              if maximum is None: 
 818                  if error_message is None: 
 819                      error_message = 'enter a number' 
 820              else: 
 821                  self.maximum = float(maximum) 
 822                  if error_message is None: 
 823                      error_message = 'enter a number less than or equal to %(max)g' 
 824          elif maximum is None: 
 825              self.minimum = float(minimum) 
 826              if error_message is None: 
 827                  error_message = 'enter a number greater than or equal to %(min)g' 
 828          else: 
 829              self.minimum = float(minimum) 
 830              self.maximum = float(maximum) 
 831              if error_message is None: 
 832                  error_message = 'enter a number between %(min)g and %(max)g' 
 833          self.error_message = translate(error_message) \ 
 834              % dict(min=self.minimum, max=self.maximum) 
  835   
 837          try: 
 838              if self.dot == '.': 
 839                  fvalue = float(value) 
 840              else: 
 841                  fvalue = float(str(value).replace(self.dot, '.')) 
 842              if self.minimum is None: 
 843                  if self.maximum is None or fvalue <= self.maximum: 
 844                      return (fvalue, None) 
 845              elif self.maximum is None: 
 846                  if fvalue >= self.minimum: 
 847                      return (fvalue, None) 
 848              elif self.minimum <= fvalue <= self.maximum: 
 849                      return (fvalue, None) 
 850          except (ValueError, TypeError): 
 851              pass 
 852          return (value, self.error_message) 
  853   
  858   
 861      """ 
 862      Determine that the argument is (or can be represented as) a Python Decimal, 
 863      and that it falls within the specified inclusive range. 
 864      The comparison is made with Python Decimal arithmetic. 
 865   
 866      The minimum and maximum limits can be None, meaning no lower or upper limit, 
 867      respectively. 
 868   
 869      example:: 
 870   
 871          INPUT(_type='text', _name='name', requires=IS_DECIMAL_IN_RANGE(0, 10)) 
 872   
 873          >>> IS_DECIMAL_IN_RANGE(1,5)('4') 
 874          (Decimal('4'), None) 
 875          >>> IS_DECIMAL_IN_RANGE(1,5)(4) 
 876          (Decimal('4'), None) 
 877          >>> IS_DECIMAL_IN_RANGE(1,5)(1) 
 878          (Decimal('1'), None) 
 879          >>> IS_DECIMAL_IN_RANGE(1,5)(5.25) 
 880          (5.25, 'enter a number between 1 and 5') 
 881          >>> IS_DECIMAL_IN_RANGE(5.25,6)(5.25) 
 882          (Decimal('5.25'), None) 
 883          >>> IS_DECIMAL_IN_RANGE(5.25,6)('5.25') 
 884          (Decimal('5.25'), None) 
 885          >>> IS_DECIMAL_IN_RANGE(1,5)(6.0) 
 886          (6.0, 'enter a number between 1 and 5') 
 887          >>> IS_DECIMAL_IN_RANGE(1,5)(3.5) 
 888          (Decimal('3.5'), None) 
 889          >>> IS_DECIMAL_IN_RANGE(1.5,5.5)(3.5) 
 890          (Decimal('3.5'), None) 
 891          >>> IS_DECIMAL_IN_RANGE(1.5,5.5)(6.5) 
 892          (6.5, 'enter a number between 1.5 and 5.5') 
 893          >>> IS_DECIMAL_IN_RANGE(1.5,None)(6.5) 
 894          (Decimal('6.5'), None) 
 895          >>> IS_DECIMAL_IN_RANGE(1.5,None)(0.5) 
 896          (0.5, 'enter a number greater than or equal to 1.5') 
 897          >>> IS_DECIMAL_IN_RANGE(None,5.5)(4.5) 
 898          (Decimal('4.5'), None) 
 899          >>> IS_DECIMAL_IN_RANGE(None,5.5)(6.5) 
 900          (6.5, 'enter a number less than or equal to 5.5') 
 901          >>> IS_DECIMAL_IN_RANGE()(6.5) 
 902          (Decimal('6.5'), None) 
 903          >>> IS_DECIMAL_IN_RANGE(0,99)(123.123) 
 904          (123.123, 'enter a number between 0 and 99') 
 905          >>> IS_DECIMAL_IN_RANGE(0,99)('123.123') 
 906          ('123.123', 'enter a number between 0 and 99') 
 907          >>> IS_DECIMAL_IN_RANGE(0,99)('12.34') 
 908          (Decimal('12.34'), None) 
 909          >>> IS_DECIMAL_IN_RANGE()('abc') 
 910          ('abc', 'enter a decimal number') 
 911      """ 
 912   
 913 -    def __init__( 
 914          self, 
 915          minimum=None, 
 916          maximum=None, 
 917          error_message=None, 
 918          dot='.' 
 919      ): 
  920          self.minimum = self.maximum = None 
 921          self.dot = dot 
 922          if minimum is None: 
 923              if maximum is None: 
 924                  if error_message is None: 
 925                      error_message = 'enter a decimal number' 
 926              else: 
 927                  self.maximum = decimal.Decimal(str(maximum)) 
 928                  if error_message is None: 
 929                      error_message = 'enter a number less than or equal to %(max)g' 
 930          elif maximum is None: 
 931              self.minimum = decimal.Decimal(str(minimum)) 
 932              if error_message is None: 
 933                  error_message = 'enter a number greater than or equal to %(min)g' 
 934          else: 
 935              self.minimum = decimal.Decimal(str(minimum)) 
 936              self.maximum = decimal.Decimal(str(maximum)) 
 937              if error_message is None: 
 938                  error_message = 'enter a number between %(min)g and %(max)g' 
 939          self.error_message = translate(error_message) \ 
 940              % dict(min=self.minimum, max=self.maximum) 
  941   
 943          try: 
 944              if isinstance(value, decimal.Decimal): 
 945                  v = value 
 946              else: 
 947                  v = decimal.Decimal(str(value).replace(self.dot, '.')) 
 948              if self.minimum is None: 
 949                  if self.maximum is None or v <= self.maximum: 
 950                      return (v, None) 
 951              elif self.maximum is None: 
 952                  if v >= self.minimum: 
 953                      return (v, None) 
 954              elif self.minimum <= v <= self.maximum: 
 955                      return (v, None) 
 956          except (ValueError, TypeError, decimal.InvalidOperation): 
 957              pass 
 958          return (value, self.error_message) 
  959   
  964   
 965   
 966 -def is_empty(value, empty_regex=None): 
  967      "test empty field" 
 968      if isinstance(value, (str, unicode)): 
 969          value = value.strip() 
 970          if empty_regex is not None and empty_regex.match(value): 
 971              value = '' 
 972      if value is None or value == '' or value == []: 
 973          return (value, True) 
 974      return (value, False) 
  975   
 978      """ 
 979      example:: 
 980   
 981          INPUT(_type='text', _name='name', requires=IS_NOT_EMPTY()) 
 982   
 983          >>> IS_NOT_EMPTY()(1) 
 984          (1, None) 
 985          >>> IS_NOT_EMPTY()(0) 
 986          (0, None) 
 987          >>> IS_NOT_EMPTY()('x') 
 988          ('x', None) 
 989          >>> IS_NOT_EMPTY()(' x ') 
 990          ('x', None) 
 991          >>> IS_NOT_EMPTY()(None) 
 992          (None, 'enter a value') 
 993          >>> IS_NOT_EMPTY()('') 
 994          ('', 'enter a value') 
 995          >>> IS_NOT_EMPTY()('  ') 
 996          ('', 'enter a value') 
 997          >>> IS_NOT_EMPTY()(' \\n\\t') 
 998          ('', 'enter a value') 
 999          >>> IS_NOT_EMPTY()([]) 
1000          ([], 'enter a value') 
1001          >>> IS_NOT_EMPTY(empty_regex='def')('def') 
1002          ('', 'enter a value') 
1003          >>> IS_NOT_EMPTY(empty_regex='de[fg]')('deg') 
1004          ('', 'enter a value') 
1005          >>> IS_NOT_EMPTY(empty_regex='def')('abc') 
1006          ('abc', None) 
1007      """ 
1008   
1009 -    def __init__(self, error_message='enter a value', empty_regex=None): 
 1010          self.error_message = error_message 
1011          if empty_regex is not None: 
1012              self.empty_regex = re.compile(empty_regex) 
1013          else: 
1014              self.empty_regex = None 
 1015   
1017          value, empty = is_empty(value, empty_regex=self.empty_regex) 
1018          if empty: 
1019              return (value, translate(self.error_message)) 
1020          return (value, None) 
  1021   
1024      """ 
1025      example:: 
1026   
1027          INPUT(_type='text', _name='name', requires=IS_ALPHANUMERIC()) 
1028   
1029          >>> IS_ALPHANUMERIC()('1') 
1030          ('1', None) 
1031          >>> IS_ALPHANUMERIC()('') 
1032          ('', None) 
1033          >>> IS_ALPHANUMERIC()('A_a') 
1034          ('A_a', None) 
1035          >>> IS_ALPHANUMERIC()('!') 
1036          ('!', 'enter only letters, numbers, and underscore') 
1037      """ 
1038   
1039 -    def __init__(self, error_message='enter only letters, numbers, and underscore'): 
  1041   
1044      """ 
1045      Checks if field's value is a valid email address. Can be set to disallow 
1046      or force addresses from certain domain(s). 
1047   
1048      Email regex adapted from 
1049      http://haacked.com/archive/2007/08/21/i-knew-how-to-validate-an-email-address-until-i.aspx, 
1050      generally following the RFCs, except that we disallow quoted strings 
1051      and permit underscores and leading numerics in subdomain labels 
1052   
1053      Arguments: 
1054   
1055      - banned: regex text for disallowed address domains 
1056      - forced: regex text for required address domains 
1057   
1058      Both arguments can also be custom objects with a match(value) method. 
1059   
1060      Examples:: 
1061   
1062          #Check for valid email address: 
1063          INPUT(_type='text', _name='name', 
1064              requires=IS_EMAIL()) 
1065   
1066          #Check for valid email address that can't be from a .com domain: 
1067          INPUT(_type='text', _name='name', 
1068              requires=IS_EMAIL(banned='^.*\.com(|\..*)$')) 
1069   
1070          #Check for valid email address that must be from a .edu domain: 
1071          INPUT(_type='text', _name='name', 
1072              requires=IS_EMAIL(forced='^.*\.edu(|\..*)$')) 
1073   
1074          >>> IS_EMAIL()('a@b.com') 
1075          ('a@b.com', None) 
1076          >>> IS_EMAIL()('abc@def.com') 
1077          ('abc@def.com', None) 
1078          >>> IS_EMAIL()('abc@3def.com') 
1079          ('abc@3def.com', None) 
1080          >>> IS_EMAIL()('abc@def.us') 
1081          ('abc@def.us', None) 
1082          >>> IS_EMAIL()('abc@d_-f.us') 
1083          ('abc@d_-f.us', None) 
1084          >>> IS_EMAIL()('@def.com')           # missing name 
1085          ('@def.com', 'enter a valid email address') 
1086          >>> IS_EMAIL()('"abc@def".com')      # quoted name 
1087          ('"abc@def".com', 'enter a valid email address') 
1088          >>> IS_EMAIL()('abc+def.com')        # no @ 
1089          ('abc+def.com', 'enter a valid email address') 
1090          >>> IS_EMAIL()('abc@def.x')          # one-char TLD 
1091          ('abc@def.x', 'enter a valid email address') 
1092          >>> IS_EMAIL()('abc@def.12')         # numeric TLD 
1093          ('abc@def.12', 'enter a valid email address') 
1094          >>> IS_EMAIL()('abc@def..com')       # double-dot in domain 
1095          ('abc@def..com', 'enter a valid email address') 
1096          >>> IS_EMAIL()('abc@.def.com')       # dot starts domain 
1097          ('abc@.def.com', 'enter a valid email address') 
1098          >>> IS_EMAIL()('abc@def.c_m')        # underscore in TLD 
1099          ('abc@def.c_m', 'enter a valid email address') 
1100          >>> IS_EMAIL()('NotAnEmail')         # missing @ 
1101          ('NotAnEmail', 'enter a valid email address') 
1102          >>> IS_EMAIL()('abc@NotAnEmail')     # missing TLD 
1103          ('abc@NotAnEmail', 'enter a valid email address') 
1104          >>> IS_EMAIL()('customer/department@example.com') 
1105          ('customer/department@example.com', None) 
1106          >>> IS_EMAIL()('$A12345@example.com') 
1107          ('$A12345@example.com', None) 
1108          >>> IS_EMAIL()('!def!xyz%abc@example.com') 
1109          ('!def!xyz%abc@example.com', None) 
1110          >>> IS_EMAIL()('_Yosemite.Sam@example.com') 
1111          ('_Yosemite.Sam@example.com', None) 
1112          >>> IS_EMAIL()('~@example.com') 
1113          ('~@example.com', None) 
1114          >>> IS_EMAIL()('.wooly@example.com')       # dot starts name 
1115          ('.wooly@example.com', 'enter a valid email address') 
1116          >>> IS_EMAIL()('wo..oly@example.com')      # adjacent dots in name 
1117          ('wo..oly@example.com', 'enter a valid email address') 
1118          >>> IS_EMAIL()('pootietang.@example.com')  # dot ends name 
1119          ('pootietang.@example.com', 'enter a valid email address') 
1120          >>> IS_EMAIL()('.@example.com')            # name is bare dot 
1121          ('.@example.com', 'enter a valid email address') 
1122          >>> IS_EMAIL()('Ima.Fool@example.com') 
1123          ('Ima.Fool@example.com', None) 
1124          >>> IS_EMAIL()('Ima Fool@example.com')     # space in name 
1125          ('Ima Fool@example.com', 'enter a valid email address') 
1126          >>> IS_EMAIL()('localguy@localhost')       # localhost as domain 
1127          ('localguy@localhost', None) 
1128   
1129      """ 
1130   
1131      regex = re.compile(''' 
1132          ^(?!\.)                            # name may not begin with a dot 
1133          ( 
1134            [-a-z0-9!\#$%&'*+/=?^_`{|}~]     # all legal characters except dot 
1135            | 
1136            (?<!\.)\.                        # single dots only 
1137          )+ 
1138          (?<!\.)                            # name may not end with a dot 
1139          @ 
1140          ( 
1141            localhost 
1142            | 
1143            ( 
1144              [a-z0-9] 
1145                  # [sub]domain begins with alphanumeric 
1146              ( 
1147                [-\w]*                         # alphanumeric, underscore, dot, hyphen 
1148                [a-z0-9]                       # ending alphanumeric 
1149              )? 
1150            \.                               # ending dot 
1151            )+ 
1152            [a-z]{2,}                        # TLD alpha-only 
1153         )$ 
1154      ''', re.VERBOSE | re.IGNORECASE) 
1155   
1156      regex_proposed_but_failed = re.compile('^([\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*[\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+@((((([a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,6})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)$', re.VERBOSE | re.IGNORECASE) 
1157   
1158 -    def __init__(self, 
1159                   banned=None, 
1160                   forced=None, 
1161                   error_message='enter a valid email address'): 
 1162          if isinstance(banned, str): 
1163              banned = re.compile(banned) 
1164          if isinstance(forced, str): 
1165              forced = re.compile(forced) 
1166          self.banned = banned 
1167          self.forced = forced 
1168          self.error_message = error_message 
 1169   
1171          match = self.regex.match(value) 
1172          if match: 
1173              domain = value.split('@')[1] 
1174              if (not self.banned or not self.banned.match(domain)) \ 
1175                      and (not self.forced or self.forced.match(domain)): 
1176                  return (value, None) 
1177          return (value, translate(self.error_message)) 
  1178   
1179   
1180   
1181   
1182   
1183  official_url_schemes = [ 
1184      'aaa', 
1185      'aaas', 
1186      'acap', 
1187      'cap', 
1188      'cid', 
1189      'crid', 
1190      'data', 
1191      'dav', 
1192      'dict', 
1193      'dns', 
1194      'fax', 
1195      'file', 
1196      'ftp', 
1197      'go', 
1198      'gopher', 
1199      'h323', 
1200      'http', 
1201      'https', 
1202      'icap', 
1203      'im', 
1204      'imap', 
1205      'info', 
1206      'ipp', 
1207      'iris', 
1208      'iris.beep', 
1209      'iris.xpc', 
1210      'iris.xpcs', 
1211      'iris.lws', 
1212      'ldap', 
1213      'mailto', 
1214      'mid', 
1215      'modem', 
1216      'msrp', 
1217      'msrps', 
1218      'mtqp', 
1219      'mupdate', 
1220      'news', 
1221      'nfs', 
1222      'nntp', 
1223      'opaquelocktoken', 
1224      'pop', 
1225      'pres', 
1226      'prospero', 
1227      'rtsp', 
1228      'service', 
1229      'shttp', 
1230      'sip', 
1231      'sips', 
1232      'snmp', 
1233      'soap.beep', 
1234      'soap.beeps', 
1235      'tag', 
1236      'tel', 
1237      'telnet', 
1238      'tftp', 
1239      'thismessage', 
1240      'tip', 
1241      'tv', 
1242      'urn', 
1243      'vemmi', 
1244      'wais', 
1245      'xmlrpc.beep', 
1246      'xmlrpc.beep', 
1247      'xmpp', 
1248      'z39.50r', 
1249      'z39.50s', 
1250  ] 
1251  unofficial_url_schemes = [ 
1252      'about', 
1253      'adiumxtra', 
1254      'aim', 
1255      'afp', 
1256      'aw', 
1257      'callto', 
1258      'chrome', 
1259      'cvs', 
1260      'ed2k', 
1261      'feed', 
1262      'fish', 
1263      'gg', 
1264      'gizmoproject', 
1265      'iax2', 
1266      'irc', 
1267      'ircs', 
1268      'itms', 
1269      'jar', 
1270      'javascript', 
1271      'keyparc', 
1272      'lastfm', 
1273      'ldaps', 
1274      'magnet', 
1275      'mms', 
1276      'msnim', 
1277      'mvn', 
1278      'notes', 
1279      'nsfw', 
1280      'psyc', 
1281      'paparazzi:http', 
1282      'rmi', 
1283      'rsync', 
1284      'secondlife', 
1285      'sgn', 
1286      'skype', 
1287      'ssh', 
1288      'sftp', 
1289      'smb', 
1290      'sms', 
1291      'soldat', 
1292      'steam', 
1293      'svn', 
1294      'teamspeak', 
1295      'unreal', 
1296      'ut2004', 
1297      'ventrilo', 
1298      'view-source', 
1299      'webcal', 
1300      'wyciwyg', 
1301      'xfire', 
1302      'xri', 
1303      'ymsgr', 
1304  ] 
1305  all_url_schemes = [None] + official_url_schemes + unofficial_url_schemes 
1306  http_schemes = [None, 'http', 'https'] 
1307   
1308   
1309   
1310   
1311   
1312   
1313   
1314   
1315   
1316   
1317   
1318  url_split_regex = \ 
1319      re.compile('^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?') 
1320   
1321   
1322   
1323   
1324  label_split_regex = re.compile(u'[\u002e\u3002\uff0e\uff61]') 
1328      ''' 
1329      Converts a unicode string into US-ASCII, using a simple conversion scheme. 
1330      Each unicode character that does not have a US-ASCII equivalent is 
1331      converted into a URL escaped form based on its hexadecimal value. 
1332      For example, the unicode character '\u4e86' will become the string '%4e%86' 
1333   
1334      :param string: unicode string, the unicode string to convert into an 
1335          escaped US-ASCII form 
1336      :returns: the US-ASCII escaped form of the inputted string 
1337      :rtype: string 
1338   
1339      @author: Jonathan Benn 
1340      ''' 
1341      returnValue = StringIO() 
1342   
1343      for character in string: 
1344          code = ord(character) 
1345          if code > 0x7F: 
1346              hexCode = hex(code) 
1347              returnValue.write('%' + hexCode[2:4] + '%' + hexCode[4:6]) 
1348          else: 
1349              returnValue.write(character) 
1350   
1351      return returnValue.getvalue() 
 1352   
1355      ''' 
1356      Follows the steps in RFC 3490, Section 4 to convert a unicode authority 
1357      string into its ASCII equivalent. 
1358      For example, u'www.Alliancefran\xe7aise.nu' will be converted into 
1359      'www.xn--alliancefranaise-npb.nu' 
1360   
1361      :param authority: unicode string, the URL authority component to convert, 
1362                        e.g. u'www.Alliancefran\xe7aise.nu' 
1363      :returns: the US-ASCII character equivalent to the inputed authority, 
1364               e.g. 'www.xn--alliancefranaise-npb.nu' 
1365      :rtype: string 
1366      :raises Exception: if the function is not able to convert the inputed 
1367          authority 
1368   
1369      @author: Jonathan Benn 
1370      ''' 
1371       
1372       
1373   
1374       
1375      labels = label_split_regex.split(authority) 
1376   
1377       
1378       
1379   
1380       
1381       
1382       
1383      asciiLabels = [] 
1384      try: 
1385          import encodings.idna 
1386          for label in labels: 
1387              if label: 
1388                  asciiLabels.append(encodings.idna.ToASCII(label)) 
1389              else: 
1390                    
1391                    
1392                    
1393                  asciiLabels.append('') 
1394      except: 
1395          asciiLabels = [str(label) for label in labels] 
1396       
1397      return str(reduce(lambda x, y: x + unichr(0x002E) + y, asciiLabels)) 
 1398   
1401      ''' 
1402      Converts the inputed unicode url into a US-ASCII equivalent. This function 
1403      goes a little beyond RFC 3490, which is limited in scope to the domain name 
1404      (authority) only. Here, the functionality is expanded to what was observed 
1405      on Wikipedia on 2009-Jan-22: 
1406   
1407         Component    Can Use Unicode? 
1408         ---------    ---------------- 
1409         scheme       No 
1410         authority    Yes 
1411         path         Yes 
1412         query        Yes 
1413         fragment     No 
1414   
1415      The authority component gets converted to punycode, but occurrences of 
1416      unicode in other components get converted into a pair of URI escapes (we 
1417      assume 4-byte unicode). E.g. the unicode character U+4E2D will be 
1418      converted into '%4E%2D'. Testing with Firefox v3.0.5 has shown that it can 
1419      understand this kind of URI encoding. 
1420   
1421      :param url: unicode string, the URL to convert from unicode into US-ASCII 
1422      :param prepend_scheme: string, a protocol scheme to prepend to the URL if 
1423          we're having trouble parsing it. 
1424          e.g. "http". Input None to disable this functionality 
1425      :returns: a US-ASCII equivalent of the inputed url 
1426      :rtype: string 
1427   
1428      @author: Jonathan Benn 
1429      ''' 
1430       
1431       
1432   
1433      groups = url_split_regex.match(url).groups() 
1434       
1435      if not groups[3]: 
1436           
1437          scheme_to_prepend = prepend_scheme or 'http' 
1438          groups = url_split_regex.match( 
1439              unicode(scheme_to_prepend) + u'://' + url).groups() 
1440       
1441      if not groups[3]: 
1442          raise Exception('No authority component found, ' + 
1443                          'could not decode unicode to US-ASCII') 
1444   
1445       
1446      scheme = groups[1] 
1447      authority = groups[3] 
1448      path = groups[4] or '' 
1449      query = groups[5] or '' 
1450      fragment = groups[7] or '' 
1451   
1452      if prepend_scheme: 
1453          scheme = str(scheme) + '://' 
1454      else: 
1455          scheme = '' 
1456      return scheme + unicode_to_ascii_authority(authority) +\ 
1457          escape_unicode(path) + escape_unicode(query) + str(fragment) 
 1458   
1461      """ 
1462      Rejects a URL string if any of the following is true: 
1463         * The string is empty or None 
1464         * The string uses characters that are not allowed in a URL 
1465         * The URL scheme specified (if one is specified) is not valid 
1466   
1467      Based on RFC 2396: http://www.faqs.org/rfcs/rfc2396.html 
1468   
1469      This function only checks the URL's syntax. It does not check that the URL 
1470      points to a real document, for example, or that it otherwise makes sense 
1471      semantically. This function does automatically prepend 'http://' in front 
1472      of a URL if and only if that's necessary to successfully parse the URL. 
1473      Please note that a scheme will be prepended only for rare cases 
1474      (e.g. 'google.ca:80') 
1475   
1476      The list of allowed schemes is customizable with the allowed_schemes 
1477      parameter. If you exclude None from the list, then abbreviated URLs 
1478      (lacking a scheme such as 'http') will be rejected. 
1479   
1480      The default prepended scheme is customizable with the prepend_scheme 
1481      parameter. If you set prepend_scheme to None then prepending will be 
1482      disabled. URLs that require prepending to parse will still be accepted, 
1483      but the return value will not be modified. 
1484   
1485      @author: Jonathan Benn 
1486   
1487      >>> IS_GENERIC_URL()('http://user@abc.com') 
1488      ('http://user@abc.com', None) 
1489   
1490      """ 
1491   
1492 -    def __init__( 
1493          self, 
1494          error_message='enter a valid URL', 
1495          allowed_schemes=None, 
1496          prepend_scheme=None, 
1497      ): 
 1498          """ 
1499          :param error_message: a string, the error message to give the end user 
1500              if the URL does not validate 
1501          :param allowed_schemes: a list containing strings or None. Each element 
1502              is a scheme the inputed URL is allowed to use 
1503          :param prepend_scheme: a string, this scheme is prepended if it's 
1504              necessary to make the URL valid 
1505          """ 
1506   
1507          self.error_message = error_message 
1508          if allowed_schemes is None: 
1509              self.allowed_schemes = all_url_schemes 
1510          else: 
1511              self.allowed_schemes = allowed_schemes 
1512          self.prepend_scheme = prepend_scheme 
1513          if self.prepend_scheme not in self.allowed_schemes: 
1514              raise SyntaxError("prepend_scheme='%s' is not in allowed_schemes=%s" 
1515                                % (self.prepend_scheme, self.allowed_schemes)) 
 1516   
1517      GENERIC_URL = re.compile(r"%[^0-9A-Fa-f]{2}|%[^0-9A-Fa-f][0-9A-Fa-f]|%[0-9A-Fa-f][^0-9A-Fa-f]|%$|%[0-9A-Fa-f]$|%[^0-9A-Fa-f]$") 
1518      GENERIC_URL_VALID = re.compile(r"[A-Za-z0-9;/?:@&=+$,\-_\.!~*'\(\)%#]+$") 
1519   
1521          """ 
1522          :param value: a string, the URL to validate 
1523          :returns: a tuple, where tuple[0] is the inputed value (possible 
1524              prepended with prepend_scheme), and tuple[1] is either 
1525              None (success!) or the string error_message 
1526          """ 
1527          try: 
1528               
1529              if not self.GENERIC_URL.search(value): 
1530                   
1531                  if self.GENERIC_URL_VALID.match(value): 
1532                       
1533                       
1534                      scheme = url_split_regex.match(value).group(2) 
1535                       
1536                      if not scheme is None: 
1537                          scheme = urllib.unquote(scheme).lower() 
1538                       
1539                      if scheme in self.allowed_schemes: 
1540                           
1541                          return (value, None) 
1542                      else: 
1543                           
1544                           
1545                           
1546                           
1547                          if value.find('://') < 0 and None in self.allowed_schemes: 
1548                              schemeToUse = self.prepend_scheme or 'http' 
1549                              prependTest = self.__call__( 
1550                                  schemeToUse + '://' + value) 
1551                               
1552                              if prependTest[1] is None: 
1553                                   
1554                                  if self.prepend_scheme: 
1555                                      return prependTest 
1556                                  else: 
1557                                       
1558                                       
1559                                      return (value, None) 
1560          except: 
1561              pass 
1562           
1563          return (value, translate(self.error_message)) 
  1564   
1565   
1566   
1567   
1568   
1569  official_top_level_domains = [ 
1570      'ac', 
1571      'ad', 
1572      'ae', 
1573      'aero', 
1574      'af', 
1575      'ag', 
1576      'ai', 
1577      'al', 
1578      'am', 
1579      'an', 
1580      'ao', 
1581      'aq', 
1582      'ar', 
1583      'arpa', 
1584      'as', 
1585      'asia', 
1586      'at', 
1587      'au', 
1588      'aw', 
1589      'ax', 
1590      'az', 
1591      'ba', 
1592      'bb', 
1593      'bd', 
1594      'be', 
1595      'bf', 
1596      'bg', 
1597      'bh', 
1598      'bi', 
1599      'biz', 
1600      'bj', 
1601      'bl', 
1602      'bm', 
1603      'bn', 
1604      'bo', 
1605      'br', 
1606      'bs', 
1607      'bt', 
1608      'bv', 
1609      'bw', 
1610      'by', 
1611      'bz', 
1612      'ca', 
1613      'cat', 
1614      'cc', 
1615      'cd', 
1616      'cf', 
1617      'cg', 
1618      'ch', 
1619      'ci', 
1620      'ck', 
1621      'cl', 
1622      'cm', 
1623      'cn', 
1624      'co', 
1625      'com', 
1626      'coop', 
1627      'cr', 
1628      'cu', 
1629      'cv', 
1630      'cx', 
1631      'cy', 
1632      'cz', 
1633      'de', 
1634      'dj', 
1635      'dk', 
1636      'dm', 
1637      'do', 
1638      'dz', 
1639      'ec', 
1640      'edu', 
1641      'ee', 
1642      'eg', 
1643      'eh', 
1644      'er', 
1645      'es', 
1646      'et', 
1647      'eu', 
1648      'example', 
1649      'fi', 
1650      'fj', 
1651      'fk', 
1652      'fm', 
1653      'fo', 
1654      'fr', 
1655      'ga', 
1656      'gb', 
1657      'gd', 
1658      'ge', 
1659      'gf', 
1660      'gg', 
1661      'gh', 
1662      'gi', 
1663      'gl', 
1664      'gm', 
1665      'gn', 
1666      'gov', 
1667      'gp', 
1668      'gq', 
1669      'gr', 
1670      'gs', 
1671      'gt', 
1672      'gu', 
1673      'gw', 
1674      'gy', 
1675      'hk', 
1676      'hm', 
1677      'hn', 
1678      'hr', 
1679      'ht', 
1680      'hu', 
1681      'id', 
1682      'ie', 
1683      'il', 
1684      'im', 
1685      'in', 
1686      'info', 
1687      'int', 
1688      'invalid', 
1689      'io', 
1690      'iq', 
1691      'ir', 
1692      'is', 
1693      'it', 
1694      'je', 
1695      'jm', 
1696      'jo', 
1697      'jobs', 
1698      'jp', 
1699      'ke', 
1700      'kg', 
1701      'kh', 
1702      'ki', 
1703      'km', 
1704      'kn', 
1705      'kp', 
1706      'kr', 
1707      'kw', 
1708      'ky', 
1709      'kz', 
1710      'la', 
1711      'lb', 
1712      'lc', 
1713      'li', 
1714      'lk', 
1715      'localhost', 
1716      'lr', 
1717      'ls', 
1718      'lt', 
1719      'lu', 
1720      'lv', 
1721      'ly', 
1722      'ma', 
1723      'mc', 
1724      'md', 
1725      'me', 
1726      'mf', 
1727      'mg', 
1728      'mh', 
1729      'mil', 
1730      'mk', 
1731      'ml', 
1732      'mm', 
1733      'mn', 
1734      'mo', 
1735      'mobi', 
1736      'mp', 
1737      'mq', 
1738      'mr', 
1739      'ms', 
1740      'mt', 
1741      'mu', 
1742      'museum', 
1743      'mv', 
1744      'mw', 
1745      'mx', 
1746      'my', 
1747      'mz', 
1748      'na', 
1749      'name', 
1750      'nc', 
1751      'ne', 
1752      'net', 
1753      'nf', 
1754      'ng', 
1755      'ni', 
1756      'nl', 
1757      'no', 
1758      'np', 
1759      'nr', 
1760      'nu', 
1761      'nz', 
1762      'om', 
1763      'org', 
1764      'pa', 
1765      'pe', 
1766      'pf', 
1767      'pg', 
1768      'ph', 
1769      'pk', 
1770      'pl', 
1771      'pm', 
1772      'pn', 
1773      'pr', 
1774      'pro', 
1775      'ps', 
1776      'pt', 
1777      'pw', 
1778      'py', 
1779      'qa', 
1780      're', 
1781      'ro', 
1782      'rs', 
1783      'ru', 
1784      'rw', 
1785      'sa', 
1786      'sb', 
1787      'sc', 
1788      'sd', 
1789      'se', 
1790      'sg', 
1791      'sh', 
1792      'si', 
1793      'sj', 
1794      'sk', 
1795      'sl', 
1796      'sm', 
1797      'sn', 
1798      'so', 
1799      'sr', 
1800      'st', 
1801      'su', 
1802      'sv', 
1803      'sy', 
1804      'sz', 
1805      'tc', 
1806      'td', 
1807      'tel', 
1808      'test', 
1809      'tf', 
1810      'tg', 
1811      'th', 
1812      'tj', 
1813      'tk', 
1814      'tl', 
1815      'tm', 
1816      'tn', 
1817      'to', 
1818      'tp', 
1819      'tr', 
1820      'travel', 
1821      'tt', 
1822      'tv', 
1823      'tw', 
1824      'tz', 
1825      'ua', 
1826      'ug', 
1827      'uk', 
1828      'um', 
1829      'us', 
1830      'uy', 
1831      'uz', 
1832      'va', 
1833      'vc', 
1834      've', 
1835      'vg', 
1836      'vi', 
1837      'vn', 
1838      'vu', 
1839      'wf', 
1840      'ws', 
1841      'xn--0zwm56d', 
1842      'xn--11b5bs3a9aj6g', 
1843      'xn--80akhbyknj4f', 
1844      'xn--9t4b11yi5a', 
1845      'xn--deba0ad', 
1846      'xn--g6w251d', 
1847      'xn--hgbk6aj7f53bba', 
1848      'xn--hlcj6aya9esc7a', 
1849      'xn--jxalpdlp', 
1850      'xn--kgbechtv', 
1851      'xn--p1ai', 
1852      'xn--zckzah', 
1853      'ye', 
1854      'yt', 
1855      'yu', 
1856      'za', 
1857      'zm', 
1858      'zw', 
1859  ] 
1863      """ 
1864      Rejects a URL string if any of the following is true: 
1865         * The string is empty or None 
1866         * The string uses characters that are not allowed in a URL 
1867         * The string breaks any of the HTTP syntactic rules 
1868         * The URL scheme specified (if one is specified) is not 'http' or 'https' 
1869         * The top-level domain (if a host name is specified) does not exist 
1870   
1871      Based on RFC 2616: http://www.faqs.org/rfcs/rfc2616.html 
1872   
1873      This function only checks the URL's syntax. It does not check that the URL 
1874      points to a real document, for example, or that it otherwise makes sense 
1875      semantically. This function does automatically prepend 'http://' in front 
1876      of a URL in the case of an abbreviated URL (e.g. 'google.ca'). 
1877   
1878      The list of allowed schemes is customizable with the allowed_schemes 
1879      parameter. If you exclude None from the list, then abbreviated URLs 
1880      (lacking a scheme such as 'http') will be rejected. 
1881   
1882      The default prepended scheme is customizable with the prepend_scheme 
1883      parameter. If you set prepend_scheme to None then prepending will be 
1884      disabled. URLs that require prepending to parse will still be accepted, 
1885      but the return value will not be modified. 
1886   
1887      @author: Jonathan Benn 
1888   
1889      >>> IS_HTTP_URL()('http://1.2.3.4') 
1890      ('http://1.2.3.4', None) 
1891      >>> IS_HTTP_URL()('http://abc.com') 
1892      ('http://abc.com', None) 
1893      >>> IS_HTTP_URL()('https://abc.com') 
1894      ('https://abc.com', None) 
1895      >>> IS_HTTP_URL()('httpx://abc.com') 
1896      ('httpx://abc.com', 'enter a valid URL') 
1897      >>> IS_HTTP_URL()('http://abc.com:80') 
1898      ('http://abc.com:80', None) 
1899      >>> IS_HTTP_URL()('http://user@abc.com') 
1900      ('http://user@abc.com', None) 
1901      >>> IS_HTTP_URL()('http://user@1.2.3.4') 
1902      ('http://user@1.2.3.4', None) 
1903   
1904      """ 
1905   
1906      GENERIC_VALID_IP = re.compile( 
1907          "([\w.!~*'|;:&=+$,-]+@)?\d+\.\d+\.\d+\.\d+(:\d*)*$") 
1908      GENERIC_VALID_DOMAIN = re.compile("([\w.!~*'|;:&=+$,-]+@)?(([A-Za-z0-9]+[A-Za-z0-9\-]*[A-Za-z0-9]+\.)*([A-Za-z0-9]+\.)*)*([A-Za-z]+[A-Za-z0-9\-]*[A-Za-z0-9]+)\.?(:\d*)*$") 
1909   
1910 -    def __init__( 
1911          self, 
1912          error_message='enter a valid URL', 
1913          allowed_schemes=None, 
1914          prepend_scheme='http', 
1915      ): 
 1916          """ 
1917          :param error_message: a string, the error message to give the end user 
1918              if the URL does not validate 
1919          :param allowed_schemes: a list containing strings or None. Each element 
1920              is a scheme the inputed URL is allowed to use 
1921          :param prepend_scheme: a string, this scheme is prepended if it's 
1922              necessary to make the URL valid 
1923          """ 
1924   
1925          self.error_message = error_message 
1926          if allowed_schemes is None: 
1927              self.allowed_schemes = http_schemes 
1928          else: 
1929              self.allowed_schemes = allowed_schemes 
1930          self.prepend_scheme = prepend_scheme 
1931   
1932          for i in self.allowed_schemes: 
1933              if i not in http_schemes: 
1934                  raise SyntaxError("allowed_scheme value '%s' is not in %s" % 
1935                                    (i, http_schemes)) 
1936   
1937          if self.prepend_scheme not in self.allowed_schemes: 
1938              raise SyntaxError("prepend_scheme='%s' is not in allowed_schemes=%s" % 
1939                                (self.prepend_scheme, self.allowed_schemes)) 
 1940   
1942          """ 
1943          :param value: a string, the URL to validate 
1944          :returns: a tuple, where tuple[0] is the inputed value 
1945              (possible prepended with prepend_scheme), and tuple[1] is either 
1946              None (success!) or the string error_message 
1947          """ 
1948   
1949          try: 
1950               
1951              x = IS_GENERIC_URL(error_message=self.error_message, 
1952                                 allowed_schemes=self.allowed_schemes, 
1953                                 prepend_scheme=self.prepend_scheme) 
1954              if x(value)[1] is None: 
1955                  componentsMatch = url_split_regex.match(value) 
1956                  authority = componentsMatch.group(4) 
1957                   
1958                  if authority: 
1959                       
1960                      if self.GENERIC_VALID_IP.match(authority): 
1961                           
1962                          return (value, None) 
1963                      else: 
1964                           
1965                          domainMatch = self.GENERIC_VALID_DOMAIN.match( 
1966                              authority) 
1967                          if domainMatch: 
1968                               
1969                              if domainMatch.group(5).lower()\ 
1970                                      in official_top_level_domains: 
1971                                   
1972                                  return (value, None) 
1973                  else: 
1974                       
1975                       
1976                      path = componentsMatch.group(5) 
1977                       
1978                       
1979                      if path.startswith('/'): 
1980                           
1981                          return (value, None) 
1982                      else: 
1983                           
1984                           
1985                          if value.find('://') < 0: 
1986                              schemeToUse = self.prepend_scheme or 'http' 
1987                              prependTest = self.__call__(schemeToUse 
1988                                                          + '://' + value) 
1989                               
1990                              if prependTest[1] is None: 
1991                                   
1992                                  if self.prepend_scheme: 
1993                                      return prependTest 
1994                                  else: 
1995                                       
1996                                       
1997                                      return (value, None) 
1998          except: 
1999              pass 
2000           
2001          return (value, translate(self.error_message)) 
  2002   
2003   
2004 -class IS_URL(Validator): 
 2005      """ 
2006      Rejects a URL string if any of the following is true: 
2007         * The string is empty or None 
2008         * The string uses characters that are not allowed in a URL 
2009         * The string breaks any of the HTTP syntactic rules 
2010         * The URL scheme specified (if one is specified) is not 'http' or 'https' 
2011         * The top-level domain (if a host name is specified) does not exist 
2012   
2013      (These rules are based on RFC 2616: http://www.faqs.org/rfcs/rfc2616.html) 
2014   
2015      This function only checks the URL's syntax. It does not check that the URL 
2016      points to a real document, for example, or that it otherwise makes sense 
2017      semantically. This function does automatically prepend 'http://' in front 
2018      of a URL in the case of an abbreviated URL (e.g. 'google.ca'). 
2019   
2020      If the parameter mode='generic' is used, then this function's behavior 
2021      changes. It then rejects a URL string if any of the following is true: 
2022         * The string is empty or None 
2023         * The string uses characters that are not allowed in a URL 
2024         * The URL scheme specified (if one is specified) is not valid 
2025   
2026      (These rules are based on RFC 2396: http://www.faqs.org/rfcs/rfc2396.html) 
2027   
2028      The list of allowed schemes is customizable with the allowed_schemes 
2029      parameter. If you exclude None from the list, then abbreviated URLs 
2030      (lacking a scheme such as 'http') will be rejected. 
2031   
2032      The default prepended scheme is customizable with the prepend_scheme 
2033      parameter. If you set prepend_scheme to None then prepending will be 
2034      disabled. URLs that require prepending to parse will still be accepted, 
2035      but the return value will not be modified. 
2036   
2037      IS_URL is compatible with the Internationalized Domain Name (IDN) standard 
2038      specified in RFC 3490 (http://tools.ietf.org/html/rfc3490). As a result, 
2039      URLs can be regular strings or unicode strings. 
2040      If the URL's domain component (e.g. google.ca) contains non-US-ASCII 
2041      letters, then the domain will be converted into Punycode (defined in 
2042      RFC 3492, http://tools.ietf.org/html/rfc3492). IS_URL goes a bit beyond 
2043      the standards, and allows non-US-ASCII characters to be present in the path 
2044      and query components of the URL as well. These non-US-ASCII characters will 
2045      be escaped using the standard '%20' type syntax. e.g. the unicode 
2046      character with hex code 0x4e86 will become '%4e%86' 
2047   
2048      Code Examples:: 
2049   
2050          INPUT(_type='text', _name='name', requires=IS_URL()) 
2051          >>> IS_URL()('abc.com') 
2052          ('http://abc.com', None) 
2053   
2054          INPUT(_type='text', _name='name', requires=IS_URL(mode='generic')) 
2055          >>> IS_URL(mode='generic')('abc.com') 
2056          ('abc.com', None) 
2057   
2058          INPUT(_type='text', _name='name', 
2059              requires=IS_URL(allowed_schemes=['https'], prepend_scheme='https')) 
2060          >>> IS_URL(allowed_schemes=['https'], prepend_scheme='https')('https://abc.com') 
2061          ('https://abc.com', None) 
2062   
2063          INPUT(_type='text', _name='name', 
2064              requires=IS_URL(prepend_scheme='https')) 
2065          >>> IS_URL(prepend_scheme='https')('abc.com') 
2066          ('https://abc.com', None) 
2067   
2068          INPUT(_type='text', _name='name', 
2069              requires=IS_URL(mode='generic', allowed_schemes=['ftps', 'https'], 
2070                  prepend_scheme='https')) 
2071          >>> IS_URL(mode='generic', allowed_schemes=['ftps', 'https'], prepend_scheme='https')('https://abc.com') 
2072          ('https://abc.com', None) 
2073          >>> IS_URL(mode='generic', allowed_schemes=['ftps', 'https', None], prepend_scheme='https')('abc.com') 
2074          ('abc.com', None) 
2075   
2076      @author: Jonathan Benn 
2077      """ 
2078   
2079 -    def __init__( 
2080          self, 
2081          error_message='enter a valid URL', 
2082          mode='http', 
2083          allowed_schemes=None, 
2084          prepend_scheme='http', 
2085      ): 
 2086          """ 
2087          :param error_message: a string, the error message to give the end user 
2088              if the URL does not validate 
2089          :param allowed_schemes: a list containing strings or None. Each element 
2090              is a scheme the inputed URL is allowed to use 
2091          :param prepend_scheme: a string, this scheme is prepended if it's 
2092              necessary to make the URL valid 
2093          """ 
2094   
2095          self.error_message = error_message 
2096          self.mode = mode.lower() 
2097          if not self.mode in ['generic', 'http']: 
2098              raise SyntaxError("invalid mode '%s' in IS_URL" % self.mode) 
2099          self.allowed_schemes = allowed_schemes 
2100   
2101          if self.allowed_schemes: 
2102              if prepend_scheme not in self.allowed_schemes: 
2103                  raise SyntaxError("prepend_scheme='%s' is not in allowed_schemes=%s" 
2104                                    % (prepend_scheme, self.allowed_schemes)) 
2105   
2106           
2107           
2108   
2109          self.prepend_scheme = prepend_scheme 
 2110   
2112          """ 
2113          :param value: a unicode or regular string, the URL to validate 
2114          :returns: a (string, string) tuple, where tuple[0] is the modified 
2115              input value and tuple[1] is either None (success!) or the 
2116              string error_message. The input value will never be modified in the 
2117              case of an error. However, if there is success then the input URL 
2118              may be modified to (1) prepend a scheme, and/or (2) convert a 
2119              non-compliant unicode URL into a compliant US-ASCII version. 
2120          """ 
2121   
2122          if self.mode == 'generic': 
2123              subMethod = IS_GENERIC_URL(error_message=self.error_message, 
2124                                         allowed_schemes=self.allowed_schemes, 
2125                                         prepend_scheme=self.prepend_scheme) 
2126          elif self.mode == 'http': 
2127              subMethod = IS_HTTP_URL(error_message=self.error_message, 
2128                                      allowed_schemes=self.allowed_schemes, 
2129                                      prepend_scheme=self.prepend_scheme) 
2130          else: 
2131              raise SyntaxError("invalid mode '%s' in IS_URL" % self.mode) 
2132   
2133          if type(value) != unicode: 
2134              return subMethod(value) 
2135          else: 
2136              try: 
2137                  asciiValue = unicode_to_ascii_url(value, self.prepend_scheme) 
2138              except Exception: 
2139                   
2140                   
2141                  return (value, translate(self.error_message)) 
2142   
2143              methodResult = subMethod(asciiValue) 
2144               
2145              if not methodResult[1] is None: 
2146                   
2147                  return (value, methodResult[1]) 
2148              else: 
2149                  return methodResult 
  2150   
2151   
2152  regex_time = re.compile( 
2153      '((?P<h>[0-9]+))([^0-9 ]+(?P<m>[0-9 ]+))?([^0-9ap ]+(?P<s>[0-9]*))?((?P<d>[ap]m))?') 
2154   
2155   
2156 -class IS_TIME(Validator): 
 2157      """ 
2158      example:: 
2159   
2160          INPUT(_type='text', _name='name', requires=IS_TIME()) 
2161   
2162      understands the following formats 
2163      hh:mm:ss [am/pm] 
2164      hh:mm [am/pm] 
2165      hh [am/pm] 
2166   
2167      [am/pm] is optional, ':' can be replaced by any other non-space non-digit 
2168   
2169          >>> IS_TIME()('21:30') 
2170          (datetime.time(21, 30), None) 
2171          >>> IS_TIME()('21-30') 
2172          (datetime.time(21, 30), None) 
2173          >>> IS_TIME()('21.30') 
2174          (datetime.time(21, 30), None) 
2175          >>> IS_TIME()('21:30:59') 
2176          (datetime.time(21, 30, 59), None) 
2177          >>> IS_TIME()('5:30') 
2178          (datetime.time(5, 30), None) 
2179          >>> IS_TIME()('5:30 am') 
2180          (datetime.time(5, 30), None) 
2181          >>> IS_TIME()('5:30 pm') 
2182          (datetime.time(17, 30), None) 
2183          >>> IS_TIME()('5:30 whatever') 
2184          ('5:30 whatever', 'enter time as hh:mm:ss (seconds, am, pm optional)') 
2185          >>> IS_TIME()('5:30 20') 
2186          ('5:30 20', 'enter time as hh:mm:ss (seconds, am, pm optional)') 
2187          >>> IS_TIME()('24:30') 
2188          ('24:30', 'enter time as hh:mm:ss (seconds, am, pm optional)') 
2189          >>> IS_TIME()('21:60') 
2190          ('21:60', 'enter time as hh:mm:ss (seconds, am, pm optional)') 
2191          >>> IS_TIME()('21:30::') 
2192          ('21:30::', 'enter time as hh:mm:ss (seconds, am, pm optional)') 
2193          >>> IS_TIME()('') 
2194          ('', 'enter time as hh:mm:ss (seconds, am, pm optional)') 
2195      """ 
2196   
2197 -    def __init__(self, error_message='enter time as hh:mm:ss (seconds, am, pm optional)'): 
 2198          self.error_message = error_message 
 2199   
2201          try: 
2202              ivalue = value 
2203              value = regex_time.match(value.lower()) 
2204              (h, m, s) = (int(value.group('h')), 0, 0) 
2205              if not value.group('m') is None: 
2206                  m = int(value.group('m')) 
2207              if not value.group('s') is None: 
2208                  s = int(value.group('s')) 
2209              if value.group('d') == 'pm' and 0 < h < 12: 
2210                  h = h + 12 
2211              if not (h in range(24) and m in range(60) and s 
2212                      in range(60)): 
2213                  raise ValueError('Hours or minutes or seconds are outside of allowed range') 
2214              value = datetime.time(h, m, s) 
2215              return (value, None) 
2216          except AttributeError: 
2217              pass 
2218          except ValueError: 
2219              pass 
2220          return (ivalue, translate(self.error_message)) 
  2221   
2222   
2223 -class UTC(datetime.tzinfo): 
 2224      """UTC""" 
2225      ZERO = datetime.timedelta(0) 
2230 -    def dst(self, dt): 
  2232  utc = UTC() 
2235      """ 
2236      example:: 
2237   
2238          INPUT(_type='text', _name='name', requires=IS_DATE()) 
2239   
2240      date has to be in the ISO8960 format YYYY-MM-DD 
2241      """ 
2242   
2243 -    def __init__(self, format='%Y-%m-%d', 
2244                   error_message='enter date as %(format)s', 
2245                   timezone = None): 
 2246          """ 
2247          timezome must be None or a pytz.timezone("America/Chicago") object 
2248          """ 
2249          self.format = translate(format) 
2250          self.error_message = str(error_message) 
2251          self.timezone = timezone 
2252          self.extremes = {} 
 2253   
2255          ovalue = value 
2256          if isinstance(value, datetime.date): 
2257              if self.timezone is not None: 
2258                  value = value - datetime.timedelta(seconds=self.timezone*3600) 
2259              return (value, None) 
2260          try: 
2261              (y, m, d, hh, mm, ss, t0, t1, t2) = \ 
2262                  time.strptime(value, str(self.format)) 
2263              value = datetime.date(y, m, d) 
2264              if self.timezone is not None: 
2265                  value = self.timezone.localize(value).astimezone(utc) 
2266              return (value, None) 
2267          except: 
2268              self.extremes.update(IS_DATETIME.nice(self.format)) 
2269              return (ovalue, translate(self.error_message) % self.extremes) 
 2270   
 2285   
2288      """ 
2289      example:: 
2290   
2291          INPUT(_type='text', _name='name', requires=IS_DATETIME()) 
2292   
2293      datetime has to be in the ISO8960 format YYYY-MM-DD hh:mm:ss 
2294      """ 
2295   
2296      isodatetime = '%Y-%m-%d %H:%M:%S' 
2297   
2298      @staticmethod 
2300          code = (('%Y', '1963'), 
2301                  ('%y', '63'), 
2302                  ('%d', '28'), 
2303                  ('%m', '08'), 
2304                  ('%b', 'Aug'), 
2305                  ('%B', 'August'), 
2306                  ('%H', '14'), 
2307                  ('%I', '02'), 
2308                  ('%p', 'PM'), 
2309                  ('%M', '30'), 
2310                  ('%S', '59')) 
2311          for (a, b) in code: 
2312              format = format.replace(a, b) 
2313          return dict(format=format) 
 2314   
2315 -    def __init__(self, format='%Y-%m-%d %H:%M:%S', 
2316                   error_message='enter date and time as %(format)s', 
2317                   timezone=None): 
 2318          """ 
2319          timezome must be None or a pytz.timezone("America/Chicago") object 
2320          """ 
2321          self.format = translate(format) 
2322          self.error_message = str(error_message) 
2323          self.extremes = {} 
2324          self.timezone = timezone 
 2325   
2327          ovalue = value 
2328          if isinstance(value, datetime.datetime): 
2329              return (value, None) 
2330          try: 
2331              (y, m, d, hh, mm, ss, t0, t1, t2) = \ 
2332                  time.strptime(value, str(self.format)) 
2333              value = datetime.datetime(y, m, d, hh, mm, ss) 
2334              if self.timezone is not None: 
2335                  value = self.timezone.localize(value).astimezone(utc) 
2336              return (value, None) 
2337          except: 
2338              self.extremes.update(IS_DATETIME.nice(self.format)) 
2339              return (ovalue, translate(self.error_message) % self.extremes) 
 2340   
 2356   
2359      """ 
2360      example:: 
2361   
2362          >>> v = IS_DATE_IN_RANGE(minimum=datetime.date(2008,1,1), \ 
2363                                   maximum=datetime.date(2009,12,31), \ 
2364                                   format="%m/%d/%Y",error_message="oops") 
2365   
2366          >>> v('03/03/2008') 
2367          (datetime.date(2008, 3, 3), None) 
2368   
2369          >>> v('03/03/2010') 
2370          ('03/03/2010', 'oops') 
2371   
2372          >>> v(datetime.date(2008,3,3)) 
2373          (datetime.date(2008, 3, 3), None) 
2374   
2375          >>> v(datetime.date(2010,3,3)) 
2376          (datetime.date(2010, 3, 3), 'oops') 
2377   
2378      """ 
2379 -    def __init__(self, 
2380                   minimum=None, 
2381                   maximum=None, 
2382                   format='%Y-%m-%d', 
2383                   error_message=None, 
2384                   timezone=None): 
 2385          self.minimum = minimum 
2386          self.maximum = maximum 
2387          if error_message is None: 
2388              if minimum is None: 
2389                  error_message = "enter date on or before %(max)s" 
2390              elif maximum is None: 
2391                  error_message = "enter date on or after %(min)s" 
2392              else: 
2393                  error_message = "enter date in range %(min)s %(max)s" 
2394          IS_DATE.__init__(self, 
2395                           format=format, 
2396                           error_message=error_message, 
2397                           timezone=timezone) 
2398          self.extremes = dict(min=minimum, max=maximum) 
 2399   
2401          ovalue = value 
2402          (value, msg) = IS_DATE.__call__(self, value) 
2403          if msg is not None: 
2404              return (value, msg) 
2405          if self.minimum and self.minimum > value: 
2406              return (ovalue, translate(self.error_message) % self.extremes) 
2407          if self.maximum and value > self.maximum: 
2408              return (ovalue, translate(self.error_message) % self.extremes) 
2409          return (value, None) 
  2410   
2413      """ 
2414      example:: 
2415   
2416          >>> v = IS_DATETIME_IN_RANGE(\ 
2417                  minimum=datetime.datetime(2008,1,1,12,20), \ 
2418                  maximum=datetime.datetime(2009,12,31,12,20), \ 
2419                  format="%m/%d/%Y %H:%M",error_message="oops") 
2420          >>> v('03/03/2008 12:40') 
2421          (datetime.datetime(2008, 3, 3, 12, 40), None) 
2422   
2423          >>> v('03/03/2010 10:34') 
2424          ('03/03/2010 10:34', 'oops') 
2425   
2426          >>> v(datetime.datetime(2008,3,3,0,0)) 
2427          (datetime.datetime(2008, 3, 3, 0, 0), None) 
2428   
2429          >>> v(datetime.datetime(2010,3,3,0,0)) 
2430          (datetime.datetime(2010, 3, 3, 0, 0), 'oops') 
2431      """ 
2432 -    def __init__(self, 
2433                   minimum=None, 
2434                   maximum=None, 
2435                   format='%Y-%m-%d %H:%M:%S', 
2436                   error_message=None, 
2437                   timezone=None): 
 2438          self.minimum = minimum 
2439          self.maximum = maximum 
2440          if error_message is None: 
2441              if minimum is None: 
2442                  error_message = "enter date and time on or before %(max)s" 
2443              elif maximum is None: 
2444                  error_message = "enter date and time on or after %(min)s" 
2445              else: 
2446                  error_message = "enter date and time in range %(min)s %(max)s" 
2447          IS_DATETIME.__init__(self, 
2448                               format=format, 
2449                               error_message=error_message, 
2450                               timezone=timezone) 
2451          self.extremes = dict(min=minimum, max=maximum) 
 2452   
2454          ovalue = value 
2455          (value, msg) = IS_DATETIME.__call__(self, value) 
2456          if msg is not None: 
2457              return (value, msg) 
2458          if self.minimum and self.minimum > value: 
2459              return (ovalue, translate(self.error_message) % self.extremes) 
2460          if self.maximum and value > self.maximum: 
2461              return (ovalue, translate(self.error_message) % self.extremes) 
2462          return (value, None) 
  2463   
2466   
2467 -    def __init__(self, other=None, minimum=0, maximum=100, 
2468                   error_message=None): 
 2469          self.other = other 
2470          self.minimum = minimum 
2471          self.maximum = maximum 
2472          self.error_message = error_message or "enter between %(min)g and %(max)g values" 
 2473   
2475          ivalue = value 
2476          if not isinstance(value, list): 
2477              ivalue = [ivalue] 
2478          if not self.minimum is None and len(ivalue) < self.minimum: 
2479              return (ivalue, translate(self.error_message) % dict(min=self.minimum, max=self.maximum)) 
2480          if not self.maximum is None and len(ivalue) > self.maximum: 
2481              return (ivalue, translate(self.error_message) % dict(min=self.minimum, max=self.maximum)) 
2482          new_value = [] 
2483          if self.other: 
2484              for item in ivalue: 
2485                  if item.strip(): 
2486                      (v, e) = self.other(item) 
2487                      if e: 
2488                          return (ivalue, e) 
2489                      else: 
2490                          new_value.append(v) 
2491              ivalue = new_value 
2492          return (ivalue, None) 
  2493   
2496      """ 
2497      convert to lower case 
2498   
2499      >>> IS_LOWER()('ABC') 
2500      ('abc', None) 
2501      >>> IS_LOWER()('Ñ') 
2502      ('\\xc3\\xb1', None) 
2503      """ 
2504   
 2507   
2510      """ 
2511      convert to upper case 
2512   
2513      >>> IS_UPPER()('abc') 
2514      ('ABC', None) 
2515      >>> IS_UPPER()('ñ') 
2516      ('\\xc3\\x91', None) 
2517      """ 
2518   
 2521   
2522   
2523 -def urlify(s, maxlen=80, keep_underscores=False): 
 2524      """ 
2525      Convert incoming string to a simplified ASCII subset. 
2526      if (keep_underscores): underscores are retained in the string 
2527      else: underscores are translated to hyphens (default) 
2528      """ 
2529      if isinstance(s, str): 
2530          s = s.decode('utf-8')              
2531      s = s.lower()                          
2532      s = unicodedata.normalize('NFKD', s)   
2533      s = s.encode('ascii', 'ignore')        
2534      s = re.sub('&\w+?;', '', s)            
2535      if keep_underscores: 
2536          s = re.sub('\s+', '-', s)          
2537          s = re.sub('[^\w\-]', '', s) 
2538                      
2539      else: 
2540          s = re.sub('[\s_]+', '-', s)       
2541          s = re.sub('[^a-z0-9\-]', '', s)   
2542      s = re.sub('[-_][-_]+', '-', s)        
2543      s = s.strip('-')                       
2544      return s[:maxlen]                      
 2545   
2548      """ 
2549      convert arbitrary text string to a slug 
2550   
2551      >>> IS_SLUG()('abc123') 
2552      ('abc123', None) 
2553      >>> IS_SLUG()('ABC123') 
2554      ('abc123', None) 
2555      >>> IS_SLUG()('abc-123') 
2556      ('abc-123', None) 
2557      >>> IS_SLUG()('abc--123') 
2558      ('abc-123', None) 
2559      >>> IS_SLUG()('abc 123') 
2560      ('abc-123', None) 
2561      >>> IS_SLUG()('abc\t_123') 
2562      ('abc-123', None) 
2563      >>> IS_SLUG()('-abc-') 
2564      ('abc', None) 
2565      >>> IS_SLUG()('--a--b--_ -c--') 
2566      ('a-b-c', None) 
2567      >>> IS_SLUG()('abc&123') 
2568      ('abc123', None) 
2569      >>> IS_SLUG()('abc&123&def') 
2570      ('abc123def', None) 
2571      >>> IS_SLUG()('ñ') 
2572      ('n', None) 
2573      >>> IS_SLUG(maxlen=4)('abc123') 
2574      ('abc1', None) 
2575      >>> IS_SLUG()('abc_123') 
2576      ('abc-123', None) 
2577      >>> IS_SLUG(keep_underscores=False)('abc_123') 
2578      ('abc-123', None) 
2579      >>> IS_SLUG(keep_underscores=True)('abc_123') 
2580      ('abc_123', None) 
2581      >>> IS_SLUG(check=False)('abc') 
2582      ('abc', None) 
2583      >>> IS_SLUG(check=True)('abc') 
2584      ('abc', None) 
2585      >>> IS_SLUG(check=False)('a bc') 
2586      ('a-bc', None) 
2587      >>> IS_SLUG(check=True)('a bc') 
2588      ('a bc', 'must be slug') 
2589      """ 
2590   
2591      @staticmethod 
2592 -    def urlify(value, maxlen=80, keep_underscores=False): 
 2593          return urlify(value, maxlen, keep_underscores) 
 2594   
2595 -    def __init__(self, maxlen=80, check=False, error_message='must be slug', keep_underscores=False): 
 2596          self.maxlen = maxlen 
2597          self.check = check 
2598          self.error_message = error_message 
2599          self.keep_underscores = keep_underscores 
 2600   
2602          if self.check and value != urlify(value, self.maxlen, self.keep_underscores): 
2603              return (value, translate(self.error_message)) 
2604          return (urlify(value, self.maxlen, self.keep_underscores), None) 
  2605   
2606   
2607 -class ANY_OF(Validator): 
 2608      """ 
2609      test if any of the validators in a list return successfully 
2610   
2611      >>> ANY_OF([IS_EMAIL(),IS_ALPHANUMERIC()])('a@b.co') 
2612      ('a@b.co', None) 
2613      >>> ANY_OF([IS_EMAIL(),IS_ALPHANUMERIC()])('abco') 
2614      ('abco', None) 
2615      >>> ANY_OF([IS_EMAIL(),IS_ALPHANUMERIC()])('@ab.co') 
2616      ('@ab.co', 'enter only letters, numbers, and underscore') 
2617      >>> ANY_OF([IS_ALPHANUMERIC(),IS_EMAIL()])('@ab.co') 
2618      ('@ab.co', 'enter a valid email address') 
2619      """ 
2620   
2623   
2625          for validator in self.subs: 
2626              value, error = validator(value) 
2627              if error == None: 
2628                  break 
2629          return value, error 
 2630   
 2637   
2640      """ 
2641      dummy class for testing IS_EMPTY_OR 
2642   
2643      >>> IS_EMPTY_OR(IS_EMAIL())('abc@def.com') 
2644      ('abc@def.com', None) 
2645      >>> IS_EMPTY_OR(IS_EMAIL())('   ') 
2646      (None, None) 
2647      >>> IS_EMPTY_OR(IS_EMAIL(), null='abc')('   ') 
2648      ('abc', None) 
2649      >>> IS_EMPTY_OR(IS_EMAIL(), null='abc', empty_regex='def')('def') 
2650      ('abc', None) 
2651      >>> IS_EMPTY_OR(IS_EMAIL())('abc') 
2652      ('abc', 'enter a valid email address') 
2653      >>> IS_EMPTY_OR(IS_EMAIL())(' abc ') 
2654      ('abc', 'enter a valid email address') 
2655      """ 
2656   
2657 -    def __init__(self, other, null=None, empty_regex=None): 
 2658          (self.other, self.null) = (other, null) 
2659          if empty_regex is not None: 
2660              self.empty_regex = re.compile(empty_regex) 
2661          else: 
2662              self.empty_regex = None 
2663          if hasattr(other, 'multiple'): 
2664              self.multiple = other.multiple 
2665          if hasattr(other, 'options'): 
2666              self.options = self._options 
 2667   
2673   
2675          if isinstance(self.other, (list, tuple)): 
2676              for item in self.other: 
2677                  if hasattr(item, 'set_self_id'): 
2678                      item.set_self_id(id) 
2679          else: 
2680              if hasattr(self.other, 'set_self_id'): 
2681                  self.other.set_self_id(id) 
 2682   
2684          value, empty = is_empty(value, empty_regex=self.empty_regex) 
2685          if empty: 
2686              return (self.null, None) 
2687          if isinstance(self.other, (list, tuple)): 
2688              error = None 
2689              for item in self.other: 
2690                  value, error = item(value) 
2691                  if error: 
2692                      break 
2693              return value, error 
2694          else: 
2695              return self.other(value) 
 2696   
 2701   
2702  IS_NULL_OR = IS_EMPTY_OR     
2703   
2704   
2705 -class CLEANUP(Validator): 
 2706      """ 
2707      example:: 
2708   
2709          INPUT(_type='text', _name='name', requires=CLEANUP()) 
2710   
2711      removes special characters on validation 
2712      """ 
2713      REGEX_CLEANUP = re.compile('[^\x09\x0a\x0d\x20-\x7e]') 
2714   
2718   
2720          v = self.regex.sub('', str(value).strip()) 
2721          return (v, None) 
  2722   
2725      """ 
2726      Stores a lazy password hash 
2727      """ 
2729          """ 
2730          crypt is an instance of the CRYPT validator, 
2731          password is the password as inserted by the user 
2732          """ 
2733          self.crypt = crypt 
2734          self.password = password 
2735          self.crypted = None 
 2736   
2738          """ 
2739          Encrypted self.password and caches it in self.crypted. 
2740          If self.crypt.salt the output is in the format <algorithm>$<salt>$<hash> 
2741   
2742          Try get the digest_alg from the key (if it exists) 
2743          else assume the default digest_alg. If not key at all, set key='' 
2744   
2745          If a salt is specified use it, if salt is True, set salt to uuid 
2746          (this should all be backward compatible) 
2747   
2748          Options: 
2749          key = 'uuid' 
2750          key = 'md5:uuid' 
2751          key = 'sha512:uuid' 
2752          ... 
2753          key = 'pbkdf2(1000,64,sha512):uuid' 1000 iterations and 64 chars length 
2754          """ 
2755          if self.crypted: 
2756              return self.crypted 
2757          if self.crypt.key: 
2758              if ':' in self.crypt.key: 
2759                  digest_alg, key = self.crypt.key.split(':', 1) 
2760              else: 
2761                  digest_alg, key = self.crypt.digest_alg, self.crypt.key 
2762          else: 
2763              digest_alg, key = self.crypt.digest_alg, '' 
2764          if self.crypt.salt: 
2765              if self.crypt.salt == True: 
2766                  salt = str(web2py_uuid()).replace('-', '')[-16:] 
2767              else: 
2768                  salt = self.crypt.salt 
2769          else: 
2770              salt = '' 
2771          hashed = simple_hash(self.password, key, salt, digest_alg) 
2772          self.crypted = '%s$%s$%s' % (digest_alg, salt, hashed) 
2773          return self.crypted 
 2774   
2775 -    def __eq__(self, stored_password): 
 2776          """ 
2777          compares the current lazy crypted password with a stored password 
2778          """ 
2779   
2780           
2781          if isinstance(stored_password, self.__class__): 
2782              return ((self is stored_password) or 
2783                     ((self.crypt.key == stored_password.crypt.key) and 
2784                     (self.password == stored_password.password))) 
2785   
2786          if self.crypt.key: 
2787              if ':' in self.crypt.key: 
2788                  key = self.crypt.key.split(':')[1] 
2789              else: 
2790                  key = self.crypt.key 
2791          else: 
2792              key = '' 
2793          if stored_password is None: 
2794              return False 
2795          elif stored_password.count('$') == 2: 
2796              (digest_alg, salt, hash) = stored_password.split('$') 
2797              h = simple_hash(self.password, key, salt, digest_alg) 
2798              temp_pass = '%s$%s$%s' % (digest_alg, salt, h) 
2799          else:   
2800               
2801              digest_alg = DIGEST_ALG_BY_SIZE.get(len(stored_password), None) 
2802              if not digest_alg: 
2803                  return False 
2804              else: 
2805                  temp_pass = simple_hash(self.password, key, '', digest_alg) 
2806          return temp_pass == stored_password 
  2807   
2808   
2809 -class CRYPT(object): 
 2810      """ 
2811      example:: 
2812   
2813          INPUT(_type='text', _name='name', requires=CRYPT()) 
2814   
2815      encodes the value on validation with a digest. 
2816   
2817      If no arguments are provided CRYPT uses the MD5 algorithm. 
2818      If the key argument is provided the HMAC+MD5 algorithm is used. 
2819      If the digest_alg is specified this is used to replace the 
2820      MD5 with, for example, SHA512. The digest_alg can be 
2821      the name of a hashlib algorithm as a string or the algorithm itself. 
2822   
2823      min_length is the minimal password length (default 4) - IS_STRONG for serious security 
2824      error_message is the message if password is too short 
2825   
2826      Notice that an empty password is accepted but invalid. It will not allow login back. 
2827      Stores junk as hashed password. 
2828   
2829      Specify an algorithm or by default we will use sha512. 
2830   
2831      Typical available algorithms: 
2832        md5, sha1, sha224, sha256, sha384, sha512 
2833   
2834      If salt, it hashes a password with a salt. 
2835      If salt is True, this method will automatically generate one. 
2836      Either case it returns an encrypted password string in the following format: 
2837   
2838        <algorithm>$<salt>$<hash> 
2839   
2840      Important: hashed password is returned as a LazyCrypt object and computed only if needed. 
2841      The LasyCrypt object also knows how to compare itself with an existing salted password 
2842   
2843      Supports standard algorithms 
2844   
2845      >>> for alg in ('md5','sha1','sha256','sha384','sha512'): 
2846      ...     print str(CRYPT(digest_alg=alg,salt=True)('test')[0]) 
2847      md5$...$... 
2848      sha1$...$... 
2849      sha256$...$... 
2850      sha384$...$... 
2851      sha512$...$... 
2852   
2853      The syntax is always alg$salt$hash 
2854   
2855      Supports for pbkdf2 
2856   
2857      >>> alg = 'pbkdf2(1000,20,sha512)' 
2858      >>> print str(CRYPT(digest_alg=alg,salt=True)('test')[0]) 
2859      pbkdf2(1000,20,sha512)$...$... 
2860   
2861      An optional hmac_key can be specified and it is used as salt prefix 
2862   
2863      >>> a = str(CRYPT(digest_alg='md5',key='mykey',salt=True)('test')[0]) 
2864      >>> print a 
2865      md5$...$... 
2866   
2867      Even if the algorithm changes the hash can still be validated 
2868   
2869      >>> CRYPT(digest_alg='sha1',key='mykey',salt=True)('test')[0] == a 
2870      True 
2871   
2872      If no salt is specified CRYPT can guess the algorithms from length: 
2873   
2874      >>> a = str(CRYPT(digest_alg='sha1',salt=False)('test')[0]) 
2875      >>> a 
2876      'sha1$$a94a8fe5ccb19ba61c4c0873d391e987982fbbd3' 
2877      >>> CRYPT(digest_alg='sha1',salt=False)('test')[0] == a 
2878      True 
2879      >>> CRYPT(digest_alg='sha1',salt=False)('test')[0] == a[6:] 
2880      True 
2881      >>> CRYPT(digest_alg='md5',salt=False)('test')[0] == a 
2882      True 
2883      >>> CRYPT(digest_alg='md5',salt=False)('test')[0] == a[6:] 
2884      True 
2885      """ 
2886   
2887 -    def __init__(self, 
2888                   key=None, 
2889                   digest_alg='pbkdf2(1000,20,sha512)', 
2890                   min_length=0, 
2891                   error_message='too short', salt=True): 
 2892          """ 
2893          important, digest_alg='md5' is not the default hashing algorithm for 
2894          web2py. This is only an example of usage of this function. 
2895   
2896          The actual hash algorithm is determined from the key which is 
2897          generated by web2py in tools.py. This defaults to hmac+sha512. 
2898          """ 
2899          self.key = key 
2900          self.digest_alg = digest_alg 
2901          self.min_length = min_length 
2902          self.error_message = error_message 
2903          self.salt = salt 
 2904   
2906          if len(value) < self.min_length: 
2907              return ('', translate(self.error_message)) 
2908          return (LazyCrypt(self, value), None) 
  2909   
2910   
2911   
2912  lowerset = frozenset(unicode('abcdefghijklmnopqrstuvwxyz')) 
2913  upperset = frozenset(unicode('ABCDEFGHIJKLMNOPQRSTUVWXYZ')) 
2914  numberset = frozenset(unicode('0123456789')) 
2915  sym1set = frozenset(unicode('!@#$%^&*()')) 
2916  sym2set = frozenset(unicode('~`-_=+[]{}\\|;:\'",.<>?/')) 
2917  otherset = frozenset( 
2918      unicode('0123456789abcdefghijklmnopqrstuvwxyz'))   
2922      " calculate a simple entropy for a given string " 
2923      import math 
2924      alphabet = 0     
2925      other = set() 
2926      seen = set() 
2927      lastset = None 
2928      if isinstance(string, str): 
2929          string = unicode(string, encoding='utf8') 
2930      for c in string: 
2931           
2932          inset = otherset 
2933          for cset in (lowerset, upperset, numberset, sym1set, sym2set): 
2934              if c in cset: 
2935                  inset = cset 
2936                  break 
2937           
2938          if inset not in seen: 
2939              seen.add(inset) 
2940              alphabet += len(inset)   
2941          elif c not in other: 
2942              alphabet += 1    
2943              other.add(c) 
2944          if inset is not lastset: 
2945              alphabet += 1    
2946              lastset = cset 
2947      entropy = len( 
2948          string) * math.log(alphabet) / 0.6931471805599453   
2949      return round(entropy, 2) 
 2950   
2953      """ 
2954      example:: 
2955   
2956          INPUT(_type='password', _name='passwd', 
2957              requires=IS_STRONG(min=10, special=2, upper=2)) 
2958   
2959      enforces complexity requirements on a field 
2960   
2961      >>> IS_STRONG(es=True)('Abcd1234') 
2962      ('Abcd1234', 
2963       'Must include at least 1 of the following: ~!@#$%^&*()_+-=?<>,.:;{}[]|') 
2964      >>> IS_STRONG(es=True)('Abcd1234!') 
2965      ('Abcd1234!', None) 
2966      >>> IS_STRONG(es=True, entropy=1)('a') 
2967      ('a', None) 
2968      >>> IS_STRONG(es=True, entropy=1, min=2)('a') 
2969      ('a', 'Minimum length is 2') 
2970      >>> IS_STRONG(es=True, entropy=100)('abc123') 
2971      ('abc123', 'Entropy (32.35) less than required (100)') 
2972      >>> IS_STRONG(es=True, entropy=100)('and') 
2973      ('and', 'Entropy (14.57) less than required (100)') 
2974      >>> IS_STRONG(es=True, entropy=100)('aaa') 
2975      ('aaa', 'Entropy (14.42) less than required (100)') 
2976      >>> IS_STRONG(es=True, entropy=100)('a1d') 
2977      ('a1d', 'Entropy (15.97) less than required (100)') 
2978      >>> IS_STRONG(es=True, entropy=100)('añd') 
2979      ('a\\xc3\\xb1d', 'Entropy (18.13) less than required (100)') 
2980   
2981      """ 
2982   
2983 -    def __init__(self, min=None, max=None, upper=None, lower=None, number=None, 
2984                   entropy=None, 
2985                   special=None, specials=r'~!@#$%^&*()_+-=?<>,.:;{}[]|', 
2986                   invalid=' "', error_message=None, es=False): 
 2987          self.entropy = entropy 
2988          if entropy is None: 
2989               
2990              self.min = 8 if min is None else min 
2991              self.max = max   
2992              self.upper = 1 if upper is None else upper 
2993              self.lower = 1 if lower is None else lower 
2994              self.number = 1 if number is None else number 
2995              self.special = 1 if special is None else special 
2996          else: 
2997               
2998              self.min = min 
2999              self.max = max 
3000              self.upper = upper 
3001              self.lower = lower 
3002              self.number = number 
3003              self.special = special 
3004          self.specials = specials 
3005          self.invalid = invalid 
3006          self.error_message = error_message 
3007          self.estring = es    
 3008   
3010          failures = [] 
3011          if value and len(value) == value.count('*') > 4: 
3012              return (value, None) 
3013          if self.entropy is not None: 
3014              entropy = calc_entropy(value) 
3015              if entropy < self.entropy: 
3016                  failures.append(translate("Entropy (%(have)s) less than required (%(need)s)") 
3017                                  % dict(have=entropy, need=self.entropy)) 
3018          if type(self.min) == int and self.min > 0: 
3019              if not len(value) >= self.min: 
3020                  failures.append(translate("Minimum length is %s") % self.min) 
3021          if type(self.max) == int and self.max > 0: 
3022              if not len(value) <= self.max: 
3023                  failures.append(translate("Maximum length is %s") % self.max) 
3024          if type(self.special) == int: 
3025              all_special = [ch in value for ch in self.specials] 
3026              if self.special > 0: 
3027                  if not all_special.count(True) >= self.special: 
3028                      failures.append(translate("Must include at least %s of the following: %s") 
3029                                      % (self.special, self.specials)) 
3030          if self.invalid: 
3031              all_invalid = [ch in value for ch in self.invalid] 
3032              if all_invalid.count(True) > 0: 
3033                  failures.append(translate("May not contain any of the following: %s") 
3034                                  % self.invalid) 
3035          if type(self.upper) == int: 
3036              all_upper = re.findall("[A-Z]", value) 
3037              if self.upper > 0: 
3038                  if not len(all_upper) >= self.upper: 
3039                      failures.append(translate("Must include at least %s upper case") 
3040                                      % str(self.upper)) 
3041              else: 
3042                  if len(all_upper) > 0: 
3043                      failures.append( 
3044                          translate("May not include any upper case letters")) 
3045          if type(self.lower) == int: 
3046              all_lower = re.findall("[a-z]", value) 
3047              if self.lower > 0: 
3048                  if not len(all_lower) >= self.lower: 
3049                      failures.append(translate("Must include at least %s lower case") 
3050                                      % str(self.lower)) 
3051              else: 
3052                  if len(all_lower) > 0: 
3053                      failures.append( 
3054                          translate("May not include any lower case letters")) 
3055          if type(self.number) == int: 
3056              all_number = re.findall("[0-9]", value) 
3057              if self.number > 0: 
3058                  numbers = "number" 
3059                  if self.number > 1: 
3060                      numbers = "numbers" 
3061                  if not len(all_number) >= self.number: 
3062                      failures.append(translate("Must include at least %s %s") 
3063                                      % (str(self.number), numbers)) 
3064              else: 
3065                  if len(all_number) > 0: 
3066                      failures.append(translate("May not include any numbers")) 
3067          if len(failures) == 0: 
3068              return (value, None) 
3069          if not self.error_message: 
3070              if self.estring: 
3071                  return (value, '|'.join(failures)) 
3072              from html import XML 
3073              return (value, XML('<br />'.join(failures))) 
3074          else: 
3075              return (value, translate(self.error_message)) 
  3076   
3079   
3080      REGEX_W = re.compile('\w+') 
3081   
3084   
 3091   
3094      """ 
3095      Checks if file uploaded through file input was saved in one of selected 
3096      image formats and has dimensions (width and height) within given boundaries. 
3097   
3098      Does *not* check for maximum file size (use IS_LENGTH for that). Returns 
3099      validation failure if no data was uploaded. 
3100   
3101      Supported file formats: BMP, GIF, JPEG, PNG. 
3102   
3103      Code parts taken from 
3104      http://mail.python.org/pipermail/python-list/2007-June/617126.html 
3105   
3106      Arguments: 
3107   
3108      extensions: iterable containing allowed *lowercase* image file extensions 
3109      ('jpg' extension of uploaded file counts as 'jpeg') 
3110      maxsize: iterable containing maximum width and height of the image 
3111      minsize: iterable containing minimum width and height of the image 
3112   
3113      Use (-1, -1) as minsize to pass image size check. 
3114   
3115      Examples:: 
3116   
3117          #Check if uploaded file is in any of supported image formats: 
3118          INPUT(_type='file', _name='name', requires=IS_IMAGE()) 
3119   
3120          #Check if uploaded file is either JPEG or PNG: 
3121          INPUT(_type='file', _name='name', 
3122              requires=IS_IMAGE(extensions=('jpeg', 'png'))) 
3123   
3124          #Check if uploaded file is PNG with maximum size of 200x200 pixels: 
3125          INPUT(_type='file', _name='name', 
3126              requires=IS_IMAGE(extensions=('png'), maxsize=(200, 200))) 
3127      """ 
3128   
3129 -    def __init__(self, 
3130                   extensions=('bmp', 'gif', 'jpeg', 'png'), 
3131                   maxsize=(10000, 10000), 
3132                   minsize=(0, 0), 
3133                   error_message='invalid image'): 
 3134   
3135          self.extensions = extensions 
3136          self.maxsize = maxsize 
3137          self.minsize = minsize 
3138          self.error_message = error_message 
 3139   
3141          try: 
3142              extension = value.filename.rfind('.') 
3143              assert extension >= 0 
3144              extension = value.filename[extension + 1:].lower() 
3145              if extension == 'jpg': 
3146                  extension = 'jpeg' 
3147              assert extension in self.extensions 
3148              if extension == 'bmp': 
3149                  width, height = self.__bmp(value.file) 
3150              elif extension == 'gif': 
3151                  width, height = self.__gif(value.file) 
3152              elif extension == 'jpeg': 
3153                  width, height = self.__jpeg(value.file) 
3154              elif extension == 'png': 
3155                  width, height = self.__png(value.file) 
3156              else: 
3157                  width = -1 
3158                  height = -1 
3159              assert self.minsize[0] <= width <= self.maxsize[0] \ 
3160                  and self.minsize[1] <= height <= self.maxsize[1] 
3161              value.file.seek(0) 
3162              return (value, None) 
3163          except: 
3164              return (value, translate(self.error_message)) 
 3165   
3166 -    def __bmp(self, stream): 
 3171   
3172 -    def __gif(self, stream): 
 3178   
3180          if stream.read(2) == '\xFF\xD8': 
3181              while True: 
3182                  (marker, code, length) = struct.unpack("!BBH", stream.read(4)) 
3183                  if marker != 0xFF: 
3184                      break 
3185                  elif code >= 0xC0 and code <= 0xC3: 
3186                      return tuple(reversed( 
3187                          struct.unpack("!xHH", stream.read(5)))) 
3188                  else: 
3189                      stream.read(length - 2) 
3190          return (-1, -1) 
 3191   
3192 -    def __png(self, stream): 
  3198   
3201      """ 
3202      Checks if name and extension of file uploaded through file input matches 
3203      given criteria. 
3204   
3205      Does *not* ensure the file type in any way. Returns validation failure 
3206      if no data was uploaded. 
3207   
3208      Arguments:: 
3209   
3210      filename: filename (before dot) regex 
3211      extension: extension (after dot) regex 
3212      lastdot: which dot should be used as a filename / extension separator: 
3213               True means last dot, eg. file.png -> file / png 
3214               False means first dot, eg. file.tar.gz -> file / tar.gz 
3215      case: 0 - keep the case, 1 - transform the string into lowercase (default), 
3216            2 - transform the string into uppercase 
3217   
3218      If there is no dot present, extension checks will be done against empty 
3219      string and filename checks against whole value. 
3220   
3221      Examples:: 
3222   
3223          #Check if file has a pdf extension (case insensitive): 
3224          INPUT(_type='file', _name='name', 
3225              requires=IS_UPLOAD_FILENAME(extension='pdf')) 
3226   
3227          #Check if file has a tar.gz extension and name starting with backup: 
3228          INPUT(_type='file', _name='name', 
3229              requires=IS_UPLOAD_FILENAME(filename='backup.*', 
3230                  extension='tar.gz', lastdot=False)) 
3231   
3232          #Check if file has no extension and name matching README 
3233          #(case sensitive): 
3234          INPUT(_type='file', _name='name', 
3235              requires=IS_UPLOAD_FILENAME(filename='^README$', 
3236                  extension='^$', case=0)) 
3237      """ 
3238   
3239 -    def __init__(self, filename=None, extension=None, lastdot=True, case=1, 
3240                   error_message='enter valid filename'): 
 3241          if isinstance(filename, str): 
3242              filename = re.compile(filename) 
3243          if isinstance(extension, str): 
3244              extension = re.compile(extension) 
3245          self.filename = filename 
3246          self.extension = extension 
3247          self.lastdot = lastdot 
3248          self.case = case 
3249          self.error_message = error_message 
 3250   
3252          try: 
3253              string = value.filename 
3254          except: 
3255              return (value, translate(self.error_message)) 
3256          if self.case == 1: 
3257              string = string.lower() 
3258          elif self.case == 2: 
3259              string = string.upper() 
3260          if self.lastdot: 
3261              dot = string.rfind('.') 
3262          else: 
3263              dot = string.find('.') 
3264          if dot == -1: 
3265              dot = len(string) 
3266          if self.filename and not self.filename.match(string[:dot]): 
3267              return (value, translate(self.error_message)) 
3268          elif self.extension and not self.extension.match(string[dot + 1:]): 
3269              return (value, translate(self.error_message)) 
3270          else: 
3271              return (value, None) 
  3272   
3275      """ 
3276      Checks if field's value is an IP version 4 address in decimal form. Can 
3277      be set to force addresses from certain range. 
3278   
3279      IPv4 regex taken from: http://regexlib.com/REDetails.aspx?regexp_id=1411 
3280   
3281      Arguments: 
3282   
3283      minip: lowest allowed address; accepts: 
3284             str, eg. 192.168.0.1 
3285             list or tuple of octets, eg. [192, 168, 0, 1] 
3286      maxip: highest allowed address; same as above 
3287      invert: True to allow addresses only from outside of given range; note 
3288              that range boundaries are not matched this way 
3289      is_localhost: localhost address treatment: 
3290                    None (default): indifferent 
3291                    True (enforce): query address must match localhost address 
3292                                    (127.0.0.1) 
3293                    False (forbid): query address must not match localhost 
3294                                    address 
3295      is_private: same as above, except that query address is checked against 
3296                  two address ranges: 172.16.0.0 - 172.31.255.255 and 
3297                  192.168.0.0 - 192.168.255.255 
3298      is_automatic: same as above, except that query address is checked against 
3299                    one address range: 169.254.0.0 - 169.254.255.255 
3300   
3301      Minip and maxip may also be lists or tuples of addresses in all above 
3302      forms (str, int, list / tuple), allowing setup of multiple address ranges: 
3303   
3304          minip = (minip1, minip2, ... minipN) 
3305                     |       |           | 
3306                     |       |           | 
3307          maxip = (maxip1, maxip2, ... maxipN) 
3308   
3309      Longer iterable will be truncated to match length of shorter one. 
3310   
3311      Examples:: 
3312   
3313          #Check for valid IPv4 address: 
3314          INPUT(_type='text', _name='name', requires=IS_IPV4()) 
3315   
3316          #Check for valid IPv4 address belonging to specific range: 
3317          INPUT(_type='text', _name='name', 
3318              requires=IS_IPV4(minip='100.200.0.0', maxip='100.200.255.255')) 
3319   
3320          #Check for valid IPv4 address belonging to either 100.110.0.0 - 
3321          #100.110.255.255 or 200.50.0.0 - 200.50.0.255 address range: 
3322          INPUT(_type='text', _name='name', 
3323              requires=IS_IPV4(minip=('100.110.0.0', '200.50.0.0'), 
3324                               maxip=('100.110.255.255', '200.50.0.255'))) 
3325   
3326          #Check for valid IPv4 address belonging to private address space: 
3327          INPUT(_type='text', _name='name', requires=IS_IPV4(is_private=True)) 
3328   
3329          #Check for valid IPv4 address that is not a localhost address: 
3330          INPUT(_type='text', _name='name', requires=IS_IPV4(is_localhost=False)) 
3331   
3332      >>> IS_IPV4()('1.2.3.4') 
3333      ('1.2.3.4', None) 
3334      >>> IS_IPV4()('255.255.255.255') 
3335      ('255.255.255.255', None) 
3336      >>> IS_IPV4()('1.2.3.4 ') 
3337      ('1.2.3.4 ', 'enter valid IPv4 address') 
3338      >>> IS_IPV4()('1.2.3.4.5') 
3339      ('1.2.3.4.5', 'enter valid IPv4 address') 
3340      >>> IS_IPV4()('123.123') 
3341      ('123.123', 'enter valid IPv4 address') 
3342      >>> IS_IPV4()('1111.2.3.4') 
3343      ('1111.2.3.4', 'enter valid IPv4 address') 
3344      >>> IS_IPV4()('0111.2.3.4') 
3345      ('0111.2.3.4', 'enter valid IPv4 address') 
3346      >>> IS_IPV4()('256.2.3.4') 
3347      ('256.2.3.4', 'enter valid IPv4 address') 
3348      >>> IS_IPV4()('300.2.3.4') 
3349      ('300.2.3.4', 'enter valid IPv4 address') 
3350      >>> IS_IPV4(minip='1.2.3.4', maxip='1.2.3.4')('1.2.3.4') 
3351      ('1.2.3.4', None) 
3352      >>> IS_IPV4(minip='1.2.3.5', maxip='1.2.3.9', error_message='bad ip')('1.2.3.4') 
3353      ('1.2.3.4', 'bad ip') 
3354      >>> IS_IPV4(maxip='1.2.3.4', invert=True)('127.0.0.1') 
3355      ('127.0.0.1', None) 
3356      >>> IS_IPV4(maxip='1.2.3.4', invert=True)('1.2.3.4') 
3357      ('1.2.3.4', 'enter valid IPv4 address') 
3358      >>> IS_IPV4(is_localhost=True)('127.0.0.1') 
3359      ('127.0.0.1', None) 
3360      >>> IS_IPV4(is_localhost=True)('1.2.3.4') 
3361      ('1.2.3.4', 'enter valid IPv4 address') 
3362      >>> IS_IPV4(is_localhost=False)('127.0.0.1') 
3363      ('127.0.0.1', 'enter valid IPv4 address') 
3364      >>> IS_IPV4(maxip='100.0.0.0', is_localhost=True)('127.0.0.1') 
3365      ('127.0.0.1', 'enter valid IPv4 address') 
3366      """ 
3367   
3368      regex = re.compile( 
3369          '^(([1-9]?\d|1\d\d|2[0-4]\d|25[0-5])\.){3}([1-9]?\d|1\d\d|2[0-4]\d|25[0-5])$') 
3370      numbers = (16777216, 65536, 256, 1) 
3371      localhost = 2130706433 
3372      private = ((2886729728L, 2886795263L), (3232235520L, 3232301055L)) 
3373      automatic = (2851995648L, 2852061183L) 
3374   
3375 -    def __init__( 
3376          self, 
3377          minip='0.0.0.0', 
3378          maxip='255.255.255.255', 
3379          invert=False, 
3380          is_localhost=None, 
3381          is_private=None, 
3382          is_automatic=None, 
3383              error_message='enter valid IPv4 address'): 
 3384          for n, value in enumerate((minip, maxip)): 
3385              temp = [] 
3386              if isinstance(value, str): 
3387                  temp.append(value.split('.')) 
3388              elif isinstance(value, (list, tuple)): 
3389                  if len(value) == len(filter(lambda item: isinstance(item, int), value)) == 4: 
3390                      temp.append(value) 
3391                  else: 
3392                      for item in value: 
3393                          if isinstance(item, str): 
3394                              temp.append(item.split('.')) 
3395                          elif isinstance(item, (list, tuple)): 
3396                              temp.append(item) 
3397              numbers = [] 
3398              for item in temp: 
3399                  number = 0 
3400                  for i, j in zip(self.numbers, item): 
3401                      number += i * int(j) 
3402                  numbers.append(number) 
3403              if n == 0: 
3404                  self.minip = numbers 
3405              else: 
3406                  self.maxip = numbers 
3407          self.invert = invert 
3408          self.is_localhost = is_localhost 
3409          self.is_private = is_private 
3410          self.is_automatic = is_automatic 
3411          self.error_message = error_message 
 3412   
3414          if self.regex.match(value): 
3415              number = 0 
3416              for i, j in zip(self.numbers, value.split('.')): 
3417                  number += i * int(j) 
3418              ok = False 
3419              for bottom, top in zip(self.minip, self.maxip): 
3420                  if self.invert != (bottom <= number <= top): 
3421                      ok = True 
3422              if not (self.is_localhost is None or self.is_localhost == 
3423                      (number == self.localhost)): 
3424                      ok = False 
3425              if not (self.is_private is None or self.is_private == 
3426                      (sum([number[0] <= number <= number[1] for number in self.private]) > 0)): 
3427                      ok = False 
3428              if not (self.is_automatic is None or self.is_automatic == 
3429                      (self.automatic[0] <= number <= self.automatic[1])): 
3430                      ok = False 
3431              if ok: 
3432                  return (value, None) 
3433          return (value, translate(self.error_message)) 
  3434   
3436      """ 
3437      Checks if field's value is an IP version 6 address. First attempts to 
3438      use the ipaddress library and falls back to contrib/ipaddr.py from Google 
3439      (https://code.google.com/p/ipaddr-py/) 
3440   
3441      Arguments: 
3442      is_private: None (default): indifferent 
3443                  True (enforce): address must be in fc00::/7 range 
3444                  False (forbid): address must NOT be in fc00::/7 range 
3445      is_link_local: Same as above but uses fe80::/10 range 
3446      is_reserved: Same as above but uses IETF reserved range 
3447      is_mulicast: Same as above but uses ff00::/8 range 
3448      is_routeable: Similar to above but enforces not private, link_local, 
3449                    reserved or multicast 
3450      is_6to4: Same as above but uses 2002::/16 range 
3451      is_teredo: Same as above but uses 2001::/32 range 
3452      subnets: value must be a member of at least one from list of subnets 
3453   
3454      Examples: 
3455   
3456          #Check for valid IPv6 address: 
3457          INPUT(_type='text', _name='name', requires=IS_IPV6()) 
3458   
3459          #Check for valid IPv6 address is a link_local address: 
3460          INPUT(_type='text', _name='name', requires=IS_IPV6(is_link_local=True)) 
3461   
3462          #Check for valid IPv6 address that is Internet routeable: 
3463          INPUT(_type='text', _name='name', requires=IS_IPV6(is_routeable=True)) 
3464   
3465          #Check for valid IPv6 address in specified subnet: 
3466          INPUT(_type='text', _name='name', requires=IS_IPV6(subnets=['2001::/32']) 
3467   
3468      >>> IS_IPV6()('fe80::126c:8ffa:fe22:b3af') 
3469      ('fe80::126c:8ffa:fe22:b3af', None) 
3470      >>> IS_IPV6()('192.168.1.1') 
3471      ('192.168.1.1', 'enter valid IPv6 address') 
3472      >>> IS_IPV6(error_message='bad ip')('192.168.1.1') 
3473      ('192.168.1.1', 'bad ip') 
3474      >>> IS_IPV6(is_link_local=True)('fe80::126c:8ffa:fe22:b3af') 
3475      ('fe80::126c:8ffa:fe22:b3af', None) 
3476      >>> IS_IPV6(is_link_local=False)('fe80::126c:8ffa:fe22:b3af') 
3477      ('fe80::126c:8ffa:fe22:b3af', 'enter valid IPv6 address') 
3478      >>> IS_IPV6(is_link_local=True)('2001::126c:8ffa:fe22:b3af') 
3479      ('2001::126c:8ffa:fe22:b3af', 'enter valid IPv6 address') 
3480      >>> IS_IPV6(is_multicast=True)('2001::126c:8ffa:fe22:b3af') 
3481      ('2001::126c:8ffa:fe22:b3af', 'enter valid IPv6 address') 
3482      >>> IS_IPV6(is_multicast=True)('ff00::126c:8ffa:fe22:b3af') 
3483      ('ff00::126c:8ffa:fe22:b3af', None) 
3484      >>> IS_IPV6(is_routeable=True)('2001::126c:8ffa:fe22:b3af') 
3485      ('2001::126c:8ffa:fe22:b3af', None) 
3486      >>> IS_IPV6(is_routeable=True)('ff00::126c:8ffa:fe22:b3af') 
3487      ('ff00::126c:8ffa:fe22:b3af', 'enter valid IPv6 address') 
3488      >>> IS_IPV6(subnets='2001::/32')('2001::8ffa:fe22:b3af') 
3489      ('2001::8ffa:fe22:b3af', None) 
3490      >>> IS_IPV6(subnets='fb00::/8')('2001::8ffa:fe22:b3af') 
3491      ('2001::8ffa:fe22:b3af', 'enter valid IPv6 address') 
3492      >>> IS_IPV6(subnets=['fc00::/8','2001::/32'])('2001::8ffa:fe22:b3af') 
3493      ('2001::8ffa:fe22:b3af', None) 
3494      >>> IS_IPV6(subnets='invalidsubnet')('2001::8ffa:fe22:b3af') 
3495      ('2001::8ffa:fe22:b3af', 'invalid subnet provided') 
3496       
3497      """ 
3498   
3499 -    def __init__( 
3500              self, 
3501              is_private=None, 
3502              is_link_local=None, 
3503              is_reserved=None, 
3504              is_multicast=None, 
3505              is_routeable=None, 
3506              is_6to4=None, 
3507              is_teredo=None, 
3508              subnets=None, 
3509              error_message='enter valid IPv6 address'): 
 3510          self.is_private = is_private 
3511          self.is_link_local = is_link_local 
3512          self.is_reserved = is_reserved 
3513          self.is_multicast = is_multicast 
3514          self.is_routeable = is_routeable 
3515          self.is_6to4 = is_6to4 
3516          self.is_teredo = is_teredo 
3517          self.subnets = subnets 
3518          self.error_message = error_message 
 3519   
3521          try: 
3522              import ipaddress 
3523          except ImportError: 
3524              from contrib import ipaddr as ipaddress 
3525   
3526          try: 
3527              ip = ipaddress.IPv6Address(value) 
3528              ok = True 
3529          except ipaddress.AddressValueError: 
3530              return (value, translate(self.error_message)) 
3531   
3532          if self.subnets: 
3533               
3534              ok = False 
3535              if isinstance(self.subnets, str): 
3536                  self.subnets = [self.subnets] 
3537              for network in self.subnets: 
3538                  try: 
3539                      ipnet = ipaddress.IPv6Network(network) 
3540                  except (ipaddress.NetmaskValueError, ipaddress.AddressValueError): 
3541                      return (value, translate('invalid subnet provided')) 
3542                  if ip in ipnet: 
3543                      ok = True 
3544   
3545          if self.is_routeable: 
3546              self.is_private = False 
3547              self.is_link_local = False 
3548              self.is_reserved = False 
3549              self.is_multicast = False 
3550   
3551          if not (self.is_private is None or self.is_private == 
3552                  ip.is_private): 
3553              ok = False 
3554          if not (self.is_link_local is None or self.is_link_local == 
3555                  ip.is_link_local): 
3556              ok = False 
3557          if not (self.is_reserved is None or self.is_reserved == 
3558                  ip.is_reserved): 
3559              ok = False 
3560          if not (self.is_multicast is None or self.is_multicast == 
3561                  ip.is_multicast): 
3562              ok = False 
3563          if not (self.is_6to4 is None or self.is_6to4 == 
3564                  ip.is_6to4): 
3565              ok = False 
3566          if not (self.is_teredo is None or self.is_teredo == 
3567                  ip.is_teredo): 
3568              ok = False 
3569   
3570          if ok: 
3571              return (value, None) 
3572   
3573          return (value, translate(self.error_message)) 
  3574   
3577      """ 
3578      Checks if field's value is an IP Address (v4 or v6). Can be set to force 
3579      addresses from within a specific range. Checks are done with the correct 
3580      IS_IPV4 and IS_IPV6 validators. 
3581   
3582      Uses ipaddress library if found, falls back to PEP-3144 ipaddr.py from 
3583      Google (in contrib). 
3584   
3585      Universal arguments: 
3586   
3587      minip: lowest allowed address; accepts: 
3588             str, eg. 192.168.0.1 
3589             list or tuple of octets, eg. [192, 168, 0, 1] 
3590      maxip: highest allowed address; same as above 
3591      invert: True to allow addresses only from outside of given range; note 
3592              that range boundaries are not matched this way 
3593   
3594      IPv4 specific arguments: 
3595   
3596      is_localhost: localhost address treatment: 
3597                    None (default): indifferent 
3598                    True (enforce): query address must match localhost address 
3599                                    (127.0.0.1) 
3600                    False (forbid): query address must not match localhost 
3601                                    address 
3602      is_private: same as above, except that query address is checked against 
3603                  two address ranges: 172.16.0.0 - 172.31.255.255 and 
3604                  192.168.0.0 - 192.168.255.255 
3605      is_automatic: same as above, except that query address is checked against 
3606                    one address range: 169.254.0.0 - 169.254.255.255 
3607      is_ipv4: None (default): indifferent 
3608               True (enforce): must be an IPv4 address 
3609               False (forbid): must NOT be an IPv4 address 
3610   
3611      IPv6 specific arguments: 
3612   
3613      is_link_local: Same as above but uses fe80::/10 range 
3614      is_reserved: Same as above but uses IETF reserved range 
3615      is_mulicast: Same as above but uses ff00::/8 range 
3616      is_routeable: Similar to above but enforces not private, link_local, 
3617                    reserved or multicast 
3618      is_6to4: Same as above but uses 2002::/16 range 
3619      is_teredo: Same as above but uses 2001::/32 range 
3620      subnets: value must be a member of at least one from list of subnets 
3621      is_ipv6: None (default): indifferent 
3622               True (enforce): must be an IPv6 address 
3623               False (forbid): must NOT be an IPv6 address 
3624   
3625      Minip and maxip may also be lists or tuples of addresses in all above 
3626      forms (str, int, list / tuple), allowing setup of multiple address ranges: 
3627   
3628          minip = (minip1, minip2, ... minipN) 
3629                     |       |           | 
3630                     |       |           | 
3631          maxip = (maxip1, maxip2, ... maxipN) 
3632   
3633      Longer iterable will be truncated to match length of shorter one. 
3634   
3635      >>> IS_IPADDRESS()('192.168.1.5') 
3636      ('192.168.1.5', None) 
3637      >>> IS_IPADDRESS(is_ipv6=False)('192.168.1.5') 
3638      ('192.168.1.5', None) 
3639      >>> IS_IPADDRESS()('255.255.255.255') 
3640      ('255.255.255.255', None) 
3641      >>> IS_IPADDRESS()('192.168.1.5 ') 
3642      ('192.168.1.5 ', 'enter valid IP address') 
3643      >>> IS_IPADDRESS()('192.168.1.1.5') 
3644      ('192.168.1.1.5', 'enter valid IP address') 
3645      >>> IS_IPADDRESS()('123.123') 
3646      ('123.123', 'enter valid IP address') 
3647      >>> IS_IPADDRESS()('1111.2.3.4') 
3648      ('1111.2.3.4', 'enter valid IP address') 
3649      >>> IS_IPADDRESS()('0111.2.3.4') 
3650      ('0111.2.3.4', 'enter valid IP address') 
3651      >>> IS_IPADDRESS()('256.2.3.4') 
3652      ('256.2.3.4', 'enter valid IP address') 
3653      >>> IS_IPADDRESS()('300.2.3.4') 
3654      ('300.2.3.4', 'enter valid IP address') 
3655      >>> IS_IPADDRESS(minip='192.168.1.0', maxip='192.168.1.255')('192.168.1.100') 
3656      ('192.168.1.100', None) 
3657      >>> IS_IPADDRESS(minip='1.2.3.5', maxip='1.2.3.9', error_message='bad ip')('1.2.3.4') 
3658      ('1.2.3.4', 'bad ip') 
3659      >>> IS_IPADDRESS(maxip='1.2.3.4', invert=True)('127.0.0.1') 
3660      ('127.0.0.1', None) 
3661      >>> IS_IPADDRESS(maxip='192.168.1.4', invert=True)('192.168.1.4') 
3662      ('192.168.1.4', 'enter valid IP address') 
3663      >>> IS_IPADDRESS(is_localhost=True)('127.0.0.1') 
3664      ('127.0.0.1', None) 
3665      >>> IS_IPADDRESS(is_localhost=True)('192.168.1.10') 
3666      ('192.168.1.10', 'enter valid IP address') 
3667      >>> IS_IPADDRESS(is_localhost=False)('127.0.0.1') 
3668      ('127.0.0.1', 'enter valid IP address') 
3669      >>> IS_IPADDRESS(maxip='100.0.0.0', is_localhost=True)('127.0.0.1') 
3670      ('127.0.0.1', 'enter valid IP address') 
3671   
3672      >>> IS_IPADDRESS()('fe80::126c:8ffa:fe22:b3af') 
3673      ('fe80::126c:8ffa:fe22:b3af', None) 
3674      >>> IS_IPADDRESS(is_ipv4=False)('fe80::126c:8ffa:fe22:b3af') 
3675      ('fe80::126c:8ffa:fe22:b3af', None) 
3676      >>> IS_IPADDRESS()('fe80::126c:8ffa:fe22:b3af  ') 
3677      ('fe80::126c:8ffa:fe22:b3af  ', 'enter valid IP address') 
3678      >>> IS_IPADDRESS(is_ipv4=True)('fe80::126c:8ffa:fe22:b3af') 
3679      ('fe80::126c:8ffa:fe22:b3af', 'enter valid IP address') 
3680      >>> IS_IPADDRESS(is_ipv6=True)('192.168.1.1') 
3681      ('192.168.1.1', 'enter valid IP address') 
3682      >>> IS_IPADDRESS(is_ipv6=True, error_message='bad ip')('192.168.1.1') 
3683      ('192.168.1.1', 'bad ip') 
3684      >>> IS_IPADDRESS(is_link_local=True)('fe80::126c:8ffa:fe22:b3af') 
3685      ('fe80::126c:8ffa:fe22:b3af', None) 
3686      >>> IS_IPADDRESS(is_link_local=False)('fe80::126c:8ffa:fe22:b3af') 
3687      ('fe80::126c:8ffa:fe22:b3af', 'enter valid IP address') 
3688      >>> IS_IPADDRESS(is_link_local=True)('2001::126c:8ffa:fe22:b3af') 
3689      ('2001::126c:8ffa:fe22:b3af', 'enter valid IP address') 
3690      >>> IS_IPADDRESS(is_multicast=True)('2001::126c:8ffa:fe22:b3af') 
3691      ('2001::126c:8ffa:fe22:b3af', 'enter valid IP address') 
3692      >>> IS_IPADDRESS(is_multicast=True)('ff00::126c:8ffa:fe22:b3af') 
3693      ('ff00::126c:8ffa:fe22:b3af', None) 
3694      >>> IS_IPADDRESS(is_routeable=True)('2001::126c:8ffa:fe22:b3af') 
3695      ('2001::126c:8ffa:fe22:b3af', None) 
3696      >>> IS_IPADDRESS(is_routeable=True)('ff00::126c:8ffa:fe22:b3af') 
3697      ('ff00::126c:8ffa:fe22:b3af', 'enter valid IP address') 
3698      >>> IS_IPADDRESS(subnets='2001::/32')('2001::8ffa:fe22:b3af') 
3699      ('2001::8ffa:fe22:b3af', None) 
3700      >>> IS_IPADDRESS(subnets='fb00::/8')('2001::8ffa:fe22:b3af') 
3701      ('2001::8ffa:fe22:b3af', 'enter valid IP address') 
3702      >>> IS_IPADDRESS(subnets=['fc00::/8','2001::/32'])('2001::8ffa:fe22:b3af') 
3703      ('2001::8ffa:fe22:b3af', None) 
3704      >>> IS_IPADDRESS(subnets='invalidsubnet')('2001::8ffa:fe22:b3af') 
3705      ('2001::8ffa:fe22:b3af', 'invalid subnet provided') 
3706      """ 
3707 -    def __init__( 
3708              self, 
3709              minip='0.0.0.0', 
3710              maxip='255.255.255.255', 
3711              invert=False, 
3712              is_localhost=None, 
3713              is_private=None, 
3714              is_automatic=None, 
3715              is_ipv4=None, 
3716              is_link_local=None, 
3717              is_reserved=None, 
3718              is_multicast=None, 
3719              is_routeable=None, 
3720              is_6to4=None, 
3721              is_teredo=None, 
3722              subnets=None, 
3723              is_ipv6=None, 
3724              error_message='enter valid IP address'): 
 3725          self.minip = minip, 
3726          self.maxip = maxip, 
3727          self.invert = invert 
3728          self.is_localhost = is_localhost 
3729          self.is_private = is_private 
3730          self.is_automatic = is_automatic 
3731          self.is_ipv4 = is_ipv4 
3732          self.is_private = is_private 
3733          self.is_link_local = is_link_local 
3734          self.is_reserved = is_reserved 
3735          self.is_multicast = is_multicast 
3736          self.is_routeable = is_routeable 
3737          self.is_6to4 = is_6to4 
3738          self.is_teredo = is_teredo 
3739          self.subnets = subnets 
3740          self.is_ipv6 = is_ipv6 
3741          self.error_message = error_message 
 3742   
3744          try: 
3745              import ipaddress 
3746          except ImportError: 
3747              from contrib import ipaddr as ipaddress 
3748   
3749          try: 
3750              ip = ipaddress.ip_address(value) 
3751          except ValueError, e: 
3752              return (value, translate(self.error_message)) 
3753   
3754          if self.is_ipv4 and isinstance(ip, ipaddress.IPv6Address): 
3755              retval = (value, translate(self.error_message)) 
3756          elif self.is_ipv6 and isinstance(ip, ipaddress.IPv4Address): 
3757              retval = (value, translate(self.error_message)) 
3758          elif self.is_ipv4 or isinstance(ip, ipaddress.IPv4Address): 
3759              retval = IS_IPV4( 
3760                  minip=self.minip, 
3761                  maxip=self.maxip, 
3762                  invert=self.invert, 
3763                  is_localhost=self.is_localhost, 
3764                  is_private=self.is_private, 
3765                  is_automatic=self.is_automatic, 
3766                  error_message=self.error_message 
3767                  )(value) 
3768          elif self.is_ipv6 or isinstance(ip, ipaddress.IPv6Address): 
3769              retval = IS_IPV6( 
3770                  is_private=self.is_private, 
3771                  is_link_local=self.is_link_local, 
3772                  is_reserved=self.is_reserved, 
3773                  is_multicast=self.is_multicast, 
3774                  is_routeable=self.is_routeable, 
3775                  is_6to4=self.is_6to4, 
3776                  is_teredo=self.is_teredo, 
3777                  subnets=self.subnets, 
3778                  error_message=self.error_message 
3779                  )(value) 
3780          else: 
3781              retval = (value, translate(self.error_message)) 
3782   
3783          return retval 
  3784   
3785   
3786  if __name__ == '__main__': 
3787      import doctest 
3788      doctest.testmod( 
3789          optionflags=doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS) 
3790