PageRenderTime 67ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/gluon/validators.py

https://bitbucket.org/schlamar/gbottle-wiki
Python | 2725 lines | 2572 code | 50 blank | 103 comment | 69 complexity | 33fe3f52c5de55e3b9406255231bbce6 MD5 | raw file
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. """
  4. This file is part of web2py Web Framework (Copyrighted, 2007-2010).
  5. Developed by Massimo Di Pierro <mdipierro@cs.depaul.edu>.
  6. License: GPL v2
  7. Thanks to ga2arch for help with IS_IN_DB and IS_NOT_IN_DB on GAE
  8. """
  9. import os
  10. import re
  11. import datetime
  12. import time
  13. import cgi
  14. import hmac
  15. import urllib
  16. import struct
  17. import decimal
  18. import unicodedata
  19. from cStringIO import StringIO
  20. from utils import hash, get_digest
  21. __all__ = [
  22. 'CLEANUP',
  23. 'CRYPT',
  24. 'IS_ALPHANUMERIC',
  25. 'IS_DATE_IN_RANGE',
  26. 'IS_DATE',
  27. 'IS_DATETIME_IN_RANGE',
  28. 'IS_DATETIME',
  29. 'IS_DECIMAL_IN_RANGE',
  30. 'IS_EMAIL',
  31. 'IS_EMPTY_OR',
  32. 'IS_EXPR',
  33. 'IS_FLOAT_IN_RANGE',
  34. 'IS_IMAGE',
  35. 'IS_IN_DB',
  36. 'IS_IN_SET',
  37. 'IS_INT_IN_RANGE',
  38. 'IS_IPV4',
  39. 'IS_LENGTH',
  40. 'IS_LIST_OF',
  41. 'IS_LOWER',
  42. 'IS_MATCH',
  43. 'IS_NOT_EMPTY',
  44. 'IS_NOT_IN_DB',
  45. 'IS_NULL_OR',
  46. 'IS_SLUG',
  47. 'IS_STRONG',
  48. 'IS_TIME',
  49. 'IS_UPLOAD_FILENAME',
  50. 'IS_UPPER',
  51. 'IS_URL',
  52. ]
  53. def options_sorter(x,y):
  54. return (str(x[1]).upper()>str(y[1]).upper() and 1) or -1
  55. class Validator(object):
  56. """
  57. Root for all validators, mainly for documentation purposes.
  58. Validators are classes used to validate input fields (including forms
  59. generated from database tables).
  60. Here is an example of using a validator with a FORM::
  61. INPUT(_name='a', requires=IS_INT_IN_RANGE(0, 10))
  62. Here is an example of how to require a validator for a table field::
  63. db.define_table('person', SQLField('name'))
  64. db.person.name.requires=IS_NOT_EMPTY()
  65. Validators are always assigned using the requires attribute of a field. A
  66. field can have a single validator or multiple validators. Multiple
  67. validators are made part of a list::
  68. db.person.name.requires=[IS_NOT_EMPTY(), IS_NOT_IN_DB(db, 'person.id')]
  69. Validators are called by the function accepts on a FORM or other HTML
  70. helper object that contains a form. They are always called in the order in
  71. which they are listed.
  72. Built-in validators have constructors that take the optional argument error
  73. message which allows you to change the default error message.
  74. Here is an example of a validator on a database table::
  75. db.person.name.requires=IS_NOT_EMPTY(error_message=T('fill this'))
  76. where we have used the translation operator T to allow for
  77. internationalization.
  78. Notice that default error messages are not translated.
  79. """
  80. def formatter(self, value):
  81. """
  82. For some validators returns a formatted version (matching the validator)
  83. of value. Otherwise just returns the value.
  84. """
  85. return value
  86. class IS_MATCH(Validator):
  87. """
  88. example::
  89. INPUT(_type='text', _name='name', requires=IS_MATCH('.+'))
  90. the argument of IS_MATCH is a regular expression::
  91. >>> IS_MATCH('.+')('hello')
  92. ('hello', None)
  93. >>> IS_MATCH('.+')('')
  94. ('', 'invalid expression')
  95. """
  96. def __init__(self, expression, error_message='invalid expression'):
  97. self.regex = re.compile(expression)
  98. self.error_message = error_message
  99. def __call__(self, value):
  100. match = self.regex.match(value)
  101. if match:
  102. return (match.group(), None)
  103. return (value, self.error_message)
  104. class IS_EXPR(Validator):
  105. """
  106. example::
  107. INPUT(_type='text', _name='name',
  108. requires=IS_EXPR('5 < int(value) < 10'))
  109. the argument of IS_EXPR must be python condition::
  110. >>> IS_EXPR('int(value) < 2')('1')
  111. ('1', None)
  112. >>> IS_EXPR('int(value) < 2')('2')
  113. ('2', 'invalid expression')
  114. """
  115. def __init__(self, expression, error_message='invalid expression'):
  116. self.expression = expression
  117. self.error_message = error_message
  118. def __call__(self, value):
  119. environment = {'value': value}
  120. exec '__ret__=' + self.expression in environment
  121. if environment['__ret__']:
  122. return (value, None)
  123. return (value, self.error_message)
  124. class IS_LENGTH(Validator):
  125. """
  126. Checks if length of field's value fits between given boundaries. Works
  127. for both text and file inputs.
  128. Arguments:
  129. maxsize: maximum allowed length / size
  130. minsize: minimum allowed length / size
  131. Examples::
  132. #Check if text string is shorter than 33 characters:
  133. INPUT(_type='text', _name='name', requires=IS_LENGTH(32))
  134. #Check if password string is longer than 5 characters:
  135. INPUT(_type='password', _name='name', requires=IS_LENGTH(minsize=6))
  136. #Check if uploaded file has size between 1KB and 1MB:
  137. INPUT(_type='file', _name='name', requires=IS_LENGTH(1048576, 1024))
  138. >>> IS_LENGTH()('')
  139. ('', None)
  140. >>> IS_LENGTH()('1234567890')
  141. ('1234567890', None)
  142. >>> IS_LENGTH(maxsize=5, minsize=0)('1234567890') # too long
  143. ('1234567890', 'enter from 0 to 5 characters')
  144. >>> IS_LENGTH(maxsize=50, minsize=20)('1234567890') # too short
  145. ('1234567890', 'enter from 20 to 50 characters')
  146. """
  147. def __init__(self, maxsize=255, minsize=0, error_message='enter from %(min)s to %(max)s characters'):
  148. self.maxsize = maxsize
  149. self.minsize = minsize
  150. self.error_message = error_message % dict(min=minsize, max=maxsize)
  151. def __call__(self, value):
  152. if isinstance(value, cgi.FieldStorage):
  153. if value.file:
  154. value.file.seek(0, os.SEEK_END)
  155. length = value.file.tell()
  156. value.file.seek(0, os.SEEK_SET)
  157. else:
  158. val = value.value
  159. if val:
  160. length = len(val)
  161. else:
  162. length = 0
  163. if self.minsize <= length <= self.maxsize:
  164. return (value, None)
  165. elif isinstance(value, (str, unicode, list)):
  166. if self.minsize <= len(value) <= self.maxsize:
  167. return (value, None)
  168. elif self.minsize <= len(str(value)) <= self.maxsize:
  169. try:
  170. value.decode('utf8')
  171. return (value, None)
  172. except:
  173. pass
  174. return (value, self.error_message)
  175. class IS_IN_SET(Validator):
  176. """
  177. example::
  178. INPUT(_type='text', _name='name',
  179. requires=IS_IN_SET(['max', 'john'],zero=''))
  180. the argument of IS_IN_SET must be a list or set
  181. >>> IS_IN_SET(['max', 'john'])('max')
  182. ('max', None)
  183. >>> IS_IN_SET(['max', 'john'])('massimo')
  184. ('massimo', 'value not allowed')
  185. >>> IS_IN_SET(['max', 'john'], multiple=True)(('max', 'john'))
  186. ('|max|john|', None)
  187. >>> IS_IN_SET(['max', 'john'], multiple=True)(('bill', 'john'))
  188. (('bill', 'john'), 'value not allowed')
  189. >>> IS_IN_SET(('id1','id2'), ['first label','second label'])('id1') # Traditional way
  190. ('id1', None)
  191. >>> IS_IN_SET({'id1':'first label', 'id2':'second label'})('id1')
  192. ('id1', None)
  193. >>> import itertools
  194. >>> IS_IN_SET(itertools.chain(['1','3','5'],['2','4','6']))('1')
  195. ('1', None)
  196. >>> IS_IN_SET([('id1','id2'), ('first label','second label')])('id1') # Redundant way
  197. ('id1', None)
  198. """
  199. def __init__(
  200. self,
  201. theset,
  202. labels=None,
  203. error_message='value not allowed',
  204. multiple=False,
  205. zero='',
  206. sort=False,
  207. ):
  208. self.multiple = multiple
  209. if isinstance(theset, dict):
  210. self.theset = [str(item) for item in theset]
  211. self.labels = theset.values()
  212. elif theset and isinstance(theset, (tuple,list)) \
  213. and isinstance(theset[0], (tuple,list)) and len(theset[0])==2:
  214. self.theset = [str(item) for item,label in theset]
  215. self.labels = [str(label) for item,label in theset]
  216. else:
  217. self.theset = [str(item) for item in theset]
  218. self.labels = labels
  219. self.error_message = error_message
  220. self.zero = zero
  221. self.sort = sort
  222. def options(self):
  223. if not self.labels:
  224. items = [(k, k) for (i, k) in enumerate(self.theset)]
  225. else:
  226. items = [(k, self.labels[i]) for (i, k) in enumerate(self.theset)]
  227. if self.sort:
  228. items.sort(options_sorter)
  229. if self.zero != None and not self.multiple:
  230. items.insert(0,('',self.zero))
  231. return items
  232. def __call__(self, value):
  233. if self.multiple:
  234. values = re.compile("[\w\-:]+").findall(str(value))
  235. else:
  236. values = [value]
  237. failures = [x for x in values if not x in self.theset]
  238. if failures:
  239. if self.multiple and value == None:
  240. return (value, None)
  241. return (value, self.error_message)
  242. if self.multiple:
  243. return ('|%s|' % '|'.join(values), None)
  244. return (value, None)
  245. regex1 = re.compile('[\w_]+\.[\w_]+')
  246. regex2 = re.compile('%\((?P<name>[^\)]+)\)s')
  247. class IS_IN_DB(Validator):
  248. """
  249. example::
  250. INPUT(_type='text', _name='name',
  251. requires=IS_IN_DB(db, db.table, zero=''))
  252. used for reference fields, rendered as a dropbox
  253. """
  254. def __init__(
  255. self,
  256. dbset,
  257. field,
  258. label=None,
  259. error_message='value not in database',
  260. orderby=None,
  261. cache=None,
  262. multiple=False,
  263. zero='',
  264. sort=False,
  265. _and=None,
  266. ):
  267. if hasattr(dbset, 'define_table'):
  268. self.dbset = dbset()
  269. else:
  270. self.dbset = dbset
  271. self.field = field
  272. (ktable, kfield) = str(self.field).split('.')
  273. if not label:
  274. label = '%%(%s)s' % kfield
  275. if isinstance(label,str):
  276. if regex1.match(str(label)):
  277. label = '%%(%s)s' % str(label).split('.')[-1]
  278. ks = regex2.findall(label)
  279. if not kfield in ks:
  280. ks += [kfield]
  281. fields = ['%s.%s' % (ktable, k) for k in ks]
  282. else:
  283. ks = [kfield]
  284. fields =[str(f) for f in self.dbset._db[ktable]]
  285. self.fields = fields
  286. self.label = label
  287. self.ktable = ktable
  288. self.kfield = kfield
  289. self.ks = ks
  290. self.error_message = error_message
  291. self.theset = None
  292. self.orderby = orderby
  293. self.cache = cache
  294. self.multiple = multiple
  295. self.zero = zero
  296. self.sort = sort
  297. self._and = _and
  298. def set_self_id(self, id):
  299. if self._and:
  300. self._and.record_id = id
  301. def build_set(self):
  302. if self.dbset._db._dbname != 'gql':
  303. orderby = self.orderby or ', '.join(self.fields)
  304. dd = dict(orderby=orderby, cache=self.cache)
  305. records = self.dbset.select(*self.fields, **dd)
  306. else:
  307. import contrib.gql
  308. orderby = self.orderby\
  309. or contrib.gql.SQLXorable('|'.join([k for k in self.ks
  310. if k != 'id']))
  311. dd = dict(orderby=orderby, cache=self.cache)
  312. records = \
  313. self.dbset.select(self.dbset._db[self.ktable].ALL, **dd)
  314. self.theset = [str(r[self.kfield]) for r in records]
  315. if isinstance(self.label,str):
  316. self.labels = [self.label % dict(r) for r in records]
  317. else:
  318. self.labels = [self.label(r) for r in records]
  319. def options(self):
  320. self.build_set()
  321. items = [(k, self.labels[i]) for (i, k) in enumerate(self.theset)]
  322. if self.sort:
  323. items.sort(options_sorter)
  324. if self.zero != None and not self.multiple:
  325. items.insert(0,('',self.zero))
  326. return items
  327. def __call__(self, value):
  328. if self.multiple:
  329. values = re.compile("[\w\-:]+").findall(str(value))
  330. if not [x for x in values if not x in self.theset]:
  331. return ('|%s|' % '|'.join(values), None)
  332. elif self.theset:
  333. if value in self.theset:
  334. if self._and:
  335. return self._and(value)
  336. else:
  337. return (value, None)
  338. else:
  339. (ktable, kfield) = str(self.field).split('.')
  340. field = self.dbset._db[ktable][kfield]
  341. if self.dbset(field == value).count():
  342. if self._and:
  343. return self._and(value)
  344. else:
  345. return (value, None)
  346. return (value, self.error_message)
  347. class IS_NOT_IN_DB(Validator):
  348. """
  349. example::
  350. INPUT(_type='text', _name='name', requires=IS_NOT_IN_DB(db, db.table))
  351. makes the field unique
  352. """
  353. def __init__(
  354. self,
  355. dbset,
  356. field,
  357. error_message='value already in database',
  358. allowed_override=[],
  359. ):
  360. if hasattr(dbset, 'define_table'):
  361. self.dbset = dbset()
  362. else:
  363. self.dbset = dbset
  364. self.field = field
  365. self.error_message = error_message
  366. self.record_id = 0
  367. self.allowed_override = allowed_override
  368. def set_self_id(self, id):
  369. self.record_id = id
  370. def __call__(self, value):
  371. if value in self.allowed_override:
  372. return (value, None)
  373. (tablename, fieldname) = str(self.field).split('.')
  374. field = self.dbset._db[tablename][fieldname]
  375. rows = self.dbset(field == value).select(limitby=(0, 1))
  376. if len(rows) > 0:
  377. if isinstance(self.record_id, dict):
  378. for f in self.record_id:
  379. if str(getattr(rows[0], f)) != str(self.record_id[f]):
  380. return (value, self.error_message)
  381. elif str(rows[0].id) != str(self.record_id):
  382. return (value, self.error_message)
  383. return (value, None)
  384. class IS_INT_IN_RANGE(Validator):
  385. """
  386. Determine that the argument is (or can be represented as) an int,
  387. and that it falls within the specified range. The range is interpreted
  388. in the Pythonic way, so the test is: min <= value < max.
  389. The minimum and maximum limits can be None, meaning no lower or upper limit,
  390. respectively.
  391. example::
  392. INPUT(_type='text', _name='name', requires=IS_INT_IN_RANGE(0, 10))
  393. >>> IS_INT_IN_RANGE(1,5)('4')
  394. (4, None)
  395. >>> IS_INT_IN_RANGE(1,5)(4)
  396. (4, None)
  397. >>> IS_INT_IN_RANGE(1,5)(1)
  398. (1, None)
  399. >>> IS_INT_IN_RANGE(1,5)(5)
  400. (5, 'enter an integer between 1 and 4')
  401. >>> IS_INT_IN_RANGE(1,5)(5)
  402. (5, 'enter an integer between 1 and 4')
  403. >>> IS_INT_IN_RANGE(1,5)(3.5)
  404. (3, 'enter an integer between 1 and 4')
  405. >>> IS_INT_IN_RANGE(None,5)('4')
  406. (4, None)
  407. >>> IS_INT_IN_RANGE(None,5)('6')
  408. (6, 'enter an integer less than or equal to 4')
  409. >>> IS_INT_IN_RANGE(1,None)('4')
  410. (4, None)
  411. >>> IS_INT_IN_RANGE(1,None)('0')
  412. (0, 'enter an integer greater than or equal to 1')
  413. """
  414. def __init__(
  415. self,
  416. minimum,
  417. maximum,
  418. error_message = None,
  419. ):
  420. if minimum is None:
  421. self.minimum = None
  422. self.maximum = int(maximum)
  423. if error_message is None:
  424. error_message = 'enter an integer less than or equal to %(max)s'
  425. self.error_message = error_message % dict(max=self.maximum-1)
  426. elif maximum is None:
  427. self.minimum = int(minimum)
  428. self.maximum = None
  429. if error_message is None:
  430. error_message = 'enter an integer greater than or equal to %(min)s'
  431. self.error_message = error_message % dict(min=self.minimum)
  432. else:
  433. self.minimum = int(minimum)
  434. self.maximum = int(maximum)
  435. if error_message is None:
  436. error_message = 'enter an integer between %(min)s and %(max)s'
  437. self.error_message = error_message % dict(min=self.minimum, max=self.maximum-1)
  438. def __call__(self, value):
  439. try:
  440. fvalue = float(value)
  441. value = int(value)
  442. if value != fvalue:
  443. return (value, self.error_message)
  444. if self.minimum is None:
  445. if value < self.maximum:
  446. return (value, None)
  447. elif self.maximum is None:
  448. if value >= self.minimum:
  449. return (value, None)
  450. elif self.minimum <= value < self.maximum:
  451. return (value, None)
  452. except ValueError:
  453. pass
  454. return (value, self.error_message)
  455. class IS_FLOAT_IN_RANGE(Validator):
  456. """
  457. Determine that the argument is (or can be represented as) a float,
  458. and that it falls within the specified inclusive range.
  459. The comparison is made with native arithmetic.
  460. The minimum and maximum limits can be None, meaning no lower or upper limit,
  461. respectively.
  462. example::
  463. INPUT(_type='text', _name='name', requires=IS_FLOAT_IN_RANGE(0, 10))
  464. >>> IS_FLOAT_IN_RANGE(1,5)('4')
  465. (4.0, None)
  466. >>> IS_FLOAT_IN_RANGE(1,5)(4)
  467. (4.0, None)
  468. >>> IS_FLOAT_IN_RANGE(1,5)(1)
  469. (1.0, None)
  470. >>> IS_FLOAT_IN_RANGE(1,5)(5.1)
  471. (5.0999999999999996, 'enter a number between 1.0 and 5.0')
  472. >>> IS_FLOAT_IN_RANGE(1,5)(6.0)
  473. (6.0, 'enter a number between 1.0 and 5.0')
  474. >>> IS_FLOAT_IN_RANGE(1,5)(3.5)
  475. (3.5, None)
  476. >>> IS_FLOAT_IN_RANGE(1,None)(3.5)
  477. (3.5, None)
  478. >>> IS_FLOAT_IN_RANGE(None,5)(3.5)
  479. (3.5, None)
  480. >>> IS_FLOAT_IN_RANGE(1,None)(0.5)
  481. (0.5, 'enter a number greater than or equal to 1.0')
  482. >>> IS_FLOAT_IN_RANGE(None,5)(6.5)
  483. (6.5, 'enter a number less than or equal to 5.0')
  484. """
  485. def __init__(
  486. self,
  487. minimum,
  488. maximum,
  489. error_message = None,
  490. ):
  491. if minimum is None:
  492. self.minimum = None
  493. self.maximum = float(maximum)
  494. if error_message is None:
  495. error_message = 'enter a number less than or equal to %(max)s'
  496. elif maximum is None:
  497. self.minimum = float(minimum)
  498. self.maximum = None
  499. if error_message is None:
  500. error_message = 'enter a number greater than or equal to %(min)s'
  501. else:
  502. self.minimum = float(minimum)
  503. self.maximum = float(maximum)
  504. if error_message is None:
  505. error_message = 'enter a number between %(min)s and %(max)s'
  506. self.error_message = error_message % dict(min=self.minimum, max=self.maximum)
  507. def __call__(self, value):
  508. try:
  509. value = float(value)
  510. if self.minimum is None:
  511. if value <= self.maximum:
  512. return (value, None)
  513. elif self.maximum is None:
  514. if value >= self.minimum:
  515. return (value, None)
  516. elif self.minimum <= value <= self.maximum:
  517. return (value, None)
  518. except (ValueError, TypeError):
  519. pass
  520. return (value, self.error_message)
  521. class IS_DECIMAL_IN_RANGE(Validator):
  522. """
  523. Determine that the argument is (or can be represented as) a Python Decimal,
  524. and that it falls within the specified inclusive range.
  525. The comparison is made with Python Decimal arithmetic.
  526. The minimum and maximum limits can be None, meaning no lower or upper limit,
  527. respectively.
  528. example::
  529. INPUT(_type='text', _name='name', requires=IS_DECIMAL_IN_RANGE(0, 10))
  530. >>> IS_DECIMAL_IN_RANGE(1,5)('4')
  531. ('4', None)
  532. >>> IS_DECIMAL_IN_RANGE(1,5)(4)
  533. (4, None)
  534. >>> IS_DECIMAL_IN_RANGE(1,5)(1)
  535. (1, None)
  536. >>> IS_DECIMAL_IN_RANGE(1,5)(5.1)
  537. (5.0999999999999996, 'enter a number between 1 and 5')
  538. >>> IS_DECIMAL_IN_RANGE(5.1,6)(5.1)
  539. (5.0999999999999996, None)
  540. >>> IS_DECIMAL_IN_RANGE(5.1,6)('5.1')
  541. ('5.1', None)
  542. >>> IS_DECIMAL_IN_RANGE(1,5)(6.0)
  543. (6.0, 'enter a number between 1 and 5')
  544. >>> IS_DECIMAL_IN_RANGE(1,5)(3.5)
  545. (3.5, None)
  546. >>> IS_DECIMAL_IN_RANGE(1.5,5.5)(3.5)
  547. (3.5, None)
  548. >>> IS_DECIMAL_IN_RANGE(1.5,5.5)(6.5)
  549. (6.5, 'enter a number between 1.5 and 5.5')
  550. >>> IS_DECIMAL_IN_RANGE(1.5,None)(6.5)
  551. (6.5, None)
  552. >>> IS_DECIMAL_IN_RANGE(1.5,None)(0.5)
  553. (0.5, 'enter a number greater than or equal to 1.5')
  554. >>> IS_DECIMAL_IN_RANGE(None,5.5)(4.5)
  555. (4.5, None)
  556. >>> IS_DECIMAL_IN_RANGE(None,5.5)(6.5)
  557. (6.5, 'enter a number less than or equal to 5.5')
  558. """
  559. def __init__(
  560. self,
  561. minimum,
  562. maximum,
  563. error_message = None,
  564. ):
  565. if minimum is None:
  566. self.minimum = None
  567. self.maximum = decimal.Decimal(str(maximum))
  568. if error_message is None:
  569. error_message = 'enter a number less than or equal to %(max)s'
  570. elif maximum is None:
  571. self.minimum = decimal.Decimal(str(minimum))
  572. self.maximum = None
  573. if error_message is None:
  574. error_message = 'enter a number greater than or equal to %(min)s'
  575. else:
  576. self.minimum = decimal.Decimal(str(minimum))
  577. self.maximum = decimal.Decimal(str(maximum))
  578. if error_message is None:
  579. error_message = 'enter a number between %(min)s and %(max)s'
  580. self.error_message = error_message % dict(min=self.minimum, max=self.maximum)
  581. def __call__(self, value):
  582. try:
  583. v = decimal.Decimal(str(value))
  584. if self.minimum is None:
  585. if v <= self.maximum:
  586. return (value, None)
  587. elif self.maximum is None:
  588. if v >= self.minimum:
  589. return (value, None)
  590. elif self.minimum <= v <= self.maximum:
  591. return (value, None)
  592. except (ValueError, TypeError):
  593. pass
  594. return (value, self.error_message)
  595. def is_empty(value, empty_regex=None):
  596. "test empty field"
  597. if isinstance(value, (str, unicode)):
  598. value = value.strip()
  599. if empty_regex is not None and empty_regex.match(value):
  600. value = ''
  601. if value == None or value == '' or value == []:
  602. return (value, True)
  603. return (value, False)
  604. class IS_NOT_EMPTY(Validator):
  605. """
  606. example::
  607. INPUT(_type='text', _name='name', requires=IS_NOT_EMPTY())
  608. >>> IS_NOT_EMPTY()(1)
  609. (1, None)
  610. >>> IS_NOT_EMPTY()(0)
  611. (0, None)
  612. >>> IS_NOT_EMPTY()('x')
  613. ('x', None)
  614. >>> IS_NOT_EMPTY()(' x ')
  615. ('x', None)
  616. >>> IS_NOT_EMPTY()(None)
  617. (None, 'enter a value')
  618. >>> IS_NOT_EMPTY()('')
  619. ('', 'enter a value')
  620. >>> IS_NOT_EMPTY()(' ')
  621. ('', 'enter a value')
  622. >>> IS_NOT_EMPTY()(' \\n\\t')
  623. ('', 'enter a value')
  624. >>> IS_NOT_EMPTY()([])
  625. ([], 'enter a value')
  626. >>> IS_NOT_EMPTY(empty_regex='def')('def')
  627. ('', 'enter a value')
  628. >>> IS_NOT_EMPTY(empty_regex='de[fg]')('deg')
  629. ('', 'enter a value')
  630. >>> IS_NOT_EMPTY(empty_regex='def')('abc')
  631. ('abc', None)
  632. """
  633. def __init__(self, error_message='enter a value', empty_regex=None):
  634. self.error_message = error_message
  635. if empty_regex is not None:
  636. self.empty_regex = re.compile(empty_regex)
  637. else:
  638. self.empty_regex = None
  639. def __call__(self, value):
  640. value, empty = is_empty(value, empty_regex=self.empty_regex)
  641. if empty:
  642. return (value, self.error_message)
  643. return (value, None)
  644. class IS_ALPHANUMERIC(IS_MATCH):
  645. """
  646. example::
  647. INPUT(_type='text', _name='name', requires=IS_ALPHANUMERIC())
  648. >>> IS_ALPHANUMERIC()('1')
  649. ('1', None)
  650. >>> IS_ALPHANUMERIC()('')
  651. ('', None)
  652. >>> IS_ALPHANUMERIC()('A_a')
  653. ('A_a', None)
  654. >>> IS_ALPHANUMERIC()('!')
  655. ('!', 'enter only letters, numbers, and underscore')
  656. """
  657. def __init__(self, error_message='enter only letters, numbers, and underscore'):
  658. IS_MATCH.__init__(self, '^[\w]*$', error_message)
  659. class IS_EMAIL(Validator):
  660. """
  661. Checks if field's value is a valid email address. Can be set to disallow
  662. or force addresses from certain domain(s).
  663. Email regex adapted from
  664. http://haacked.com/archive/2007/08/21/i-knew-how-to-validate-an-email-address-until-i.aspx,
  665. generally following the RFCs, except that we disallow quoted strings
  666. and permit underscores and leading numerics in subdomain labels
  667. Arguments:
  668. - banned: regex text for disallowed address domains
  669. - forced: regex text for required address domains
  670. Both arguments can also be custom objects with a match(value) method.
  671. Examples::
  672. #Check for valid email address:
  673. INPUT(_type='text', _name='name',
  674. requires=IS_EMAIL())
  675. #Check for valid email address that can't be from a .com domain:
  676. INPUT(_type='text', _name='name',
  677. requires=IS_EMAIL(banned='^.*\.com(|\..*)$'))
  678. #Check for valid email address that must be from a .edu domain:
  679. INPUT(_type='text', _name='name',
  680. requires=IS_EMAIL(forced='^.*\.edu(|\..*)$'))
  681. >>> IS_EMAIL()('a@b.com')
  682. ('a@b.com', None)
  683. >>> IS_EMAIL()('abc@def.com')
  684. ('abc@def.com', None)
  685. >>> IS_EMAIL()('abc@3def.com')
  686. ('abc@3def.com', None)
  687. >>> IS_EMAIL()('abc@def.us')
  688. ('abc@def.us', None)
  689. >>> IS_EMAIL()('abc@d_-f.us')
  690. ('abc@d_-f.us', None)
  691. >>> IS_EMAIL()('@def.com') # missing name
  692. ('@def.com', 'enter a valid email address')
  693. >>> IS_EMAIL()('"abc@def".com') # quoted name
  694. ('"abc@def".com', 'enter a valid email address')
  695. >>> IS_EMAIL()('abc+def.com') # no @
  696. ('abc+def.com', 'enter a valid email address')
  697. >>> IS_EMAIL()('abc@def.x') # one-char TLD
  698. ('abc@def.x', 'enter a valid email address')
  699. >>> IS_EMAIL()('abc@def.12') # numeric TLD
  700. ('abc@def.12', 'enter a valid email address')
  701. >>> IS_EMAIL()('abc@def..com') # double-dot in domain
  702. ('abc@def..com', 'enter a valid email address')
  703. >>> IS_EMAIL()('abc@.def.com') # dot starts domain
  704. ('abc@.def.com', 'enter a valid email address')
  705. >>> IS_EMAIL()('abc@def.c_m') # underscore in TLD
  706. ('abc@def.c_m', 'enter a valid email address')
  707. >>> IS_EMAIL()('NotAnEmail') # missing @
  708. ('NotAnEmail', 'enter a valid email address')
  709. >>> IS_EMAIL()('abc@NotAnEmail') # missing TLD
  710. ('abc@NotAnEmail', 'enter a valid email address')
  711. >>> IS_EMAIL()('customer/department@example.com')
  712. ('customer/department@example.com', None)
  713. >>> IS_EMAIL()('$A12345@example.com')
  714. ('$A12345@example.com', None)
  715. >>> IS_EMAIL()('!def!xyz%abc@example.com')
  716. ('!def!xyz%abc@example.com', None)
  717. >>> IS_EMAIL()('_Yosemite.Sam@example.com')
  718. ('_Yosemite.Sam@example.com', None)
  719. >>> IS_EMAIL()('~@example.com')
  720. ('~@example.com', None)
  721. >>> IS_EMAIL()('.wooly@example.com') # dot starts name
  722. ('.wooly@example.com', 'enter a valid email address')
  723. >>> IS_EMAIL()('wo..oly@example.com') # adjacent dots in name
  724. ('wo..oly@example.com', 'enter a valid email address')
  725. >>> IS_EMAIL()('pootietang.@example.com') # dot ends name
  726. ('pootietang.@example.com', 'enter a valid email address')
  727. >>> IS_EMAIL()('.@example.com') # name is bare dot
  728. ('.@example.com', 'enter a valid email address')
  729. >>> IS_EMAIL()('Ima.Fool@example.com')
  730. ('Ima.Fool@example.com', None)
  731. >>> IS_EMAIL()('Ima Fool@example.com') # space in name
  732. ('Ima Fool@example.com', 'enter a valid email address')
  733. """
  734. regex = re.compile('''
  735. ^(?!\.) # name may not begin with a dot
  736. (
  737. [-a-z0-9!\#$%&'*+/=?^_`{|}~] # all legal characters except dot
  738. |
  739. (?<!\.)\. # single dots only
  740. )+
  741. (?<!\.) # name may not end with a dot
  742. @
  743. (
  744. [a-z0-9] # [sub]domain begins with alphanumeric
  745. (
  746. [-\w]* # alphanumeric, underscore, dot, hyphen
  747. [a-z0-9] # ending alphanumeric
  748. )?
  749. \. # ending dot
  750. )+
  751. [a-z]{2,}$ # TLD alpha-only
  752. ''', re.VERBOSE|re.IGNORECASE)
  753. def __init__(self,
  754. banned=None,
  755. forced=None,
  756. error_message='enter a valid email address'):
  757. if isinstance(banned, str):
  758. banned = re.compile(banned)
  759. if isinstance(forced, str):
  760. forced = re.compile(forced)
  761. self.banned = banned
  762. self.forced = forced
  763. self.error_message = error_message
  764. def __call__(self, value):
  765. match = self.regex.match(value)
  766. if match:
  767. domain = value.split('@')[1]
  768. if (not self.banned or not self.banned.match(domain)) \
  769. and (not self.forced or self.forced.match(domain)):
  770. return (value, None)
  771. return (value, self.error_message)
  772. # URL scheme source:
  773. # <http://en.wikipedia.org/wiki/URI_scheme> obtained on 2008-Nov-10
  774. official_url_schemes = [
  775. 'aaa',
  776. 'aaas',
  777. 'acap',
  778. 'cap',
  779. 'cid',
  780. 'crid',
  781. 'data',
  782. 'dav',
  783. 'dict',
  784. 'dns',
  785. 'fax',
  786. 'file',
  787. 'ftp',
  788. 'go',
  789. 'gopher',
  790. 'h323',
  791. 'http',
  792. 'https',
  793. 'icap',
  794. 'im',
  795. 'imap',
  796. 'info',
  797. 'ipp',
  798. 'iris',
  799. 'iris.beep',
  800. 'iris.xpc',
  801. 'iris.xpcs',
  802. 'iris.lws',
  803. 'ldap',
  804. 'mailto',
  805. 'mid',
  806. 'modem',
  807. 'msrp',
  808. 'msrps',
  809. 'mtqp',
  810. 'mupdate',
  811. 'news',
  812. 'nfs',
  813. 'nntp',
  814. 'opaquelocktoken',
  815. 'pop',
  816. 'pres',
  817. 'prospero',
  818. 'rtsp',
  819. 'service',
  820. 'shttp',
  821. 'sip',
  822. 'sips',
  823. 'snmp',
  824. 'soap.beep',
  825. 'soap.beeps',
  826. 'tag',
  827. 'tel',
  828. 'telnet',
  829. 'tftp',
  830. 'thismessage',
  831. 'tip',
  832. 'tv',
  833. 'urn',
  834. 'vemmi',
  835. 'wais',
  836. 'xmlrpc.beep',
  837. 'xmlrpc.beep',
  838. 'xmpp',
  839. 'z39.50r',
  840. 'z39.50s',
  841. ]
  842. unofficial_url_schemes = [
  843. 'about',
  844. 'adiumxtra',
  845. 'aim',
  846. 'afp',
  847. 'aw',
  848. 'callto',
  849. 'chrome',
  850. 'cvs',
  851. 'ed2k',
  852. 'feed',
  853. 'fish',
  854. 'gg',
  855. 'gizmoproject',
  856. 'iax2',
  857. 'irc',
  858. 'ircs',
  859. 'itms',
  860. 'jar',
  861. 'javascript',
  862. 'keyparc',
  863. 'lastfm',
  864. 'ldaps',
  865. 'magnet',
  866. 'mms',
  867. 'msnim',
  868. 'mvn',
  869. 'notes',
  870. 'nsfw',
  871. 'psyc',
  872. 'paparazzi:http',
  873. 'rmi',
  874. 'rsync',
  875. 'secondlife',
  876. 'sgn',
  877. 'skype',
  878. 'ssh',
  879. 'sftp',
  880. 'smb',
  881. 'sms',
  882. 'soldat',
  883. 'steam',
  884. 'svn',
  885. 'teamspeak',
  886. 'unreal',
  887. 'ut2004',
  888. 'ventrilo',
  889. 'view-source',
  890. 'webcal',
  891. 'wyciwyg',
  892. 'xfire',
  893. 'xri',
  894. 'ymsgr',
  895. ]
  896. all_url_schemes = [None] + official_url_schemes + unofficial_url_schemes
  897. http_schemes = [None, 'http', 'https']
  898. # This regex comes from RFC 2396, Appendix B. It's used to split a URL into
  899. # its component parts
  900. # Here are the regex groups that it extracts:
  901. # scheme = group(2)
  902. # authority = group(4)
  903. # path = group(5)
  904. # query = group(7)
  905. # fragment = group(9)
  906. url_split_regex = \
  907. re.compile('^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?')
  908. # Defined in RFC 3490, Section 3.1, Requirement #1
  909. # Use this regex to split the authority component of a unicode URL into
  910. # its component labels
  911. label_split_regex = re.compile(u'[\u002e\u3002\uff0e\uff61]')
  912. def escape_unicode(string):
  913. '''
  914. Converts a unicode string into US-ASCII, using a simple conversion scheme.
  915. Each unicode character that does not have a US-ASCII equivalent is
  916. converted into a URL escaped form based on its hexadecimal value.
  917. For example, the unicode character '\u4e86' will become the string '%4e%86'
  918. :param string: unicode string, the unicode string to convert into an
  919. escaped US-ASCII form
  920. :returns: the US-ASCII escaped form of the inputted string
  921. :rtype: string
  922. @author: Jonathan Benn
  923. '''
  924. returnValue = StringIO()
  925. for character in string:
  926. code = ord(character)
  927. if code > 0x7F:
  928. hexCode = hex(code)
  929. returnValue.write('%' + hexCode[2:4] + '%' + hexCode[4:6])
  930. else:
  931. returnValue.write(character)
  932. return returnValue.getvalue()
  933. def unicode_to_ascii_authority(authority):
  934. '''
  935. Follows the steps in RFC 3490, Section 4 to convert a unicode authority
  936. string into its ASCII equivalent.
  937. For example, u'www.Alliancefran\xe7aise.nu' will be converted into
  938. 'www.xn--alliancefranaise-npb.nu'
  939. :param authority: unicode string, the URL authority component to convert,
  940. e.g. u'www.Alliancefran\xe7aise.nu'
  941. :returns: the US-ASCII character equivalent to the inputed authority,
  942. e.g. 'www.xn--alliancefranaise-npb.nu'
  943. :rtype: string
  944. :raises Exception: if the function is not able to convert the inputed
  945. authority
  946. @author: Jonathan Benn
  947. '''
  948. #RFC 3490, Section 4, Step 1
  949. #The encodings.idna Python module assumes that AllowUnassigned == True
  950. #RFC 3490, Section 4, Step 2
  951. labels = label_split_regex.split(authority)
  952. #RFC 3490, Section 4, Step 3
  953. #The encodings.idna Python module assumes that UseSTD3ASCIIRules == False
  954. #RFC 3490, Section 4, Step 4
  955. #We use the ToASCII operation because we are about to put the authority
  956. #into an IDN-unaware slot
  957. asciiLabels = []
  958. try:
  959. import encodings.idna
  960. for label in labels:
  961. if label:
  962. asciiLabels.append(encodings.idna.ToASCII(label))
  963. else:
  964. #encodings.idna.ToASCII does not accept an empty string, but
  965. #it is necessary for us to allow for empty labels so that we
  966. #don't modify the URL
  967. asciiLabels.append('')
  968. except:
  969. asciiLabels=[str(label) for label in labels]
  970. #RFC 3490, Section 4, Step 5
  971. return str(reduce(lambda x, y: x + unichr(0x002E) + y, asciiLabels))
  972. def unicode_to_ascii_url(url, prepend_scheme):
  973. '''
  974. Converts the inputed unicode url into a US-ASCII equivalent. This function
  975. goes a little beyond RFC 3490, which is limited in scope to the domain name
  976. (authority) only. Here, the functionality is expanded to what was observed
  977. on Wikipedia on 2009-Jan-22:
  978. Component Can Use Unicode?
  979. --------- ----------------
  980. scheme No
  981. authority Yes
  982. path Yes
  983. query Yes
  984. fragment No
  985. The authority component gets converted to punycode, but occurrences of
  986. unicode in other components get converted into a pair of URI escapes (we
  987. assume 4-byte unicode). E.g. the unicode character U+4E2D will be
  988. converted into '%4E%2D'. Testing with Firefox v3.0.5 has shown that it can
  989. understand this kind of URI encoding.
  990. :param url: unicode string, the URL to convert from unicode into US-ASCII
  991. :param prepend_scheme: string, a protocol scheme to prepend to the URL if
  992. we're having trouble parsing it.
  993. e.g. "http". Input None to disable this functionality
  994. :returns: a US-ASCII equivalent of the inputed url
  995. :rtype: string
  996. @author: Jonathan Benn
  997. '''
  998. #convert the authority component of the URL into an ASCII punycode string,
  999. #but encode the rest using the regular URI character encoding
  1000. groups = url_split_regex.match(url).groups()
  1001. #If no authority was found
  1002. if not groups[3]:
  1003. #Try appending a scheme to see if that fixes the problem
  1004. scheme_to_prepend = prepend_scheme or 'http'
  1005. groups = url_split_regex.match(
  1006. unicode(scheme_to_prepend) + u'://' + url).groups()
  1007. #if we still can't find the authority
  1008. if not groups[3]:
  1009. raise Exception('No authority component found, '+ \
  1010. 'could not decode unicode to US-ASCII')
  1011. #We're here if we found an authority, let's rebuild the URL
  1012. scheme = groups[1]
  1013. authority = groups[3]
  1014. path = groups[4] or ''
  1015. query = groups[5] or ''
  1016. fragment = groups[7] or ''
  1017. if prepend_scheme:
  1018. scheme = str(scheme) + '://'
  1019. else:
  1020. scheme = ''
  1021. return scheme + unicode_to_ascii_authority(authority) +\
  1022. escape_unicode(path) + escape_unicode(query) + str(fragment)
  1023. class IS_GENERIC_URL(Validator):
  1024. """
  1025. Rejects a URL string if any of the following is true:
  1026. * The string is empty or None
  1027. * The string uses characters that are not allowed in a URL
  1028. * The URL scheme specified (if one is specified) is not valid
  1029. Based on RFC 2396: http://www.faqs.org/rfcs/rfc2396.html
  1030. This function only checks the URL's syntax. It does not check that the URL
  1031. points to a real document, for example, or that it otherwise makes sense
  1032. semantically. This function does automatically prepend 'http://' in front
  1033. of a URL if and only if that's necessary to successfully parse the URL.
  1034. Please note that a scheme will be prepended only for rare cases
  1035. (e.g. 'google.ca:80')
  1036. The list of allowed schemes is customizable with the allowed_schemes
  1037. parameter. If you exclude None from the list, then abbreviated URLs
  1038. (lacking a scheme such as 'http') will be rejected.
  1039. The default prepended scheme is customizable with the prepend_scheme
  1040. parameter. If you set prepend_scheme to None then prepending will be
  1041. disabled. URLs that require prepending to parse will still be accepted,
  1042. but the return value will not be modified.
  1043. @author: Jonathan Benn
  1044. >>> IS_GENERIC_URL()('http://user@abc.com')
  1045. ('http://user@abc.com', None)
  1046. """
  1047. def __init__(
  1048. self,
  1049. error_message='enter a valid URL',
  1050. allowed_schemes=None,
  1051. prepend_scheme=None,
  1052. ):
  1053. """
  1054. :param error_message: a string, the error message to give the end user
  1055. if the URL does not validate
  1056. :param allowed_schemes: a list containing strings or None. Each element
  1057. is a scheme the inputed URL is allowed to use
  1058. :param prepend_scheme: a string, this scheme is prepended if it's
  1059. necessary to make the URL valid
  1060. """
  1061. self.error_message = error_message
  1062. if allowed_schemes == None:
  1063. self.allowed_schemes = all_url_schemes
  1064. else:
  1065. self.allowed_schemes = allowed_schemes
  1066. self.prepend_scheme = prepend_scheme
  1067. if self.prepend_scheme not in self.allowed_schemes:
  1068. raise SyntaxError, \
  1069. "prepend_scheme='%s' is not in allowed_schemes=%s" \
  1070. % (self.prepend_scheme, self.allowed_schemes)
  1071. def __call__(self, value):
  1072. """
  1073. :param value: a string, the URL to validate
  1074. :returns: a tuple, where tuple[0] is the inputed value (possible
  1075. prepended with prepend_scheme), and tuple[1] is either
  1076. None (success!) or the string error_message
  1077. """
  1078. try:
  1079. # if the URL does not misuse the '%' character
  1080. if not re.compile(
  1081. 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]$"
  1082. ).search(value):
  1083. # if the URL is only composed of valid characters
  1084. if re.compile(
  1085. r"[A-Za-z0-9;/?:@&=+$,\-_\.!~*'\(\)%#]+$").match(value):
  1086. # Then split up the URL into its components and check on
  1087. # the scheme
  1088. scheme = url_split_regex.match(value).group(2)
  1089. # Clean up the scheme before we check it
  1090. if scheme != None:
  1091. scheme = urllib.unquote(scheme).lower()
  1092. # If the scheme really exists
  1093. if scheme in self.allowed_schemes:
  1094. # Then the URL is valid
  1095. return (value, None)
  1096. else:
  1097. # else, for the possible case of abbreviated URLs with
  1098. # ports, check to see if adding a valid scheme fixes
  1099. # the problem (but only do this if it doesn't have
  1100. # one already!)
  1101. if not re.compile('://').search(value) and None\
  1102. in self.allowed_schemes:
  1103. schemeToUse = self.prepend_scheme or 'http'
  1104. prependTest = self.__call__(schemeToUse
  1105. + '://' + value)
  1106. # if the prepend test succeeded
  1107. if prependTest[1] == None:
  1108. # if prepending in the output is enabled
  1109. if self.prepend_scheme:
  1110. return prependTest
  1111. else:
  1112. # else return the original,
  1113. # non-prepended value
  1114. return (value, None)
  1115. except:
  1116. pass
  1117. # else the URL is not valid
  1118. return (value, self.error_message)
  1119. # Sources (obtained 2008-Nov-11):
  1120. # http://en.wikipedia.org/wiki/Top-level_domain
  1121. # http://www.iana.org/domains/root/db/
  1122. official_top_level_domains = [
  1123. 'ac',
  1124. 'ad',
  1125. 'ae',
  1126. 'aero',
  1127. 'af',
  1128. 'ag',
  1129. 'ai',
  1130. 'al',
  1131. 'am',
  1132. 'an',
  1133. 'ao',
  1134. 'aq',
  1135. 'ar',
  1136. 'arpa',
  1137. 'as',
  1138. 'asia',
  1139. 'at',
  1140. 'au',
  1141. 'aw',
  1142. 'ax',
  1143. 'az',
  1144. 'ba',
  1145. 'bb',
  1146. 'bd',
  1147. 'be',
  1148. 'bf',
  1149. 'bg',
  1150. 'bh',
  1151. 'bi',
  1152. 'biz',
  1153. 'bj',
  1154. 'bl',
  1155. 'bm',
  1156. 'bn',
  1157. 'bo',
  1158. 'br',
  1159. 'bs',
  1160. 'bt',
  1161. 'bv',
  1162. 'bw',
  1163. 'by',
  1164. 'bz',
  1165. 'ca',
  1166. 'cat',
  1167. 'cc',
  1168. 'cd',
  1169. 'cf',
  1170. 'cg',
  1171. 'ch',
  1172. 'ci',
  1173. 'ck',
  1174. 'cl',
  1175. 'cm',
  1176. 'cn',
  1177. 'co',
  1178. 'com',
  1179. 'coop',
  1180. 'cr',
  1181. 'cu',
  1182. 'cv',
  1183. 'cx',
  1184. 'cy',
  1185. 'cz',
  1186. 'de',
  1187. 'dj',
  1188. 'dk',
  1189. 'dm',
  1190. 'do',
  1191. 'dz',
  1192. 'ec',
  1193. 'edu',
  1194. 'ee',
  1195. 'eg',
  1196. 'eh',
  1197. 'er',
  1198. 'es',
  1199. 'et',
  1200. 'eu',
  1201. 'example',
  1202. 'fi',
  1203. 'fj',
  1204. 'fk',
  1205. 'fm',
  1206. 'fo',
  1207. 'fr',
  1208. 'ga',
  1209. 'gb',
  1210. 'gd',
  1211. 'ge',
  1212. 'gf',
  1213. 'gg',
  1214. 'gh',
  1215. 'gi',
  1216. 'gl',
  1217. 'gm',
  1218. 'gn',
  1219. 'gov',
  1220. 'gp',
  1221. 'gq',
  1222. 'gr',
  1223. 'gs',
  1224. 'gt',
  1225. 'gu',
  1226. 'gw',
  1227. 'gy',
  1228. 'hk',
  1229. 'hm',
  1230. 'hn',
  1231. 'hr',
  1232. 'ht',
  1233. 'hu',
  1234. 'id',
  1235. 'ie',
  1236. 'il',
  1237. 'im',
  1238. 'in',
  1239. 'info',
  1240. 'int',
  1241. 'invalid',
  1242. 'io',
  1243. 'iq',
  1244. 'ir',
  1245. 'is',
  1246. 'it',
  1247. 'je',
  1248. 'jm',
  1249. 'jo',
  1250. 'jobs',
  1251. 'jp',
  1252. 'ke',
  1253. 'kg',
  1254. 'kh',
  1255. 'ki',
  1256. 'km',
  1257. 'kn',
  1258. 'kp',
  1259. 'kr',
  1260. 'kw',
  1261. 'ky',
  1262. 'kz',
  1263. 'la',
  1264. 'lb',
  1265. 'lc',
  1266. 'li',
  1267. 'lk',
  1268. 'localhost',
  1269. 'lr',
  1270. 'ls',
  1271. 'lt',
  1272. 'lu',
  1273. 'lv',
  1274. 'ly',
  1275. 'ma',
  1276. 'mc',
  1277. 'md',
  1278. 'me',
  1279. 'mf',
  1280. 'mg',
  1281. 'mh',
  1282. 'mil',
  1283. 'mk',
  1284. 'ml',
  1285. 'mm',
  1286. 'mn',
  1287. 'mo',
  1288. 'mobi',
  1289. 'mp',
  1290. 'mq',
  1291. 'mr',
  1292. 'ms',
  1293. 'mt',
  1294. 'mu',
  1295. 'museum',
  1296. 'mv',
  1297. 'mw',
  1298. 'mx',
  1299. 'my',
  1300. 'mz',
  1301. 'na',
  1302. 'name',
  1303. 'nc',
  1304. 'ne',
  1305. 'net',
  1306. 'nf',
  1307. 'ng',
  1308. 'ni',
  1309. 'nl',
  1310. 'no',
  1311. 'np',
  1312. 'nr',
  1313. 'nu',
  1314. 'nz',
  1315. 'om',
  1316. 'org',
  1317. 'pa',
  1318. 'pe',
  1319. 'pf',
  1320. 'pg',
  1321. 'ph',
  1322. 'pk',
  1323. 'pl',
  1324. 'pm',
  1325. 'pn',
  1326. 'pr',
  1327. 'pro',
  1328. 'ps',
  1329. 'pt',
  1330. 'pw',
  1331. 'py',
  1332. 'qa',
  1333. 're',
  1334. 'ro',
  1335. 'rs',
  1336. 'ru',
  1337. 'rw',
  1338. 'sa',
  1339. 'sb',
  1340. 'sc',
  1341. 'sd',
  1342. 'se',
  1343. 'sg',
  1344. 'sh',
  1345. 'si',
  1346. 'sj',
  1347. 'sk',
  1348. 'sl',
  1349. 'sm',
  1350. 'sn',
  1351. 'so',
  1352. 'sr',
  1353. 'st',
  1354. 'su',
  1355. 'sv',
  1356. 'sy',
  1357. 'sz',
  1358. 'tc',
  1359. 'td',
  1360. 'tel',
  1361. 'test',
  1362. 'tf',
  1363. 'tg',
  1364. 'th',
  1365. 'tj',
  1366. 'tk',
  1367. 'tl',
  1368. 'tm',
  1369. 'tn',
  1370. 'to',
  1371. 'tp',
  1372. 'tr',
  1373. 'travel',
  1374. 'tt',
  1375. 'tv',
  1376. 'tw',
  1377. 'tz',
  1378. 'ua',
  1379. 'ug',
  1380. 'uk',
  1381. 'um',
  1382. 'us',
  1383. 'uy',
  1384. 'uz',
  1385. 'va',
  1386. 'vc',
  1387. 've',
  1388. 'vg',
  1389. 'vi',
  1390. 'vn',
  1391. 'vu',
  1392. 'wf',
  1393. 'ws',
  1394. 'xn--0zwm56d',
  1395. 'xn--11b5bs3a9aj6g',
  1396. 'xn--80akhbyknj4f',
  1397. 'xn--9t4b11yi5a',
  1398. 'xn--deba0ad',
  1399. 'xn--g6w251d',
  1400. 'xn--hgbk6aj7f53bba',
  1401. 'xn--hlcj6aya9esc7a',
  1402. 'xn--jxalpdlp',
  1403. 'xn--kgbechtv',
  1404. 'xn--zckzah',
  1405. 'ye',
  1406. 'yt',
  1407. 'yu',
  1408. 'za',
  1409. 'zm',
  1410. 'zw',
  1411. ]
  1412. class IS_HTTP_URL(Validator):
  1413. """
  1414. Rejects a URL string if any of the following is true:
  1415. * The string is empty or None
  1416. * The string uses characters that are not allowed in a URL
  1417. * The string breaks any of the HTTP syntactic rules
  1418. * The URL scheme specified (if one is specified) is not 'http' or 'https'
  1419. * The top-level domain (if a host name is specified) does not exist
  1420. Based on RFC 2616: http://www.faqs.org/rfcs/rfc2616.html
  1421. This function only checks the URL's syntax. It does not check that the URL
  1422. points to a real document, for example, or that it otherwise makes sense
  1423. semantically. This function does automatically prepend 'http://' in front
  1424. of a URL in the case of an abbreviated URL (e.g. 'google.ca').
  1425. The list of allowed schemes is customizable with the allowed_schemes
  1426. parameter. If you exclude None from the list, then abbreviated URLs
  1427. (lacking a scheme such as 'http') will be rejected.
  1428. The default prepended scheme is customizable with the prepend_scheme
  1429. parameter. If you set prepend_scheme to None then prepending will be
  1430. disabled. URLs that require prepending to parse will still be accepted,
  1431. but the return value will not be modified.
  1432. @author: Jonathan Benn
  1433. >>> IS_HTTP_URL()('http://1.2.3.4')
  1434. ('http://1.2.3.4', None)
  1435. >>> IS_HTTP_URL()('http://abc.com')
  1436. ('http://abc.com', None)
  1437. >>> IS_HTTP_URL()('https://abc.com')
  1438. ('https://abc.com', None)
  1439. >>> IS_HTTP_URL()('httpx://abc.com')
  1440. ('httpx://abc.com', 'enter a valid URL')
  1441. >>> IS_HTTP_URL()('http://abc.com:80')
  1442. ('http://abc.com:80', None)
  1443. >>> IS_HTTP_URL()('http://user@abc.com')
  1444. ('http://user@abc.com', None)
  1445. >>> IS_HTTP_URL()('http://user@1.2.3.4')
  1446. ('http://user@1.2.3.4', None)
  1447. """
  1448. def __init__(
  1449. self,
  1450. error_message='enter a valid URL',
  1451. allowed_schemes=None,
  1452. prepend_scheme='http',
  1453. ):
  1454. """
  1455. :param error_message: a string, the error message to give the end user
  1456. if the URL does not validate
  1457. :param allowed_schemes: a list containing strings or None. Each element
  1458. is a scheme the inputed URL is allowed to use
  1459. :param prepend_scheme: a string, this scheme is prepended if it's
  1460. necessary to make the URL valid
  1461. """
  1462. self.error_message = error_message
  1463. if allowed_schemes == None:
  1464. self.allowed_schemes = http_schemes
  1465. else:
  1466. self.allowed_schemes = allowed_schemes
  1467. self.prepend_scheme = prepend_scheme
  1468. for i in self.allowed_schemes:
  1469. if i not in http_schemes:
  1470. raise SyntaxError, \
  1471. "allowed_scheme value '%s' is not in %s" % \
  1472. (i, http_schemes)
  1473. if self.prepend_scheme not in self.allowed_schemes:
  1474. raise SyntaxError, \
  1475. "prepend_scheme='%s' is not in allowed_schemes=%s" % \
  1476. (self.prepend_scheme, self.allowed_schemes)
  1477. def __call__(self, value):
  1478. """
  1479. :param value: a string, the URL to validate
  1480. :returns: a tuple, where tuple[0] is the inputed value
  1481. (possible prepended with prepend_scheme), and tuple[1] is either
  1482. None (success!) or the string error_message
  1483. """
  1484. try:
  1485. # if the URL passes generic validation
  1486. x = IS_GENERIC_URL(error_message=self.error_message,
  1487. allowed_schemes=self.allowed_schemes,
  1488. prepend_scheme=self.prepend_scheme)
  1489. if x(value)[1] == None:
  1490. componentsMatch = url_split_regex.match(value)
  1491. authority = componentsMatch.group(4)
  1492. # if there is an authority component
  1493. if authority:
  1494. # if authority is a valid IP address
  1495. if re.compile(
  1496. "([\w.!~*'|;:&=+$,-]+@)?\d+\.\d+\.\d+\.\d+(:\d*)*$").match(authority):
  1497. # Then this HTTP URL is valid
  1498. return (value, None)
  1499. else:
  1500. # else if authority is a valid domain name
  1501. domainMatch = \
  1502. re.compile(
  1503. "([\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*)*$"
  1504. ).match(authority)
  1505. if domainMatch:
  1506. # if the top-level domain really exists
  1507. if domainMatch.group(5).lower()\
  1508. in official_top_level_domains:
  1509. # Then this HTTP URL is valid
  1510. return (value, None)
  1511. else:
  1512. # else this is a relative/abbreviated URL, which will parse
  1513. # into the URL's path component
  1514. path = componentsMatch.group(5)
  1515. # relative case: if this is a valid path (if it starts with
  1516. # a slash)
  1517. if re.compile('/').match(path):
  1518. # Then this HTTP URL is valid
  1519. return (value, None)
  1520. else:
  1521. # abbreviated case: if we haven't already, prepend a
  1522. # scheme and see if it fixes the problem
  1523. if not re.compile('://').search(value):
  1524. schemeToUse = self.prepend_scheme or 'http'
  1525. prependTest = self.__call__(schemeToUse
  1526. + '://' + value)
  1527. # if the prepend test succeeded
  1528. if prependTest[1] == None:
  1529. # if prepending in the output is enabled
  1530. if self.prepend_scheme:
  1531. return prependTest
  1532. else:
  1533. # else return the original, non-prepended
  1534. # value
  1535. return (value, None)
  1536. except:
  1537. pass
  1538. # else the HTTP URL is not valid
  1539. return (value, self.error_message)
  1540. class IS_URL(Validator):
  1541. """
  1542. Rejects a URL string if any of the following is true:
  1543. * The string is empty or None
  1544. * The string uses characters that are not allowed in a URL
  1545. * The string breaks any of the HTTP syntactic rules
  1546. * The URL scheme specified (if one is specified) is not 'http' or 'https'
  1547. * The top-level domain (if a host name is specified) does not exist
  1548. (These rules are based on RFC 2616: http://www.faqs.org/rfcs/rfc2616.html)
  1549. This function only checks the URL's syntax. It does not check that the URL
  1550. points to a real document, for example, or that it otherwise makes sense
  1551. semantically. This function does automatically prepend 'http://' in front
  1552. of a URL in the case of an abbreviated URL (e.g. 'google.ca').
  1553. If the parameter mode='generic' is used, then this function's behavior
  1554. changes. It then rejects a URL string if any of the following is true:
  1555. * The string is empty or None
  1556. * The string uses characters that are not allowed in a URL
  1557. * The URL scheme specified (if one is specified) is not valid
  1558. (These rules are based on RFC 2396: http://www.faqs.org/rfcs/rfc2396.html)
  1559. The list of allowed schemes is customizable with the allowed_schemes
  1560. parameter. If you exclude None from the list, then abbreviated URLs
  1561. (lacking a scheme such as 'http') will be rejected.
  1562. The default prepended scheme is customizable with the prepend_scheme
  1563. parameter. If you set prepend_scheme to None then prepending will be
  1564. disabled. URLs that require prepending to parse will still be accepted,
  1565. but the return value will not be modified.
  1566. IS_URL is compatible with the Internationalized Domain Name (IDN) standard
  1567. specified in RFC 3490 (http://tools.ietf.org/html/rfc3490). As a result,
  1568. URLs can be regular strings or unicode strings.
  1569. If the URL's domain component (e.g. google.ca) contains non-US-ASCII
  1570. letters, then the domain will be converted into Punycode (defined in
  1571. RFC 3492, http://tools.ietf.org/html/rfc3492). IS_URL goes a bit beyond
  1572. the standards, and allows non-US-ASCII characters to be present in the path
  1573. and query components of the URL as well. These non-US-ASCII characters will
  1574. be escaped using the standard '%20' type syntax. e.g. the unicode
  1575. character with hex code 0x4e86 will become '%4e%86'
  1576. Code Examples::
  1577. INPUT(_type='text', _name='name', requires=IS_URL())
  1578. >>> IS_URL()('abc.com')
  1579. ('http://abc.com', None)
  1580. INPUT(_type='text', _name='name', requires=IS_URL(mode='generic'))
  1581. >>> IS_URL(mode='generic')('abc.com')
  1582. ('abc.com', None)
  1583. INPUT(_type='text', _name='name',
  1584. requires=IS_URL(allowed_schemes=['https'], prepend_scheme='https'))
  1585. >>> IS_URL(allowed_schemes=['https'], prepend_scheme='https')('https://abc.com')
  1586. ('https://abc.com', None)
  1587. INPUT(_type='text', _name='name',
  1588. requires=IS_URL(prepend_scheme='https'))
  1589. >>> IS_URL(prepend_scheme='https')('abc.com')
  1590. ('https://abc.com', None)
  1591. INPUT(_type='text', _name='name',
  1592. requires=IS_URL(mode='generic', allowed_schemes=['ftps', 'https'],
  1593. prepend_scheme='https'))
  1594. >>> IS_URL(mode='generic', allowed_schemes=['ftps', 'https'], prepend_scheme='https')('https://abc.com')
  1595. ('https://abc.com', None)
  1596. >>> IS_URL(mode='generic', allowed_schemes=['ftps', 'https', None], prepend_scheme='https')('abc.com')
  1597. ('abc.com', None)
  1598. @author: Jonathan Benn
  1599. """
  1600. def __init__(
  1601. self,
  1602. error_message='enter a valid URL',
  1603. mode='http',
  1604. allowed_schemes=None,
  1605. prepend_scheme='http',
  1606. ):
  1607. """
  1608. :param error_message: a string, the error message to give the end user
  1609. if the URL does not validate
  1610. :param allowed_schemes: a list containing strings or None. Each element
  1611. is a scheme the inputed URL is allowed to use
  1612. :param prepend_scheme: a string, this scheme is prepended if it's
  1613. necessary to make the URL valid
  1614. """
  1615. self.error_message = error_message
  1616. self.mode = mode.lower()
  1617. if not self.mode in ['generic', 'http']:
  1618. raise SyntaxError, "invalid mode '%s' in IS_URL" % self.mode
  1619. self.allowed_schemes = allowed_schemes
  1620. if self.allowed_schemes:
  1621. if prepend_scheme not in self.allowed_schemes:
  1622. raise SyntaxError, \
  1623. "prepend_scheme='%s' is not in allowed_schemes=%s" \
  1624. % (prepend_scheme, self.allowed_schemes)
  1625. # if allowed_schemes is None, then we will defer testing
  1626. # prepend_scheme's validity to a sub-method
  1627. self.prepend_scheme = prepend_scheme
  1628. def __call__(self, value):
  1629. """
  1630. :param value: a unicode or regular string, the URL to validate
  1631. :returns: a (string, string) tuple, where tuple[0] is the modified
  1632. input value and tuple[1] is either None (success!) or the
  1633. string error_message. The input value will never be modified in the
  1634. case of an error. However, if there is success then the input URL
  1635. may be modified to (1) prepend a scheme, and/or (2) convert a
  1636. non-compliant unicode URL into a compliant US-ASCII version.
  1637. """
  1638. if self.mode == 'generic':
  1639. subMethod = IS_GENERIC_URL(error_message=self.error_message,
  1640. allowed_schemes=self.allowed_schemes,
  1641. prepend_scheme=self.prepend_scheme)
  1642. elif self.mode == 'http':
  1643. subMethod = IS_HTTP_URL(error_message=self.error_message,
  1644. allowed_schemes=self.allowed_schemes,
  1645. prepend_scheme=self.prepend_scheme)
  1646. else:
  1647. raise SyntaxError, "invalid mode '%s' in IS_URL" % self.mode
  1648. if type(value) != unicode:
  1649. return subMethod(value)
  1650. else:
  1651. try:
  1652. asciiValue = unicode_to_ascii_url(value, self.prepend_scheme)
  1653. except Exception:
  1654. #If we are not able to convert the unicode url into a
  1655. # US-ASCII URL, then the URL is not valid
  1656. return (value, self.error_message)
  1657. methodResult = subMethod(asciiValue)
  1658. #if the validation of the US-ASCII version of the value failed
  1659. if methodResult[1] != None:
  1660. # then return the original input value, not the US-ASCII version
  1661. return (value, methodResult[1])
  1662. else:
  1663. return methodResult
  1664. regex_time = re.compile(
  1665. '((?P<h>[0-9]+))([^0-9 ]+(?P<m>[0-9 ]+))?([^0-9ap ]+(?P<s>[0-9]*))?((?P<d>[ap]m))?')
  1666. class IS_TIME(Validator):
  1667. """
  1668. example::
  1669. INPUT(_type='text', _name='name', requires=IS_TIME())
  1670. understands the following formats
  1671. hh:mm:ss [am/pm]
  1672. hh:mm [am/pm]
  1673. hh [am/pm]
  1674. [am/pm] is optional, ':' can be replaced by any other non-space non-digit
  1675. >>> IS_TIME()('21:30')
  1676. (datetime.time(21, 30), None)
  1677. >>> IS_TIME()('21-30')
  1678. (datetime.time(21, 30), None)
  1679. >>> IS_TIME()('21.30')
  1680. (datetime.time(21, 30), None)
  1681. >>> IS_TIME()('21:30:59')
  1682. (datetime.time(21, 30, 59), None)
  1683. >>> IS_TIME()('5:30')
  1684. (datetime.time(5, 30), None)
  1685. >>> IS_TIME()('5:30 am')
  1686. (datetime.time(5, 30), None)
  1687. >>> IS_TIME()('5:30 pm')
  1688. (datetime.time(17, 30), None)
  1689. >>> IS_TIME()('5:30 whatever')
  1690. ('5:30 whatever', 'enter time as hh:mm:ss (seconds, am, pm optional)')
  1691. >>> IS_TIME()('5:30 20')
  1692. ('5:30 20', 'enter time as hh:mm:ss (seconds, am, pm optional)')
  1693. >>> IS_TIME()('24:30')
  1694. ('24:30', 'enter time as hh:mm:ss (seconds, am, pm optional)')
  1695. >>> IS_TIME()('21:60')
  1696. ('21:60', 'enter time as hh:mm:ss (seconds, am, pm optional)')
  1697. >>> IS_TIME()('21:30::')
  1698. ('21:30::', 'enter time as hh:mm:ss (seconds, am, pm optional)')
  1699. >>> IS_TIME()('')
  1700. ('', 'enter time as hh:mm:ss (seconds, am, pm optional)')
  1701. """
  1702. def __init__(self, error_message='enter time as hh:mm:ss (seconds, am, pm optional)'):
  1703. self.error_message = error_message
  1704. def __call__(self, value):
  1705. try:
  1706. ivalue = value
  1707. value = regex_time.match(value.lower())
  1708. (h, m, s) = (int(value.group('h')), 0, 0)
  1709. if value.group('m') != None:
  1710. m = int(value.group('m'))
  1711. if value.group('s') != None:
  1712. s = int(value.group('s'))
  1713. if value.group('d') == 'pm' and 0 < h < 12:
  1714. h = h + 12
  1715. if not (h in range(24) and m in range(60) and s
  1716. in range(60)):
  1717. raise ValueError\
  1718. ('Hours or minutes or seconds are outside of allowed range')
  1719. value = datetime.time(h, m, s)
  1720. return (value, None)
  1721. except AttributeError:
  1722. pass
  1723. except ValueError:
  1724. pass
  1725. return (ivalue, self.error_message)
  1726. class IS_DATE(Validator):
  1727. """
  1728. example::
  1729. INPUT(_type='text', _name='name', requires=IS_DATE())
  1730. date has to be in the ISO8960 format YYYY-MM-DD
  1731. """
  1732. def __init__(self, format='%Y-%m-%d',
  1733. error_message='enter date as %(format)s'):
  1734. self.format = str(format)
  1735. self.error_message = str(error_message)
  1736. def __call__(self, value):
  1737. try:
  1738. (y, m, d, hh, mm, ss, t0, t1, t2) = \
  1739. time.strptime(value, str(self.format))
  1740. value = datetime.date(y, m, d)
  1741. return (value, None)
  1742. except:
  1743. return (value, self.error_message % IS_DATETIME.nice(self.format))
  1744. def formatter(self, value):
  1745. format = self.format
  1746. y = '%.4i' % value.year
  1747. format = format.replace('%y',y[-2:])
  1748. format = format.replace('%Y',y)
  1749. d = datetime.date(2000,value.month,value.day)
  1750. return d.strftime(format)
  1751. class IS_DATETIME(Validator):
  1752. """
  1753. example::
  1754. INPUT(_type='text', _name='name', requires=IS_DATETIME())
  1755. datetime has to be in the ISO8960 format YYYY-MM-DD hh:mm:ss
  1756. """
  1757. isodatetime = '%Y-%m-%d %H:%M:%S'
  1758. @staticmethod
  1759. def nice(format):
  1760. code=(('%Y','1963'),
  1761. ('%y','63'),
  1762. ('%d','28'),
  1763. ('%m','08'),
  1764. ('%b','Aug'),
  1765. ('%b','August'),
  1766. ('%H','14'),
  1767. ('%I','02'),
  1768. ('%p','PM'),
  1769. ('%M','30'),
  1770. ('%S','59'))
  1771. for (a,b) in code:
  1772. format=format.replace(a,b)
  1773. return dict(format=format)
  1774. def __init__(self, format='%Y-%m-%d %H:%M:%S',
  1775. error_message='enter date and time as %(format)s'):
  1776. self.format = str(format)
  1777. self.error_message = str(error_message)
  1778. def __call__(self, value):
  1779. try:
  1780. (y, m, d, hh, mm, ss, t0, t1, t2) = \
  1781. time.strptime(value, str(self.format))
  1782. value = datetime.datetime(y, m, d, hh, mm, ss)
  1783. return (value, None)
  1784. except:
  1785. return (value, self.error_message % IS_DATETIME.nice(self.format))
  1786. def formatter(self, value):
  1787. format = self.format
  1788. y = '%.4i' % value.year
  1789. format = format.replace('%y',y[-2:])
  1790. format = format.replace('%Y',y)
  1791. d = datetime.datetime(2000,value.month,value.day,value.hour,value.minute,value.second)
  1792. return d.strftime(format)
  1793. class IS_DATE_IN_RANGE(IS_DATE):
  1794. def __init__(self,
  1795. minimum = None,
  1796. maximum = None,
  1797. format='%Y-%m-%d',
  1798. error_message = None):
  1799. self.minimum = minimum
  1800. self.maximum = maximum
  1801. if error_message is None:
  1802. if minimum is None:
  1803. error_message = "enter date on or before %(max)"
  1804. elif maximum is None:
  1805. error_message = "enter date on or after %(min)"
  1806. else:
  1807. error_message = "enter date in range %(min)s %(max)s"
  1808. d = dict(min=minimum, max=maximum)
  1809. IS_DATE.__init__(self,
  1810. format = format,
  1811. error_message = error_message % d)
  1812. def __call__(self, value):
  1813. (value, msg) = IS_DATE.__call__(self,value)
  1814. if msg is not None:
  1815. return (value, msg)
  1816. if self.minimum and self.minimum >= value:
  1817. return (value, self.error_message)
  1818. if self.maximum and value >= self.maximum:
  1819. return (value, self.error_message)
  1820. return (value, None)
  1821. class IS_DATETIME_IN_RANGE(IS_DATETIME):
  1822. def __init__(self,
  1823. minimum = None,
  1824. maximum = None,
  1825. format = '%Y-%m-%d %H:%M:%S',
  1826. error_message = None):
  1827. self.minimum = minimum
  1828. self.maximum = maximum
  1829. if error_message is None:
  1830. if minimum is None:
  1831. error_message = "enter date and time on or before %(max)"
  1832. elif maximum is None:
  1833. error_message = "enter date and time on or after %(min)"
  1834. else:
  1835. error_message = "enter date and time in range %(min)s %(max)s"
  1836. d = dict(min = minimum, max = maximum)
  1837. IS_DATETIME.__init__(self,
  1838. format = format,
  1839. error_message = error_message % d)
  1840. def __call__(self, value):
  1841. (value, msg) = IS_DATETIME.__call__(self, value)
  1842. if msg is not None:
  1843. return (value, msg)
  1844. if self.minimum and self.minimum >= value:
  1845. return (value, self.error_message)
  1846. if self.maximum and value >= self.maximum:
  1847. return (value, self.error_message)
  1848. return (value, None)
  1849. class IS_LIST_OF(Validator):
  1850. def __init__(self, other):
  1851. self.other = other
  1852. def __call__(self, value):
  1853. ivalue = value
  1854. if not isinstance(value, list):
  1855. ivalue = [ivalue]
  1856. new_value = []
  1857. for item in ivalue:
  1858. (v, e) = self.other(item)
  1859. if e:
  1860. return (value, e)
  1861. else:
  1862. new_value.append(v)
  1863. return (new_value, None)
  1864. class IS_LOWER(Validator):
  1865. """
  1866. convert to lower case
  1867. >>> IS_LOWER()('ABC')
  1868. ('abc', None)
  1869. >>> IS_LOWER()('Ñ')
  1870. ('\\xc3\\xb1', None)
  1871. """
  1872. def __call__(self, value):
  1873. return (value.decode('utf8').lower().encode('utf8'), None)
  1874. class IS_UPPER(Validator):
  1875. """
  1876. convert to upper case
  1877. >>> IS_UPPER()('abc')
  1878. ('ABC', None)
  1879. >>> IS_UPPER()('ñ')
  1880. ('\\xc3\\x91', None)
  1881. """
  1882. def __call__(self, value):
  1883. return (value.decode('utf8').upper().encode('utf8'), None)
  1884. class IS_SLUG(Validator):
  1885. """
  1886. convert arbitrary text string to a slug
  1887. >>> IS_SLUG()('abc123')
  1888. ('abc123', None)
  1889. >>> IS_SLUG()('ABC123')
  1890. ('abc123', None)
  1891. >>> IS_SLUG()('abc-123')
  1892. ('abc-123', None)
  1893. >>> IS_SLUG()('abc--123')
  1894. ('abc-123', None)
  1895. >>> IS_SLUG()('abc 123')
  1896. ('abc-123', None)
  1897. >>> IS_SLUG()('-abc-')
  1898. ('abc', None)
  1899. >>> IS_SLUG()('abc&amp;123')
  1900. ('abc123', None)
  1901. >>> IS_SLUG()('abc&amp;123&amp;def')
  1902. ('abc123def', None)
  1903. >>> IS_SLUG()('ñ')
  1904. ('n', None)
  1905. >>> IS_SLUG(maxlen=4)('abc123')
  1906. ('abc1', None)
  1907. """
  1908. def __init__(self, maxlen=80, check=False, error_message='must be slug'):
  1909. self.maxlen = maxlen
  1910. self.check = check
  1911. self.error_message = error_message
  1912. @staticmethod
  1913. def urlify(value, maxlen=80):
  1914. s = value.decode('utf-8').lower() # to lowercase utf-8
  1915. s = unicodedata.normalize('NFKD', s) # normalize eg è => e, ñ => n
  1916. s = s.encode('ASCII', 'ignore') # encode as ASCII
  1917. s = re.sub('&\w+?;', '', s) # strip html entities
  1918. s = re.sub('[^a-z0-9\-\s]', '', s) # strip all but alphanumeric/hyphen/space
  1919. s = s.replace(' ', '-') # spaces to hyphens
  1920. s = re.sub('--+', '-', s) # collapse strings of hyphens
  1921. s = s.strip('-') # remove leading and traling hyphens
  1922. return s[:maxlen].strip('-') # enforce maximum length
  1923. def __call__(self,value):
  1924. if self.check and value != IS_SLUG.urlify(value,self.maxlen):
  1925. return (value,self.error_message)
  1926. return (IS_SLUG.urlify(value,self.maxlen), None)
  1927. class IS_EMPTY_OR(Validator):
  1928. """
  1929. dummy class for testing IS_EMPTY_OR
  1930. >>> IS_EMPTY_OR(IS_EMAIL())('abc@def.com')
  1931. ('abc@def.com', None)
  1932. >>> IS_EMPTY_OR(IS_EMAIL())(' ')
  1933. (None, None)
  1934. >>> IS_EMPTY_OR(IS_EMAIL(), null='abc')(' ')
  1935. ('abc', None)
  1936. >>> IS_EMPTY_OR(IS_EMAIL(), null='abc', empty_regex='def')('def')
  1937. ('abc', None)
  1938. >>> IS_EMPTY_OR(IS_EMAIL())('abc')
  1939. ('abc', 'enter a valid email address')
  1940. >>> IS_EMPTY_OR(IS_EMAIL())(' abc ')
  1941. ('abc', 'enter a valid email address')
  1942. """
  1943. def __init__(self, other, null=None, empty_regex=None):
  1944. (self.other, self.null) = (other, null)
  1945. if empty_regex is not None:
  1946. self.empty_regex = re.compile(empty_regex)
  1947. else:
  1948. self.empty_regex = None
  1949. if hasattr(other, 'multiple'):
  1950. self.multiple = other.multiple
  1951. if hasattr(other, 'options'):
  1952. self.options=self._options
  1953. def _options(self):
  1954. options = self.other.options()
  1955. if (not options or options[0][0]!='') and not self.multiple:
  1956. options.insert(0,('',''))
  1957. return options
  1958. def set_self_id(self, id):
  1959. if hasattr(self.other, 'set_self_id'):
  1960. self.other.set_self_id(id)
  1961. def __call__(self, value):
  1962. value, empty = is_empty(value, empty_regex=self.empty_regex)
  1963. if empty:
  1964. return (self.null, None)
  1965. return self.other(value)
  1966. def formatter(self, value):
  1967. if hasattr(self.other, 'formatter'):
  1968. return self.other.formatter(value)
  1969. return value
  1970. IS_NULL_OR = IS_EMPTY_OR # for backward compatibility
  1971. class CLEANUP(Validator):
  1972. """
  1973. example::
  1974. INPUT(_type='text', _name='name', requires=CLEANUP())
  1975. removes special characters on validation
  1976. """
  1977. def __init__(self, regex='[^ \n\w]'):
  1978. self.regex = re.compile(regex)
  1979. def __call__(self, value):
  1980. v = self.regex.sub('',str(value).strip())
  1981. return (v, None)
  1982. class CRYPT(object):
  1983. """
  1984. example::
  1985. INPUT(_type='text', _name='name', requires=CRYPT())
  1986. encodes the value on validation with a digest.
  1987. If no arguments are provided CRYPT uses the MD5 algorithm.
  1988. If the key argument is provided the HMAC+MD5 algorithm is used.
  1989. If the digest_alg is specified this is used to replace the
  1990. MD5 with, for example, SHA512. The digest_alg can be
  1991. the name of a hashlib algorithm as a string or the algorithm itself.
  1992. """
  1993. def __init__(self, key=None, digest_alg=None):
  1994. if key and not digest_alg:
  1995. if key.count(':')==1:
  1996. (digest_alg, key) = key.split(':')
  1997. if not digest_alg:
  1998. digest_alg = 'md5' # for backward compatibility
  1999. self.key = key
  2000. self.digest_alg = digest_alg
  2001. def __call__(self, value):
  2002. if self.key:
  2003. alg = get_digest(self.digest_alg)
  2004. return (hmac.new(self.key, value, alg).hexdigest(), None)
  2005. else:
  2006. return (hash(value, self.digest_alg), None)
  2007. class IS_STRONG(object):
  2008. """
  2009. example::
  2010. INPUT(_type='password', _name='passwd',
  2011. requires=IS_STRONG(min=10, special=2, upper=2))
  2012. enforces complexity requirements on a field
  2013. """
  2014. def __init__(self, min=8, max=20, upper=1, lower=1, number=1,
  2015. special=1, specials=r'~!@#$%^&*()_+-=?<>,.:;{}[]|',
  2016. invalid=' "', error_message=None):
  2017. self.min = min
  2018. self.max = max
  2019. self.upper = upper
  2020. self.lower = lower
  2021. self.number = number
  2022. self.special = special
  2023. self.specials = specials
  2024. self.invalid = invalid
  2025. self.error_message = error_message
  2026. def __call__(self, value):
  2027. failures = []
  2028. if type(self.min) == int and self.min > 0:
  2029. if not len(value) >= self.min:
  2030. failures.append("Minimum length is %s" % self.min)
  2031. if type(self.max) == int and self.max > 0:
  2032. if not len(value) <= self.max:
  2033. failures.append("Maximum length is %s" % self.max)
  2034. if type(self.special) == int:
  2035. all_special = [ch in value for ch in self.specials]
  2036. if self.special > 0:
  2037. if not all_special.count(True) >= self.special:
  2038. failures.append("Must include at least %s of the following : %s" % (self.special, self.specials))
  2039. if self.invalid:
  2040. all_invalid = [ch in value for ch in self.invalid]
  2041. if all_invalid.count(True) > 0:
  2042. failures.append("May not contain any of the following: %s" \
  2043. % self.invalid)
  2044. if type(self.upper) == int:
  2045. all_upper = re.findall("[A-Z]", value)
  2046. if self.upper > 0:
  2047. if not len(all_upper) >= self.upper:
  2048. failures.append("Must include at least %s upper case" \
  2049. % str(self.upper))
  2050. else:
  2051. if len(all_upper) > 0:
  2052. failures.append("May not include any upper case letters")
  2053. if type(self.lower) == int:
  2054. all_lower = re.findall("[a-z]", value)
  2055. if self.lower > 0:
  2056. if not len(all_lower) >= self.lower:
  2057. failures.append("Must include at least %s lower case" \
  2058. % str(self.lower))
  2059. else:
  2060. if len(all_lower) > 0:
  2061. failures.append("May not include any lower case letters")
  2062. if type(self.number) == int:
  2063. all_number = re.findall("[0-9]", value)
  2064. if self.number > 0:
  2065. numbers = "number"
  2066. if self.number > 1:
  2067. numbers = "numbers"
  2068. if not len(all_number) >= self.number:
  2069. failures.append("Must include at least %s %s" \
  2070. % (str(self.number), numbers))
  2071. else:
  2072. if len(all_number) > 0:
  2073. failures.append("May not include any numbers")
  2074. if len(failures) == 0:
  2075. return (value, None)
  2076. if not self.error_message:
  2077. from gluon.html import XML
  2078. return (value, XML('<br />'.join(failures)))
  2079. else:
  2080. return (value, self.error_message)
  2081. class IS_IN_SUBSET(IS_IN_SET):
  2082. def __init__(self, *a, **b):
  2083. IS_IN_SET.__init__(self, *a, **b)
  2084. def __call__(self, value):
  2085. values = re.compile("\w+").findall(str(value))
  2086. failures = [x for x in values if IS_IN_SET.__call__(self, x)[1]]
  2087. if failures:
  2088. return (value, self.error_message)
  2089. return (value, None)
  2090. class IS_IMAGE(Validator):
  2091. """
  2092. Checks if file uploaded through file input was saved in one of selected
  2093. image formats and has dimensions (width and height) within given boundaries.
  2094. Does *not* check for maximum file size (use IS_LENGTH for that). Returns
  2095. validation failure if no data was uploaded.
  2096. Supported file formats: BMP, GIF, JPEG, PNG.
  2097. Code parts taken from
  2098. http://mail.python.org/pipermail/python-list/2007-June/617126.html
  2099. Arguments:
  2100. extensions: iterable containing allowed *lowercase* image file extensions
  2101. ('jpg' extension of uploaded file counts as 'jpeg')
  2102. maxsize: iterable containing maximum width and height of the image
  2103. minsize: iterable containing minimum width and height of the image
  2104. Use (-1, -1) as minsize to pass image size check.
  2105. Examples::
  2106. #Check if uploaded file is in any of supported image formats:
  2107. INPUT(_type='file', _name='name', requires=IS_IMAGE())
  2108. #Check if uploaded file is either JPEG or PNG:
  2109. INPUT(_type='file', _name='name',
  2110. requires=IS_IMAGE(extensions=('jpeg', 'png')))
  2111. #Check if uploaded file is PNG with maximum size of 200x200 pixels:
  2112. INPUT(_type='file', _name='name',
  2113. requires=IS_IMAGE(extensions=('png'), maxsize=(200, 200)))
  2114. """
  2115. def __init__(self,
  2116. extensions=('bmp', 'gif', 'jpeg', 'png'),
  2117. maxsize=(10000, 10000),
  2118. minsize=(0, 0),
  2119. error_message='invalid image'):
  2120. self.extensions = extensions
  2121. self.maxsize = maxsize
  2122. self.minsize = minsize
  2123. self.error_message = error_message
  2124. def __call__(self, value):
  2125. try:
  2126. extension = value.filename.rfind('.')
  2127. assert extension >= 0
  2128. extension = value.filename[extension + 1:].lower()
  2129. if extension == 'jpg':
  2130. extension = 'jpeg'
  2131. assert extension in self.extensions
  2132. if extension == 'bmp':
  2133. width, height = self.__bmp(value.file)
  2134. elif extension == 'gif':
  2135. width, height = self.__gif(value.file)
  2136. elif extension == 'jpeg':
  2137. width, height = self.__jpeg(value.file)
  2138. elif extension == 'png':
  2139. width, height = self.__png(value.file)
  2140. else:
  2141. width = -1
  2142. height = -1
  2143. assert self.minsize[0] <= width <= self.maxsize[0] \
  2144. and self.minsize[1] <= height <= self.maxsize[1]
  2145. value.file.seek(0)
  2146. return (value, None)
  2147. except:
  2148. return (value, self.error_message)
  2149. def __bmp(self, stream):
  2150. if stream.read(2) == 'BM':
  2151. stream.read(16)
  2152. return struct.unpack("<LL", stream.read(8))
  2153. return (-1, -1)
  2154. def __gif(self, stream):
  2155. if stream.read(6) in ('GIF87a', 'GIF89a'):
  2156. stream = stream.read(5)
  2157. if len(stream) == 5:
  2158. return tuple(struct.unpack("<HHB", stream)[:-1])
  2159. return (-1, -1)
  2160. def __jpeg(self, stream):
  2161. if stream.read(2) == '\xFF\xD8':
  2162. while True:
  2163. (marker, code, length) = struct.unpack("!BBH", stream.read(4))
  2164. if marker != 0xFF:
  2165. break
  2166. elif code >= 0xC0 and code <= 0xC3:
  2167. return tuple(reversed(
  2168. struct.unpack("!xHH", stream.read(5))))
  2169. else:
  2170. stream.read(length - 2)
  2171. return (-1, -1)
  2172. def __png(self, stream):
  2173. if stream.read(8) == '\211PNG\r\n\032\n':
  2174. stream.read(4)
  2175. if stream.read(4) == "IHDR":
  2176. return struct.unpack("!LL", stream.read(8))
  2177. return (-1, -1)
  2178. class IS_UPLOAD_FILENAME(Validator):
  2179. """
  2180. Checks if name and extension of file uploaded through file input matches
  2181. given criteria.
  2182. Does *not* ensure the file type in any way. Returns validation failure
  2183. if no data was uploaded.
  2184. Arguments::
  2185. filename: filename (before dot) regex
  2186. extension: extension (after dot) regex
  2187. lastdot: which dot should be used as a filename / extension separator:
  2188. True means last dot, eg. file.png -> file / png
  2189. False means first dot, eg. file.tar.gz -> file / tar.gz
  2190. case: 0 - keep the case, 1 - transform the string into lowercase (default),
  2191. 2 - transform the string into uppercase
  2192. If there is no dot present, extension checks will be done against empty
  2193. string and filename checks against whole value.
  2194. Examples::
  2195. #Check if file has a pdf extension (case insensitive):
  2196. INPUT(_type='file', _name='name',
  2197. requires=IS_UPLOAD_FILENAME(extension='pdf'))
  2198. #Check if file has a tar.gz extension and name starting with backup:
  2199. INPUT(_type='file', _name='name',
  2200. requires=IS_UPLOAD_FILENAME(filename='backup.*',
  2201. extension='tar.gz', lastdot=False))
  2202. #Check if file has no extension and name matching README
  2203. #(case sensitive):
  2204. INPUT(_type='file', _name='name',
  2205. requires=IS_UPLOAD_FILENAME(filename='^README$',
  2206. extension='^$', case=0))
  2207. """
  2208. def __init__(self, filename=None, extension=None, lastdot=True, case=1,
  2209. error_message='enter valid filename'):
  2210. if isinstance(filename, str):
  2211. filename = re.compile(filename)
  2212. if isinstance(extension, str):
  2213. extension = re.compile(extension)
  2214. self.filename = filename
  2215. self.extension = extension
  2216. self.lastdot = lastdot
  2217. self.case = case
  2218. self.error_message = error_message
  2219. def __call__(self, value):
  2220. try:
  2221. string = value.filename
  2222. except:
  2223. return (value, self.error_message)
  2224. if self.case == 1:
  2225. string = string.lower()
  2226. elif self.case == 2:
  2227. string = string.upper()
  2228. if self.lastdot:
  2229. dot = string.rfind('.')
  2230. else:
  2231. dot = string.find('.')
  2232. if dot == -1:
  2233. dot = len(string)
  2234. if self.filename and not self.filename.match(string[:dot]):
  2235. return (value, self.error_message)
  2236. elif self.extension and not self.extension.match(string[dot + 1:]):
  2237. return (value, self.error_message)
  2238. else:
  2239. return (value, None)
  2240. class IS_IPV4(Validator):
  2241. """
  2242. Checks if field's value is an IP version 4 address in decimal form. Can
  2243. be set to force addresses from certain range.
  2244. IPv4 regex taken from: http://regexlib.com/REDetails.aspx?regexp_id=1411
  2245. Arguments:
  2246. minip: lowest allowed address; accepts:
  2247. str, eg. 192.168.0.1
  2248. list or tuple of octets, eg. [192, 168, 0, 1]
  2249. maxip: highest allowed address; same as above
  2250. invert: True to allow addresses only from outside of given range; note
  2251. that range boundaries are not matched this way
  2252. is_localhost: localhost address treatment:
  2253. None (default): indifferent
  2254. True (enforce): query address must match localhost address
  2255. (127.0.0.1)
  2256. False (forbid): query address must not match localhost
  2257. address
  2258. is_private: same as above, except that query address is checked against
  2259. two address ranges: 172.16.0.0 - 172.31.255.255 and
  2260. 192.168.0.0 - 192.168.255.255
  2261. is_automatic: same as above, except that query address is checked against
  2262. one address range: 169.254.0.0 - 169.254.255.255
  2263. Minip and maxip may also be lists or tuples of addresses in all above
  2264. forms (str, int, list / tuple), allowing setup of multiple address ranges:
  2265. minip = (minip1, minip2, ... minipN)
  2266. | | |
  2267. | | |
  2268. maxip = (maxip1, maxip2, ... maxipN)
  2269. Longer iterable will be truncated to match length of shorter one.
  2270. Examples::
  2271. #Check for valid IPv4 address:
  2272. INPUT(_type='text', _name='name', requires=IS_IPV4())
  2273. #Check for valid IPv4 address belonging to specific range:
  2274. INPUT(_type='text', _name='name',
  2275. requires=IS_IPV4(minip='100.200.0.0', maxip='100.200.255.255'))
  2276. #Check for valid IPv4 address belonging to either 100.110.0.0 -
  2277. #100.110.255.255 or 200.50.0.0 - 200.50.0.255 address range:
  2278. INPUT(_type='text', _name='name',
  2279. requires=IS_IPV4(minip=('100.110.0.0', '200.50.0.0'),
  2280. maxip=('100.110.255.255', '200.50.0.255')))
  2281. #Check for valid IPv4 address belonging to private address space:
  2282. INPUT(_type='text', _name='name', requires=IS_IPV4(is_private=True))
  2283. #Check for valid IPv4 address that is not a localhost address:
  2284. INPUT(_type='text', _name='name', requires=IS_IPV4(is_localhost=False))
  2285. >>> IS_IPV4()('1.2.3.4')
  2286. ('1.2.3.4', None)
  2287. >>> IS_IPV4()('255.255.255.255')
  2288. ('255.255.255.255', None)
  2289. >>> IS_IPV4()('1.2.3.4 ')
  2290. ('1.2.3.4 ', 'enter valid IPv4 address')
  2291. >>> IS_IPV4()('1.2.3.4.5')
  2292. ('1.2.3.4.5', 'enter valid IPv4 address')
  2293. >>> IS_IPV4()('123.123')
  2294. ('123.123', 'enter valid IPv4 address')
  2295. >>> IS_IPV4()('1111.2.3.4')
  2296. ('1111.2.3.4', 'enter valid IPv4 address')
  2297. >>> IS_IPV4()('0111.2.3.4')
  2298. ('0111.2.3.4', 'enter valid IPv4 address')
  2299. >>> IS_IPV4()('256.2.3.4')
  2300. ('256.2.3.4', 'enter valid IPv4 address')
  2301. >>> IS_IPV4()('300.2.3.4')
  2302. ('300.2.3.4', 'enter valid IPv4 address')
  2303. >>> IS_IPV4(minip='1.2.3.4', maxip='1.2.3.4')('1.2.3.4')
  2304. ('1.2.3.4', None)
  2305. >>> IS_IPV4(minip='1.2.3.5', maxip='1.2.3.9', error_message='bad ip')('1.2.3.4')
  2306. ('1.2.3.4', 'bad ip')
  2307. >>> IS_IPV4(maxip='1.2.3.4', invert=True)('127.0.0.1')
  2308. ('127.0.0.1', None)
  2309. >>> IS_IPV4(maxip='1.2.3.4', invert=True)('1.2.3.4')
  2310. ('1.2.3.4', 'enter valid IPv4 address')
  2311. >>> IS_IPV4(is_localhost=True)('127.0.0.1')
  2312. ('127.0.0.1', None)
  2313. >>> IS_IPV4(is_localhost=True)('1.2.3.4')
  2314. ('1.2.3.4', 'enter valid IPv4 address')
  2315. >>> IS_IPV4(is_localhost=False)('127.0.0.1')
  2316. ('127.0.0.1', 'enter valid IPv4 address')
  2317. >>> IS_IPV4(maxip='100.0.0.0', is_localhost=True)('127.0.0.1')
  2318. ('127.0.0.1', 'enter valid IPv4 address')
  2319. """
  2320. regex = re.compile(
  2321. '^(([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])$')
  2322. numbers = (16777216, 65536, 256, 1)
  2323. localhost = 2130706433
  2324. private = ((2886729728L, 2886795263L), (3232235520L, 3232301055L))
  2325. automatic = (2851995648L, 2852061183L)
  2326. def __init__(
  2327. self,
  2328. minip='0.0.0.0',
  2329. maxip='255.255.255.255',
  2330. invert=False,
  2331. is_localhost=None,
  2332. is_private=None,
  2333. is_automatic=None,
  2334. error_message='enter valid IPv4 address'):
  2335. for n, value in enumerate((minip, maxip)):
  2336. temp = []
  2337. if isinstance(value, str):
  2338. temp.append(value.split('.'))
  2339. elif isinstance(value, (list, tuple)):
  2340. if len(value) == len(filter(lambda item: isinstance(item, int), value)) == 4:
  2341. temp.append(value)
  2342. else:
  2343. for item in value:
  2344. if isinstance(item, str):
  2345. temp.append(item.split('.'))
  2346. elif isinstance(item, (list, tuple)):
  2347. temp.append(item)
  2348. numbers = []
  2349. for item in temp:
  2350. number = 0
  2351. for i, j in zip(self.numbers, item):
  2352. number += i * int(j)
  2353. numbers.append(number)
  2354. if n == 0:
  2355. self.minip = numbers
  2356. else:
  2357. self.maxip = numbers
  2358. self.invert = invert
  2359. self.is_localhost = is_localhost
  2360. self.is_private = is_private
  2361. self.is_automatic = is_automatic
  2362. self.error_message = error_message
  2363. def __call__(self, value):
  2364. if self.regex.match(value):
  2365. number = 0
  2366. for i, j in zip(self.numbers, value.split('.')):
  2367. number += i * int(j)
  2368. ok = False
  2369. for bottom, top in zip(self.minip, self.maxip):
  2370. if self.invert != (bottom <= number <= top):
  2371. ok = True
  2372. if not (self.is_localhost == None or self.is_localhost == \
  2373. (number == self.localhost)):
  2374. ok = False
  2375. if not (self.is_private == None or self.is_private == \
  2376. (sum([number[0] <= number <= number[1] for number in self.private]) > 0)):
  2377. ok = False
  2378. if not (self.is_automatic == None or self.is_automatic == \
  2379. (self.automatic[0] <= number <= self.automatic[1])):
  2380. ok = False
  2381. if ok:
  2382. return (value, None)
  2383. return (value, self.error_message)
  2384. if __name__ == '__main__':
  2385. import doctest
  2386. doctest.testmod()