PageRenderTime 87ms CodeModel.GetById 31ms RepoModel.GetById 0ms app.codeStats 1ms

/gluon/validators.py

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