PageRenderTime 83ms CodeModel.GetById 41ms RepoModel.GetById 0ms app.codeStats 1ms

/gluon/validators.py

https://bitbucket.org/rochacbruno/dal_on_flask/
Python | 2848 lines | 2688 code | 57 blank | 103 comment | 78 complexity | 77e9d4f2f958fb0c768fd8f33c72597a MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1. #!/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_EQUAL_TO',
  44. 'IS_NOT_EMPTY',
  45. 'IS_NOT_IN_DB',
  46. 'IS_NULL_OR',
  47. 'IS_SLUG',
  48. 'IS_STRONG',
  49. 'IS_TIME',
  50. 'IS_UPLOAD_FILENAME',
  51. 'IS_UPPER',
  52. 'IS_URL',
  53. ]
  54. def options_sorter(x,y):
  55. return (str(x[1]).upper()>str(y[1]).upper() and 1) or -1
  56. class Validator(object):
  57. """
  58. Root for all validators, mainly for documentation purposes.
  59. Validators are classes used to validate input fields (including forms
  60. generated from database tables).
  61. Here is an example of using a validator with a FORM::
  62. INPUT(_name='a', requires=IS_INT_IN_RANGE(0, 10))
  63. Here is an example of how to require a validator for a table field::
  64. db.define_table('person', SQLField('name'))
  65. db.person.name.requires=IS_NOT_EMPTY()
  66. Validators are always assigned using the requires attribute of a field. A
  67. field can have a single validator or multiple validators. Multiple
  68. validators are made part of a list::
  69. db.person.name.requires=[IS_NOT_EMPTY(), IS_NOT_IN_DB(db, 'person.id')]
  70. Validators are called by the function accepts on a FORM or other HTML
  71. helper object that contains a form. They are always called in the order in
  72. which they are listed.
  73. Built-in validators have constructors that take the optional argument error
  74. message which allows you to change the default error message.
  75. Here is an example of a validator on a database table::
  76. db.person.name.requires=IS_NOT_EMPTY(error_message=T('fill this'))
  77. where we have used the translation operator T to allow for
  78. internationalization.
  79. Notice that default error messages are not translated.
  80. """
  81. def formatter(self, value):
  82. """
  83. For some validators returns a formatted version (matching the validator)
  84. of value. Otherwise just returns the value.
  85. """
  86. return value
  87. class IS_MATCH(Validator):
  88. """
  89. example::
  90. INPUT(_type='text', _name='name', requires=IS_MATCH('.+'))
  91. the argument of IS_MATCH is a regular expression::
  92. >>> IS_MATCH('.+')('hello')
  93. ('hello', None)
  94. >>> IS_MATCH('.+')('')
  95. ('', 'invalid expression')
  96. """
  97. def __init__(self, expression, error_message='invalid expression'):
  98. self.regex = re.compile(expression)
  99. self.error_message = error_message
  100. def __call__(self, value):
  101. match = self.regex.match(value)
  102. if match:
  103. return (match.group(), None)
  104. return (value, self.error_message)
  105. class IS_EQUAL_TO(Validator):
  106. """
  107. example::
  108. INPUT(_type='text', _name='password')
  109. INPUT(_type='text', _name='password2',
  110. requires=IS_EQUAL_TO(request.vars.password))
  111. the argument of IS_EQUAL_TO is a string
  112. >>> IS_EQUAL_TO('aaa')('aaa')
  113. ('aaa', None)
  114. >>> IS_EQUAL_TO('aaa')('aab')
  115. ('aab', 'no match')
  116. """
  117. def __init__(self, expression, error_message='no match'):
  118. self.expression = expression
  119. self.error_message = error_message
  120. def __call__(self, value):
  121. if value == self.expression:
  122. return (value, None)
  123. return (value, self.error_message)
  124. class IS_EXPR(Validator):
  125. """
  126. example::
  127. INPUT(_type='text', _name='name',
  128. requires=IS_EXPR('5 < int(value) < 10'))
  129. the argument of IS_EXPR must be python condition::
  130. >>> IS_EXPR('int(value) < 2')('1')
  131. ('1', None)
  132. >>> IS_EXPR('int(value) < 2')('2')
  133. ('2', 'invalid expression')
  134. """
  135. def __init__(self, expression, error_message='invalid expression'):
  136. self.expression = expression
  137. self.error_message = error_message
  138. def __call__(self, value):
  139. environment = {'value': value}
  140. exec '__ret__=' + self.expression in environment
  141. if environment['__ret__']:
  142. return (value, None)
  143. return (value, self.error_message)
  144. class IS_LENGTH(Validator):
  145. """
  146. Checks if length of field's value fits between given boundaries. Works
  147. for both text and file inputs.
  148. Arguments:
  149. maxsize: maximum allowed length / size
  150. minsize: minimum allowed length / size
  151. Examples::
  152. #Check if text string is shorter than 33 characters:
  153. INPUT(_type='text', _name='name', requires=IS_LENGTH(32))
  154. #Check if password string is longer than 5 characters:
  155. INPUT(_type='password', _name='name', requires=IS_LENGTH(minsize=6))
  156. #Check if uploaded file has size between 1KB and 1MB:
  157. INPUT(_type='file', _name='name', requires=IS_LENGTH(1048576, 1024))
  158. >>> IS_LENGTH()('')
  159. ('', None)
  160. >>> IS_LENGTH()('1234567890')
  161. ('1234567890', None)
  162. >>> IS_LENGTH(maxsize=5, minsize=0)('1234567890') # too long
  163. ('1234567890', 'enter from 0 to 5 characters')
  164. >>> IS_LENGTH(maxsize=50, minsize=20)('1234567890') # too short
  165. ('1234567890', 'enter from 20 to 50 characters')
  166. """
  167. def __init__(self, maxsize=255, minsize=0, error_message='enter from %(min)s to %(max)s characters'):
  168. self.maxsize = maxsize
  169. self.minsize = minsize
  170. self.error_message = error_message % dict(min=minsize, max=maxsize)
  171. def __call__(self, value):
  172. if isinstance(value, cgi.FieldStorage):
  173. if value.file:
  174. value.file.seek(0, os.SEEK_END)
  175. length = value.file.tell()
  176. value.file.seek(0, os.SEEK_SET)
  177. else:
  178. val = value.value
  179. if val:
  180. length = len(val)
  181. else:
  182. length = 0
  183. if self.minsize <= length <= self.maxsize:
  184. return (value, None)
  185. elif isinstance(value, (str, unicode, list)):
  186. if self.minsize <= len(value) <= self.maxsize:
  187. return (value, None)
  188. elif self.minsize <= len(str(value)) <= self.maxsize:
  189. try:
  190. value.decode('utf8')
  191. return (value, None)
  192. except:
  193. pass
  194. return (value, self.error_message)
  195. class IS_IN_SET(Validator):
  196. """
  197. example::
  198. INPUT(_type='text', _name='name',
  199. requires=IS_IN_SET(['max', 'john'],zero=''))
  200. the argument of IS_IN_SET must be a list or set
  201. >>> IS_IN_SET(['max', 'john'])('max')
  202. ('max', None)
  203. >>> IS_IN_SET(['max', 'john'])('massimo')
  204. ('massimo', 'value not allowed')
  205. >>> IS_IN_SET(['max', 'john'], multiple=True)(('max', 'john'))
  206. (('max', 'john'), None)
  207. >>> IS_IN_SET(['max', 'john'], multiple=True)(('bill', 'john'))
  208. (('bill', 'john'), 'value not allowed')
  209. >>> IS_IN_SET(('id1','id2'), ['first label','second label'])('id1') # Traditional way
  210. ('id1', None)
  211. >>> IS_IN_SET({'id1':'first label', 'id2':'second label'})('id1')
  212. ('id1', None)
  213. >>> import itertools
  214. >>> IS_IN_SET(itertools.chain(['1','3','5'],['2','4','6']))('1')
  215. ('1', None)
  216. >>> IS_IN_SET([('id1','first label'), ('id2','second label')])('id1') # Redundant way
  217. ('id1', None)
  218. """
  219. def __init__(
  220. self,
  221. theset,
  222. labels=None,
  223. error_message='value not allowed',
  224. multiple=False,
  225. zero='',
  226. sort=False,
  227. ):
  228. self.multiple = multiple
  229. if isinstance(theset, dict):
  230. self.theset = [str(item) for item in theset]
  231. self.labels = theset.values()
  232. elif theset and isinstance(theset, (tuple,list)) \
  233. and isinstance(theset[0], (tuple,list)) and len(theset[0])==2:
  234. self.theset = [str(item) for item,label in theset]
  235. self.labels = [str(label) for item,label in theset]
  236. else:
  237. self.theset = [str(item) for item in theset]
  238. self.labels = labels
  239. self.error_message = error_message
  240. self.zero = zero
  241. self.sort = sort
  242. def options(self):
  243. if not self.labels:
  244. items = [(k, k) for (i, k) in enumerate(self.theset)]
  245. else:
  246. items = [(k, self.labels[i]) for (i, k) in enumerate(self.theset)]
  247. if self.sort:
  248. items.sort(options_sorter)
  249. if self.zero != None and not self.multiple:
  250. items.insert(0,('',self.zero))
  251. return items
  252. def __call__(self, value):
  253. if self.multiple:
  254. ### if below was values = re.compile("[\w\-:]+").findall(str(value))
  255. if isinstance(value, (str,unicode)):
  256. values = [value]
  257. elif isinstance(value, (tuple, list)):
  258. values = value
  259. elif not value:
  260. values = []
  261. else:
  262. values = [value]
  263. failures = [x for x in values if not x in self.theset]
  264. if failures and self.theset:
  265. if self.multiple and (value == None or value == ''):
  266. return ([], None)
  267. return (value, self.error_message)
  268. if self.multiple:
  269. return (values, None)
  270. return (value, None)
  271. regex1 = re.compile('[\w_]+\.[\w_]+')
  272. regex2 = re.compile('%\((?P<name>[^\)]+)\)s')
  273. class IS_IN_DB(Validator):
  274. """
  275. example::
  276. INPUT(_type='text', _name='name',
  277. requires=IS_IN_DB(db, db.table, zero=''))
  278. used for reference fields, rendered as a dropbox
  279. """
  280. def __init__(
  281. self,
  282. dbset,
  283. field,
  284. label=None,
  285. error_message='value not in database',
  286. orderby=None,
  287. groupby=None,
  288. cache=None,
  289. multiple=False,
  290. zero='',
  291. sort=False,
  292. _and=None,
  293. ):
  294. if hasattr(dbset, 'define_table'):
  295. self.dbset = dbset()
  296. else:
  297. self.dbset = dbset
  298. self.field = field
  299. (ktable, kfield) = str(self.field).split('.')
  300. if not label:
  301. label = '%%(%s)s' % kfield
  302. if isinstance(label,str):
  303. if regex1.match(str(label)):
  304. label = '%%(%s)s' % str(label).split('.')[-1]
  305. ks = regex2.findall(label)
  306. if not kfield in ks:
  307. ks += [kfield]
  308. fields = ['%s.%s' % (ktable, k) for k in ks]
  309. else:
  310. ks = [kfield]
  311. fields =[str(f) for f in self.dbset._db[ktable]]
  312. self.fields = fields
  313. self.label = label
  314. self.ktable = ktable
  315. self.kfield = kfield
  316. self.ks = ks
  317. self.error_message = error_message
  318. self.theset = None
  319. self.orderby = orderby
  320. self.groupby = groupby
  321. self.cache = cache
  322. self.multiple = multiple
  323. self.zero = zero
  324. self.sort = sort
  325. self._and = _and
  326. def set_self_id(self, id):
  327. if self._and:
  328. self._and.record_id = id
  329. def build_set(self):
  330. if self.dbset._db._dbname != 'gql':
  331. orderby = self.orderby or ', '.join(self.fields)
  332. groupby = self.groupby
  333. dd = dict(orderby=orderby, groupby=groupby, cache=self.cache)
  334. records = self.dbset.select(*self.fields, **dd)
  335. else:
  336. import contrib.gql
  337. orderby = self.orderby\
  338. or contrib.gql.SQLXorable('|'.join([k for k in self.ks
  339. if k != 'id']))
  340. dd = dict(orderby=orderby, cache=self.cache)
  341. records = \
  342. self.dbset.select(self.dbset._db[self.ktable].ALL, **dd)
  343. self.theset = [str(r[self.kfield]) for r in records]
  344. if isinstance(self.label,str):
  345. self.labels = [self.label % dict(r) for r in records]
  346. else:
  347. self.labels = [self.label(r) for r in records]
  348. def options(self):
  349. self.build_set()
  350. items = [(k, self.labels[i]) for (i, k) in enumerate(self.theset)]
  351. if self.sort:
  352. items.sort(options_sorter)
  353. if self.zero != None and not self.multiple:
  354. items.insert(0,('',self.zero))
  355. return items
  356. def __call__(self, value):
  357. if self.multiple:
  358. if isinstance(value,list):
  359. values=value
  360. elif value:
  361. values = [value]
  362. else:
  363. values = []
  364. if not [x for x in values if not x in self.theset]:
  365. return (values, None)
  366. elif self.theset:
  367. if value in self.theset:
  368. if self._and:
  369. return self._and(value)
  370. else:
  371. return (value, None)
  372. else:
  373. (ktable, kfield) = str(self.field).split('.')
  374. field = self.dbset._db[ktable][kfield]
  375. if self.dbset(field == value).count():
  376. if self._and:
  377. return self._and(value)
  378. else:
  379. return (value, None)
  380. return (value, self.error_message)
  381. class IS_NOT_IN_DB(Validator):
  382. """
  383. example::
  384. INPUT(_type='text', _name='name', requires=IS_NOT_IN_DB(db, db.table))
  385. makes the field unique
  386. """
  387. def __init__(
  388. self,
  389. dbset,
  390. field,
  391. error_message='value already in database or empty',
  392. allowed_override=[],
  393. ):
  394. if hasattr(dbset, 'define_table'):
  395. self.dbset = dbset()
  396. else:
  397. self.dbset = dbset
  398. self.field = field
  399. self.error_message = error_message
  400. self.record_id = 0
  401. self.allowed_override = allowed_override
  402. def set_self_id(self, id):
  403. self.record_id = id
  404. def __call__(self, value):
  405. value=str(value)
  406. if not value.strip():
  407. return (value, self.error_message)
  408. if value in self.allowed_override:
  409. return (value, None)
  410. (tablename, fieldname) = str(self.field).split('.')
  411. field = self.dbset._db[tablename][fieldname]
  412. rows = self.dbset(field == value).select(limitby=(0, 1))
  413. if len(rows) > 0:
  414. if isinstance(self.record_id, dict):
  415. for f in self.record_id:
  416. if str(getattr(rows[0], f)) != str(self.record_id[f]):
  417. return (value, self.error_message)
  418. elif str(rows[0].id) != str(self.record_id):
  419. return (value, self.error_message)
  420. return (value, None)
  421. class IS_INT_IN_RANGE(Validator):
  422. """
  423. Determine that the argument is (or can be represented as) an int,
  424. and that it falls within the specified range. The range is interpreted
  425. in the Pythonic way, so the test is: min <= value < max.
  426. The minimum and maximum limits can be None, meaning no lower or upper limit,
  427. respectively.
  428. example::
  429. INPUT(_type='text', _name='name', requires=IS_INT_IN_RANGE(0, 10))
  430. >>> IS_INT_IN_RANGE(1,5)('4')
  431. (4, None)
  432. >>> IS_INT_IN_RANGE(1,5)(4)
  433. (4, None)
  434. >>> IS_INT_IN_RANGE(1,5)(1)
  435. (1, None)
  436. >>> IS_INT_IN_RANGE(1,5)(5)
  437. (5, 'enter an integer between 1 and 4')
  438. >>> IS_INT_IN_RANGE(1,5)(5)
  439. (5, 'enter an integer between 1 and 4')
  440. >>> IS_INT_IN_RANGE(1,5)(3.5)
  441. (3, 'enter an integer between 1 and 4')
  442. >>> IS_INT_IN_RANGE(None,5)('4')
  443. (4, None)
  444. >>> IS_INT_IN_RANGE(None,5)('6')
  445. (6, 'enter an integer less than or equal to 4')
  446. >>> IS_INT_IN_RANGE(1,None)('4')
  447. (4, None)
  448. >>> IS_INT_IN_RANGE(1,None)('0')
  449. (0, 'enter an integer greater than or equal to 1')
  450. >>> IS_INT_IN_RANGE()(6)
  451. (6, None)
  452. >>> IS_INT_IN_RANGE()('abc')
  453. ('abc', 'enter an integer')
  454. """
  455. def __init__(
  456. self,
  457. minimum=None,
  458. maximum=None,
  459. error_message=None,
  460. ):
  461. self.minimum = self.maximum = None
  462. if minimum is None:
  463. if maximum is None:
  464. if error_message is None:
  465. self.error_message = 'enter an integer'
  466. else:
  467. self.maximum = int(maximum)
  468. if error_message is None:
  469. error_message = 'enter an integer less than or equal to %(max)s'
  470. self.error_message = error_message % dict(max=self.maximum-1)
  471. elif maximum is None:
  472. self.minimum = int(minimum)
  473. if error_message is None:
  474. error_message = 'enter an integer greater than or equal to %(min)s'
  475. self.error_message = error_message % dict(min=self.minimum)
  476. else:
  477. self.minimum = int(minimum)
  478. self.maximum = int(maximum)
  479. if error_message is None:
  480. error_message = 'enter an integer between %(min)s and %(max)s'
  481. self.error_message = error_message % dict(min=self.minimum, max=self.maximum-1)
  482. def __call__(self, value):
  483. try:
  484. fvalue = float(value)
  485. value = int(value)
  486. if value != fvalue:
  487. return (value, self.error_message)
  488. if self.minimum is None:
  489. if self.maximum is None or value < self.maximum:
  490. return (value, None)
  491. elif self.maximum is None:
  492. if value >= self.minimum:
  493. return (value, None)
  494. elif self.minimum <= value < self.maximum:
  495. return (value, None)
  496. except ValueError:
  497. pass
  498. return (value, self.error_message)
  499. class IS_FLOAT_IN_RANGE(Validator):
  500. """
  501. Determine that the argument is (or can be represented as) a float,
  502. and that it falls within the specified inclusive range.
  503. The comparison is made with native arithmetic.
  504. The minimum and maximum limits can be None, meaning no lower or upper limit,
  505. respectively.
  506. example::
  507. INPUT(_type='text', _name='name', requires=IS_FLOAT_IN_RANGE(0, 10))
  508. >>> IS_FLOAT_IN_RANGE(1,5)('4')
  509. (4.0, None)
  510. >>> IS_FLOAT_IN_RANGE(1,5)(4)
  511. (4.0, None)
  512. >>> IS_FLOAT_IN_RANGE(1,5)(1)
  513. (1.0, None)
  514. >>> IS_FLOAT_IN_RANGE(1,5)(5.1)
  515. (5.0999999999999996, 'enter a number between 1.0 and 5.0')
  516. >>> IS_FLOAT_IN_RANGE(1,5)(6.0)
  517. (6.0, 'enter a number between 1.0 and 5.0')
  518. >>> IS_FLOAT_IN_RANGE(1,5)(3.5)
  519. (3.5, None)
  520. >>> IS_FLOAT_IN_RANGE(1,None)(3.5)
  521. (3.5, None)
  522. >>> IS_FLOAT_IN_RANGE(None,5)(3.5)
  523. (3.5, None)
  524. >>> IS_FLOAT_IN_RANGE(1,None)(0.5)
  525. (0.5, 'enter a number greater than or equal to 1.0')
  526. >>> IS_FLOAT_IN_RANGE(None,5)(6.5)
  527. (6.5, 'enter a number less than or equal to 5.0')
  528. >>> IS_FLOAT_IN_RANGE()(6.5)
  529. (6.5, None)
  530. >>> IS_FLOAT_IN_RANGE()('abc')
  531. ('abc', 'enter a number')
  532. """
  533. def __init__(
  534. self,
  535. minimum=None,
  536. maximum=None,
  537. error_message=None,
  538. dot='.'
  539. ):
  540. self.minimum = self.maximum = None
  541. self.dot = dot
  542. if minimum is None:
  543. if maximum is None:
  544. if error_message is None:
  545. error_message = 'enter a number'
  546. else:
  547. self.maximum = float(maximum)
  548. if error_message is None:
  549. error_message = 'enter a number less than or equal to %(max)s'
  550. elif maximum is None:
  551. self.minimum = float(minimum)
  552. if error_message is None:
  553. error_message = 'enter a number greater than or equal to %(min)s'
  554. else:
  555. self.minimum = float(minimum)
  556. self.maximum = float(maximum)
  557. if error_message is None:
  558. error_message = 'enter a number between %(min)s and %(max)s'
  559. self.error_message = error_message % dict(min=self.minimum, max=self.maximum)
  560. def __call__(self, value):
  561. try:
  562. if self.dot=='.':
  563. fvalue = float(value)
  564. else:
  565. fvalue = float(str(value).replace(self.dot,'.'))
  566. if self.minimum is None:
  567. if self.maximum is None or fvalue <= self.maximum:
  568. return (fvalue, None)
  569. elif self.maximum is None:
  570. if fvalue >= self.minimum:
  571. return (fvalue, None)
  572. elif self.minimum <= fvalue <= self.maximum:
  573. return (fvalue, None)
  574. except (ValueError, TypeError):
  575. pass
  576. return (value, self.error_message)
  577. def formatter(self,value):
  578. if self.dot=='.':
  579. return str(value)
  580. else:
  581. return str(value).replace('.',self.dot)
  582. class IS_DECIMAL_IN_RANGE(Validator):
  583. """
  584. Determine that the argument is (or can be represented as) a Python Decimal,
  585. and that it falls within the specified inclusive range.
  586. The comparison is made with Python Decimal arithmetic.
  587. The minimum and maximum limits can be None, meaning no lower or upper limit,
  588. respectively.
  589. example::
  590. INPUT(_type='text', _name='name', requires=IS_DECIMAL_IN_RANGE(0, 10))
  591. >>> IS_DECIMAL_IN_RANGE(1,5)('4')
  592. ('4', None)
  593. >>> IS_DECIMAL_IN_RANGE(1,5)(4)
  594. (4, None)
  595. >>> IS_DECIMAL_IN_RANGE(1,5)(1)
  596. (1, None)
  597. >>> IS_DECIMAL_IN_RANGE(1,5)(5.1)
  598. (5.0999999999999996, 'enter a number between 1 and 5')
  599. >>> IS_DECIMAL_IN_RANGE(5.1,6)(5.1)
  600. (5.0999999999999996, None)
  601. >>> IS_DECIMAL_IN_RANGE(5.1,6)('5.1')
  602. ('5.1', None)
  603. >>> IS_DECIMAL_IN_RANGE(1,5)(6.0)
  604. (6.0, 'enter a number between 1 and 5')
  605. >>> IS_DECIMAL_IN_RANGE(1,5)(3.5)
  606. (3.5, None)
  607. >>> IS_DECIMAL_IN_RANGE(1.5,5.5)(3.5)
  608. (3.5, None)
  609. >>> IS_DECIMAL_IN_RANGE(1.5,5.5)(6.5)
  610. (6.5, 'enter a number between 1.5 and 5.5')
  611. >>> IS_DECIMAL_IN_RANGE(1.5,None)(6.5)
  612. (6.5, None)
  613. >>> IS_DECIMAL_IN_RANGE(1.5,None)(0.5)
  614. (0.5, 'enter a number greater than or equal to 1.5')
  615. >>> IS_DECIMAL_IN_RANGE(None,5.5)(4.5)
  616. (4.5, None)
  617. >>> IS_DECIMAL_IN_RANGE(None,5.5)(6.5)
  618. (6.5, 'enter a number less than or equal to 5.5')
  619. >>> IS_DECIMAL_IN_RANGE()(6.5)
  620. (6.5, None)
  621. >>> IS_DECIMAL_IN_RANGE()('abc')
  622. ('abc', 'enter a decimal number')
  623. """
  624. def __init__(
  625. self,
  626. minimum=None,
  627. maximum=None,
  628. error_message=None,
  629. dot='.'
  630. ):
  631. self.minimum = self.maximum = None
  632. self.dot = dot
  633. if minimum is None:
  634. if maximum is None:
  635. if error_message is None:
  636. error_message = 'enter a decimal number'
  637. else:
  638. self.maximum = decimal.Decimal(str(maximum))
  639. if error_message is None:
  640. error_message = 'enter a number less than or equal to %(max)s'
  641. elif maximum is None:
  642. self.minimum = decimal.Decimal(str(minimum))
  643. if error_message is None:
  644. error_message = 'enter a number greater than or equal to %(min)s'
  645. else:
  646. self.minimum = decimal.Decimal(str(minimum))
  647. self.maximum = decimal.Decimal(str(maximum))
  648. if error_message is None:
  649. error_message = 'enter a number between %(min)s and %(max)s'
  650. self.error_message = error_message % dict(min=self.minimum, max=self.maximum)
  651. def __call__(self, value):
  652. try:
  653. if self.dot=='.':
  654. v = decimal.Decimal(str(value))
  655. else:
  656. v = decimal.Decimal(str(value).replace(self.dot,'.'))
  657. if self.minimum is None:
  658. if self.maximum is None or v <= self.maximum:
  659. return (v, None)
  660. elif self.maximum is None:
  661. if v >= self.minimum:
  662. return (v, None)
  663. elif self.minimum <= v <= self.maximum:
  664. return (v, None)
  665. except (ValueError, TypeError, decimal.InvalidOperation):
  666. pass
  667. return (value, self.error_message)
  668. def formatter(self, value):
  669. return str(value).replace('.',self.dot)
  670. def is_empty(value, empty_regex=None):
  671. "test empty field"
  672. if isinstance(value, (str, unicode)):
  673. value = value.strip()
  674. if empty_regex is not None and empty_regex.match(value):
  675. value = ''
  676. if value == None or value == '' or value == []:
  677. return (value, True)
  678. return (value, False)
  679. class IS_NOT_EMPTY(Validator):
  680. """
  681. example::
  682. INPUT(_type='text', _name='name', requires=IS_NOT_EMPTY())
  683. >>> IS_NOT_EMPTY()(1)
  684. (1, None)
  685. >>> IS_NOT_EMPTY()(0)
  686. (0, None)
  687. >>> IS_NOT_EMPTY()('x')
  688. ('x', None)
  689. >>> IS_NOT_EMPTY()(' x ')
  690. ('x', None)
  691. >>> IS_NOT_EMPTY()(None)
  692. (None, 'enter a value')
  693. >>> IS_NOT_EMPTY()('')
  694. ('', 'enter a value')
  695. >>> IS_NOT_EMPTY()(' ')
  696. ('', 'enter a value')
  697. >>> IS_NOT_EMPTY()(' \\n\\t')
  698. ('', 'enter a value')
  699. >>> IS_NOT_EMPTY()([])
  700. ([], 'enter a value')
  701. >>> IS_NOT_EMPTY(empty_regex='def')('def')
  702. ('', 'enter a value')
  703. >>> IS_NOT_EMPTY(empty_regex='de[fg]')('deg')
  704. ('', 'enter a value')
  705. >>> IS_NOT_EMPTY(empty_regex='def')('abc')
  706. ('abc', None)
  707. """
  708. def __init__(self, error_message='enter a value', empty_regex=None):
  709. self.error_message = error_message
  710. if empty_regex is not None:
  711. self.empty_regex = re.compile(empty_regex)
  712. else:
  713. self.empty_regex = None
  714. def __call__(self, value):
  715. value, empty = is_empty(value, empty_regex=self.empty_regex)
  716. if empty:
  717. return (value, self.error_message)
  718. return (value, None)
  719. class IS_ALPHANUMERIC(IS_MATCH):
  720. """
  721. example::
  722. INPUT(_type='text', _name='name', requires=IS_ALPHANUMERIC())
  723. >>> IS_ALPHANUMERIC()('1')
  724. ('1', None)
  725. >>> IS_ALPHANUMERIC()('')
  726. ('', None)
  727. >>> IS_ALPHANUMERIC()('A_a')
  728. ('A_a', None)
  729. >>> IS_ALPHANUMERIC()('!')
  730. ('!', 'enter only letters, numbers, and underscore')
  731. """
  732. def __init__(self, error_message='enter only letters, numbers, and underscore'):
  733. IS_MATCH.__init__(self, '^[\w]*$', error_message)
  734. class IS_EMAIL(Validator):
  735. """
  736. Checks if field's value is a valid email address. Can be set to disallow
  737. or force addresses from certain domain(s).
  738. Email regex adapted from
  739. http://haacked.com/archive/2007/08/21/i-knew-how-to-validate-an-email-address-until-i.aspx,
  740. generally following the RFCs, except that we disallow quoted strings
  741. and permit underscores and leading numerics in subdomain labels
  742. Arguments:
  743. - banned: regex text for disallowed address domains
  744. - forced: regex text for required address domains
  745. Both arguments can also be custom objects with a match(value) method.
  746. Examples::
  747. #Check for valid email address:
  748. INPUT(_type='text', _name='name',
  749. requires=IS_EMAIL())
  750. #Check for valid email address that can't be from a .com domain:
  751. INPUT(_type='text', _name='name',
  752. requires=IS_EMAIL(banned='^.*\.com(|\..*)$'))
  753. #Check for valid email address that must be from a .edu domain:
  754. INPUT(_type='text', _name='name',
  755. requires=IS_EMAIL(forced='^.*\.edu(|\..*)$'))
  756. >>> IS_EMAIL()('a@b.com')
  757. ('a@b.com', None)
  758. >>> IS_EMAIL()('abc@def.com')
  759. ('abc@def.com', None)
  760. >>> IS_EMAIL()('abc@3def.com')
  761. ('abc@3def.com', None)
  762. >>> IS_EMAIL()('abc@def.us')
  763. ('abc@def.us', None)
  764. >>> IS_EMAIL()('abc@d_-f.us')
  765. ('abc@d_-f.us', None)
  766. >>> IS_EMAIL()('@def.com') # missing name
  767. ('@def.com', 'enter a valid email address')
  768. >>> IS_EMAIL()('"abc@def".com') # quoted name
  769. ('"abc@def".com', 'enter a valid email address')
  770. >>> IS_EMAIL()('abc+def.com') # no @
  771. ('abc+def.com', 'enter a valid email address')
  772. >>> IS_EMAIL()('abc@def.x') # one-char TLD
  773. ('abc@def.x', 'enter a valid email address')
  774. >>> IS_EMAIL()('abc@def.12') # numeric TLD
  775. ('abc@def.12', 'enter a valid email address')
  776. >>> IS_EMAIL()('abc@def..com') # double-dot in domain
  777. ('abc@def..com', 'enter a valid email address')
  778. >>> IS_EMAIL()('abc@.def.com') # dot starts domain
  779. ('abc@.def.com', 'enter a valid email address')
  780. >>> IS_EMAIL()('abc@def.c_m') # underscore in TLD
  781. ('abc@def.c_m', 'enter a valid email address')
  782. >>> IS_EMAIL()('NotAnEmail') # missing @
  783. ('NotAnEmail', 'enter a valid email address')
  784. >>> IS_EMAIL()('abc@NotAnEmail') # missing TLD
  785. ('abc@NotAnEmail', 'enter a valid email address')
  786. >>> IS_EMAIL()('customer/department@example.com')
  787. ('customer/department@example.com', None)
  788. >>> IS_EMAIL()('$A12345@example.com')
  789. ('$A12345@example.com', None)
  790. >>> IS_EMAIL()('!def!xyz%abc@example.com')
  791. ('!def!xyz%abc@example.com', None)
  792. >>> IS_EMAIL()('_Yosemite.Sam@example.com')
  793. ('_Yosemite.Sam@example.com', None)
  794. >>> IS_EMAIL()('~@example.com')
  795. ('~@example.com', None)
  796. >>> IS_EMAIL()('.wooly@example.com') # dot starts name
  797. ('.wooly@example.com', 'enter a valid email address')
  798. >>> IS_EMAIL()('wo..oly@example.com') # adjacent dots in name
  799. ('wo..oly@example.com', 'enter a valid email address')
  800. >>> IS_EMAIL()('pootietang.@example.com') # dot ends name
  801. ('pootietang.@example.com', 'enter a valid email address')
  802. >>> IS_EMAIL()('.@example.com') # name is bare dot
  803. ('.@example.com', 'enter a valid email address')
  804. >>> IS_EMAIL()('Ima.Fool@example.com')
  805. ('Ima.Fool@example.com', None)
  806. >>> IS_EMAIL()('Ima Fool@example.com') # space in name
  807. ('Ima Fool@example.com', 'enter a valid email address')
  808. >>> IS_EMAIL()('localguy@localhost') # localhost as domain
  809. ('localguy@localhost', None)
  810. """
  811. regex = re.compile('''
  812. ^(?!\.) # name may not begin with a dot
  813. (
  814. [-a-z0-9!\#$%&'*+/=?^_`{|}~] # all legal characters except dot
  815. |
  816. (?<!\.)\. # single dots only
  817. )+
  818. (?<!\.) # name may not end with a dot
  819. @
  820. (
  821. localhost
  822. |
  823. (
  824. [a-z0-9] # [sub]domain begins with alphanumeric
  825. (
  826. [-\w]* # alphanumeric, underscore, dot, hyphen
  827. [a-z0-9] # ending alphanumeric
  828. )?
  829. \. # ending dot
  830. )+
  831. [a-z]{2,} # TLD alpha-only
  832. )$
  833. ''', re.VERBOSE|re.IGNORECASE)
  834. 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)
  835. def __init__(self,
  836. banned=None,
  837. forced=None,
  838. error_message='enter a valid email address'):
  839. if isinstance(banned, str):
  840. banned = re.compile(banned)
  841. if isinstance(forced, str):
  842. forced = re.compile(forced)
  843. self.banned = banned
  844. self.forced = forced
  845. self.error_message = error_message
  846. def __call__(self, value):
  847. match = self.regex.match(value)
  848. if match:
  849. domain = value.split('@')[1]
  850. if (not self.banned or not self.banned.match(domain)) \
  851. and (not self.forced or self.forced.match(domain)):
  852. return (value, None)
  853. return (value, self.error_message)
  854. # URL scheme source:
  855. # <http://en.wikipedia.org/wiki/URI_scheme> obtained on 2008-Nov-10
  856. official_url_schemes = [
  857. 'aaa',
  858. 'aaas',
  859. 'acap',
  860. 'cap',
  861. 'cid',
  862. 'crid',
  863. 'data',
  864. 'dav',
  865. 'dict',
  866. 'dns',
  867. 'fax',
  868. 'file',
  869. 'ftp',
  870. 'go',
  871. 'gopher',
  872. 'h323',
  873. 'http',
  874. 'https',
  875. 'icap',
  876. 'im',
  877. 'imap',
  878. 'info',
  879. 'ipp',
  880. 'iris',
  881. 'iris.beep',
  882. 'iris.xpc',
  883. 'iris.xpcs',
  884. 'iris.lws',
  885. 'ldap',
  886. 'mailto',
  887. 'mid',
  888. 'modem',
  889. 'msrp',
  890. 'msrps',
  891. 'mtqp',
  892. 'mupdate',
  893. 'news',
  894. 'nfs',
  895. 'nntp',
  896. 'opaquelocktoken',
  897. 'pop',
  898. 'pres',
  899. 'prospero',
  900. 'rtsp',
  901. 'service',
  902. 'shttp',
  903. 'sip',
  904. 'sips',
  905. 'snmp',
  906. 'soap.beep',
  907. 'soap.beeps',
  908. 'tag',
  909. 'tel',
  910. 'telnet',
  911. 'tftp',
  912. 'thismessage',
  913. 'tip',
  914. 'tv',
  915. 'urn',
  916. 'vemmi',
  917. 'wais',
  918. 'xmlrpc.beep',
  919. 'xmlrpc.beep',
  920. 'xmpp',
  921. 'z39.50r',
  922. 'z39.50s',
  923. ]
  924. unofficial_url_schemes = [
  925. 'about',
  926. 'adiumxtra',
  927. 'aim',
  928. 'afp',
  929. 'aw',
  930. 'callto',
  931. 'chrome',
  932. 'cvs',
  933. 'ed2k',
  934. 'feed',
  935. 'fish',
  936. 'gg',
  937. 'gizmoproject',
  938. 'iax2',
  939. 'irc',
  940. 'ircs',
  941. 'itms',
  942. 'jar',
  943. 'javascript',
  944. 'keyparc',
  945. 'lastfm',
  946. 'ldaps',
  947. 'magnet',
  948. 'mms',
  949. 'msnim',
  950. 'mvn',
  951. 'notes',
  952. 'nsfw',
  953. 'psyc',
  954. 'paparazzi:http',
  955. 'rmi',
  956. 'rsync',
  957. 'secondlife',
  958. 'sgn',
  959. 'skype',
  960. 'ssh',
  961. 'sftp',
  962. 'smb',
  963. 'sms',
  964. 'soldat',
  965. 'steam',
  966. 'svn',
  967. 'teamspeak',
  968. 'unreal',
  969. 'ut2004',
  970. 'ventrilo',
  971. 'view-source',
  972. 'webcal',
  973. 'wyciwyg',
  974. 'xfire',
  975. 'xri',
  976. 'ymsgr',
  977. ]
  978. all_url_schemes = [None] + official_url_schemes + unofficial_url_schemes
  979. http_schemes = [None, 'http', 'https']
  980. # This regex comes from RFC 2396, Appendix B. It's used to split a URL into
  981. # its component parts
  982. # Here are the regex groups that it extracts:
  983. # scheme = group(2)
  984. # authority = group(4)
  985. # path = group(5)
  986. # query = group(7)
  987. # fragment = group(9)
  988. url_split_regex = \
  989. re.compile('^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?')
  990. # Defined in RFC 3490, Section 3.1, Requirement #1
  991. # Use this regex to split the authority component of a unicode URL into
  992. # its component labels
  993. label_split_regex = re.compile(u'[\u002e\u3002\uff0e\uff61]')
  994. def escape_unicode(string):
  995. '''
  996. Converts a unicode string into US-ASCII, using a simple conversion scheme.
  997. Each unicode character that does not have a US-ASCII equivalent is
  998. converted into a URL escaped form based on its hexadecimal value.
  999. For example, the unicode character '\u4e86' will become the string '%4e%86'
  1000. :param string: unicode string, the unicode string to convert into an
  1001. escaped US-ASCII form
  1002. :returns: the US-ASCII escaped form of the inputted string
  1003. :rtype: string
  1004. @author: Jonathan Benn
  1005. '''
  1006. returnValue = StringIO()
  1007. for character in string:
  1008. code = ord(character)
  1009. if code > 0x7F:
  1010. hexCode = hex(code)
  1011. returnValue.write('%' + hexCode[2:4] + '%' + hexCode[4:6])
  1012. else:
  1013. returnValue.write(character)
  1014. return returnValue.getvalue()
  1015. def unicode_to_ascii_authority(authority):
  1016. '''
  1017. Follows the steps in RFC 3490, Section 4 to convert a unicode authority
  1018. string into its ASCII equivalent.
  1019. For example, u'www.Alliancefran\xe7aise.nu' will be converted into
  1020. 'www.xn--alliancefranaise-npb.nu'
  1021. :param authority: unicode string, the URL authority component to convert,
  1022. e.g. u'www.Alliancefran\xe7aise.nu'
  1023. :returns: the US-ASCII character equivalent to the inputed authority,
  1024. e.g. 'www.xn--alliancefranaise-npb.nu'
  1025. :rtype: string
  1026. :raises Exception: if the function is not able to convert the inputed
  1027. authority
  1028. @author: Jonathan Benn
  1029. '''
  1030. #RFC 3490, Section 4, Step 1
  1031. #The encodings.idna Python module assumes that AllowUnassigned == True
  1032. #RFC 3490, Section 4, Step 2
  1033. labels = label_split_regex.split(authority)
  1034. #RFC 3490, Section 4, Step 3
  1035. #The encodings.idna Python module assumes that UseSTD3ASCIIRules == False
  1036. #RFC 3490, Section 4, Step 4
  1037. #We use the ToASCII operation because we are about to put the authority
  1038. #into an IDN-unaware slot
  1039. asciiLabels = []
  1040. try:
  1041. import encodings.idna
  1042. for label in labels:
  1043. if label:
  1044. asciiLabels.append(encodings.idna.ToASCII(label))
  1045. else:
  1046. #encodings.idna.ToASCII does not accept an empty string, but
  1047. #it is necessary for us to allow for empty labels so that we
  1048. #don't modify the URL
  1049. asciiLabels.append('')
  1050. except:
  1051. asciiLabels=[str(label) for label in labels]
  1052. #RFC 3490, Section 4, Step 5
  1053. return str(reduce(lambda x, y: x + unichr(0x002E) + y, asciiLabels))
  1054. def unicode_to_ascii_url(url, prepend_scheme):
  1055. '''
  1056. Converts the inputed unicode url into a US-ASCII equivalent. This function
  1057. goes a little beyond RFC 3490, which is limited in scope to the domain name
  1058. (authority) only. Here, the functionality is expanded to what was observed
  1059. on Wikipedia on 2009-Jan-22:
  1060. Component Can Use Unicode?
  1061. --------- ----------------
  1062. scheme No
  1063. authority Yes
  1064. path Yes
  1065. query Yes
  1066. fragment No
  1067. The authority component gets converted to punycode, but occurrences of
  1068. unicode in other components get converted into a pair of URI escapes (we
  1069. assume 4-byte unicode). E.g. the unicode character U+4E2D will be
  1070. converted into '%4E%2D'. Testing with Firefox v3.0.5 has shown that it can
  1071. understand this kind of URI encoding.
  1072. :param url: unicode string, the URL to convert from unicode into US-ASCII
  1073. :param prepend_scheme: string, a protocol scheme to prepend to the URL if
  1074. we're having trouble parsing it.
  1075. e.g. "http". Input None to disable this functionality
  1076. :returns: a US-ASCII equivalent of the inputed url
  1077. :rtype: string
  1078. @author: Jonathan Benn
  1079. '''
  1080. #convert the authority component of the URL into an ASCII punycode string,
  1081. #but encode the rest using the regular URI character encoding
  1082. groups = url_split_regex.match(url).groups()
  1083. #If no authority was found
  1084. if not groups[3]:
  1085. #Try appending a scheme to see if that fixes the problem
  1086. scheme_to_prepend = prepend_scheme or 'http'
  1087. groups = url_split_regex.match(
  1088. unicode(scheme_to_prepend) + u'://' + url).groups()
  1089. #if we still can't find the authority
  1090. if not groups[3]:
  1091. raise Exception('No authority component found, '+ \
  1092. 'could not decode unicode to US-ASCII')
  1093. #We're here if we found an authority, let's rebuild the URL
  1094. scheme = groups[1]
  1095. authority = groups[3]
  1096. path = groups[4] or ''
  1097. query = groups[5] or ''
  1098. fragment = groups[7] or ''
  1099. if prepend_scheme:
  1100. scheme = str(scheme) + '://'
  1101. else:
  1102. scheme = ''
  1103. return scheme + unicode_to_ascii_authority(authority) +\
  1104. escape_unicode(path) + escape_unicode(query) + str(fragment)
  1105. class IS_GENERIC_URL(Validator):
  1106. """
  1107. Rejects a URL string if any of the following is true:
  1108. * The string is empty or None
  1109. * The string uses characters that are not allowed in a URL
  1110. * The URL scheme specified (if one is specified) is not valid
  1111. Based on RFC 2396: http://www.faqs.org/rfcs/rfc2396.html
  1112. This function only checks the URL's syntax. It does not check that the URL
  1113. points to a real document, for example, or that it otherwise makes sense
  1114. semantically. This function does automatically prepend 'http://' in front
  1115. of a URL if and only if that's necessary to successfully parse the URL.
  1116. Please note that a scheme will be prepended only for rare cases
  1117. (e.g. 'google.ca:80')
  1118. The list of allowed schemes is customizable with the allowed_schemes
  1119. parameter. If you exclude None from the list, then abbreviated URLs
  1120. (lacking a scheme such as 'http') will be rejected.
  1121. The default prepended scheme is customizable with the prepend_scheme
  1122. parameter. If you set prepend_scheme to None then prepending will be
  1123. disabled. URLs that require prepending to parse will still be accepted,
  1124. but the return value will not be modified.
  1125. @author: Jonathan Benn
  1126. >>> IS_GENERIC_URL()('http://user@abc.com')
  1127. ('http://user@abc.com', None)
  1128. """
  1129. def __init__(
  1130. self,
  1131. error_message='enter a valid URL',
  1132. allowed_schemes=None,
  1133. prepend_scheme=None,
  1134. ):
  1135. """
  1136. :param error_message: a string, the error message to give the end user
  1137. if the URL does not validate
  1138. :param allowed_schemes: a list containing strings or None. Each element
  1139. is a scheme the inputed URL is allowed to use
  1140. :param prepend_scheme: a string, this scheme is prepended if it's
  1141. necessary to make the URL valid
  1142. """
  1143. self.error_message = error_message
  1144. if allowed_schemes == None:
  1145. self.allowed_schemes = all_url_schemes
  1146. else:
  1147. self.allowed_schemes = allowed_schemes
  1148. self.prepend_scheme = prepend_scheme
  1149. if self.prepend_scheme not in self.allowed_schemes:
  1150. raise SyntaxError, \
  1151. "prepend_scheme='%s' is not in allowed_schemes=%s" \
  1152. % (self.prepend_scheme, self.allowed_schemes)
  1153. def __call__(self, value):
  1154. """
  1155. :param value: a string, the URL to validate
  1156. :returns: a tuple, where tuple[0] is the inputed value (possible
  1157. prepended with prepend_scheme), and tuple[1] is either
  1158. None (success!) or the string error_message
  1159. """
  1160. try:
  1161. # if the URL does not misuse the '%' character
  1162. if not re.compile(
  1163. 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]$"
  1164. ).search(value):
  1165. # if the URL is only composed of valid characters
  1166. if re.compile(
  1167. r"[A-Za-z0-9;/?:@&=+$,\-_\.!~*'\(\)%#]+$").match(value):
  1168. # Then split up the URL into its components and check on
  1169. # the scheme
  1170. scheme = url_split_regex.match(value).group(2)
  1171. # Clean up the scheme before we check it
  1172. if scheme != None:
  1173. scheme = urllib.unquote(scheme).lower()
  1174. # If the scheme really exists
  1175. if scheme in self.allowed_schemes:
  1176. # Then the URL is valid
  1177. return (value, None)
  1178. else:
  1179. # else, for the possible case of abbreviated URLs with
  1180. # ports, check to see if adding a valid scheme fixes
  1181. # the problem (but only do this if it doesn't have
  1182. # one already!)
  1183. if not re.compile('://').search(value) and None\
  1184. in self.allowed_schemes:
  1185. schemeToUse = self.prepend_scheme or 'http'
  1186. prependTest = self.__call__(schemeToUse
  1187. + '://' + value)
  1188. # if the prepend test succeeded
  1189. if prependTest[1] == None:
  1190. # if prepending in the output is enabled
  1191. if self.prepend_scheme:
  1192. return prependTest
  1193. else:
  1194. # else return the original,
  1195. # non-prepended value
  1196. return (value, None)
  1197. except:
  1198. pass
  1199. # else the URL is not valid
  1200. return (value, self.error_message)
  1201. # Sources (obtained 2008-Nov-11):
  1202. # http://en.wikipedia.org/wiki/Top-level_domain
  1203. # http://www.iana.org/domains/root/db/
  1204. official_top_level_domains = [
  1205. 'ac',
  1206. 'ad',
  1207. 'ae',
  1208. 'aero',
  1209. 'af',
  1210. 'ag',
  1211. 'ai',
  1212. 'al',
  1213. 'am',
  1214. 'an',
  1215. 'ao',
  1216. 'aq',
  1217. 'ar',
  1218. 'arpa',
  1219. 'as',
  1220. 'asia',
  1221. 'at',
  1222. 'au',
  1223. 'aw',
  1224. 'ax',
  1225. 'az',
  1226. 'ba',
  1227. 'bb',
  1228. 'bd',
  1229. 'be',
  1230. 'bf',
  1231. 'bg',
  1232. 'bh',
  1233. 'bi',
  1234. 'biz',
  1235. 'bj',
  1236. 'bl',
  1237. 'bm',
  1238. 'bn',
  1239. 'bo',
  1240. 'br',
  1241. 'bs',
  1242. 'bt',
  1243. 'bv',
  1244. 'bw',
  1245. 'by',
  1246. 'bz',
  1247. 'ca',
  1248. 'cat',
  1249. 'cc',
  1250. 'cd',
  1251. 'cf',
  1252. 'cg',
  1253. 'ch',
  1254. 'ci',
  1255. 'ck',
  1256. 'cl',
  1257. 'cm',
  1258. 'cn',
  1259. 'co',
  1260. 'com',
  1261. 'coop',
  1262. 'cr',
  1263. 'cu',
  1264. 'cv',
  1265. 'cx',
  1266. 'cy',
  1267. 'cz',
  1268. 'de',
  1269. 'dj',
  1270. 'dk',
  1271. 'dm',
  1272. 'do',
  1273. 'dz',
  1274. 'ec',
  1275. 'edu',
  1276. 'ee',
  1277. 'eg',
  1278. 'eh',
  1279. 'er',
  1280. 'es',
  1281. 'et',
  1282. 'eu',
  1283. 'example',
  1284. 'fi',
  1285. 'fj',
  1286. 'fk',
  1287. 'fm',
  1288. 'fo',
  1289. 'fr',
  1290. 'ga',
  1291. 'gb',
  1292. 'gd',
  1293. 'ge',
  1294. 'gf',
  1295. 'gg',
  1296. 'gh',
  1297. 'gi',
  1298. 'gl',
  1299. 'gm',
  1300. 'gn',
  1301. 'gov',
  1302. 'gp',
  1303. 'gq',
  1304. 'gr',
  1305. 'gs',
  1306. 'gt',
  1307. 'gu',
  1308. 'gw',
  1309. 'gy',
  1310. 'hk',
  1311. 'hm',
  1312. 'hn',
  1313. 'hr',
  1314. 'ht',
  1315. 'hu',
  1316. 'id',
  1317. 'ie',
  1318. 'il',
  1319. 'im',
  1320. 'in',
  1321. 'info',
  1322. 'int',
  1323. 'invalid',
  1324. 'io',
  1325. 'iq',
  1326. 'ir',
  1327. 'is',
  1328. 'it',
  1329. 'je',
  1330. 'jm',
  1331. 'jo',
  1332. 'jobs',
  1333. 'jp',
  1334. 'ke',
  1335. 'kg',
  1336. 'kh',
  1337. 'ki',
  1338. 'km',
  1339. 'kn',
  1340. 'kp',
  1341. 'kr',
  1342. 'kw',
  1343. 'ky',
  1344. 'kz',
  1345. 'la',
  1346. 'lb',
  1347. 'lc',
  1348. 'li',
  1349. 'lk',
  1350. 'localhost',
  1351. 'lr',
  1352. 'ls',
  1353. 'lt',
  1354. 'lu',
  1355. 'lv',
  1356. 'ly',
  1357. 'ma',
  1358. 'mc',
  1359. 'md',
  1360. 'me',
  1361. 'mf',
  1362. 'mg',
  1363. 'mh',
  1364. 'mil',
  1365. 'mk',
  1366. 'ml',
  1367. 'mm',
  1368. 'mn',
  1369. 'mo',
  1370. 'mobi',
  1371. 'mp',
  1372. 'mq',
  1373. 'mr',
  1374. 'ms',
  1375. 'mt',
  1376. 'mu',
  1377. 'museum',
  1378. 'mv',
  1379. 'mw',
  1380. 'mx',
  1381. 'my',
  1382. 'mz',
  1383. 'na',
  1384. 'name',
  1385. 'nc',
  1386. 'ne',
  1387. 'net',
  1388. 'nf',
  1389. 'ng',
  1390. 'ni',
  1391. 'nl',
  1392. 'no',
  1393. 'np',
  1394. 'nr',
  1395. 'nu',
  1396. 'nz',
  1397. 'om',
  1398. 'org',
  1399. 'pa',
  1400. 'pe',
  1401. 'pf',
  1402. 'pg',
  1403. 'ph',
  1404. 'pk',
  1405. 'pl',
  1406. 'pm',
  1407. 'pn',
  1408. 'pr',
  1409. 'pro',
  1410. 'ps',
  1411. 'pt',
  1412. 'pw',
  1413. 'py',
  1414. 'qa',
  1415. 're',
  1416. 'ro',
  1417. 'rs',
  1418. 'ru',
  1419. 'rw',
  1420. 'sa',
  1421. 'sb',
  1422. 'sc',
  1423. 'sd',
  1424. 'se',
  1425. 'sg',
  1426. 'sh',
  1427. 'si',
  1428. 'sj',
  1429. 'sk',
  1430. 'sl',
  1431. 'sm',
  1432. 'sn',
  1433. 'so',
  1434. 'sr',
  1435. 'st',
  1436. 'su',
  1437. 'sv',
  1438. 'sy',
  1439. 'sz',
  1440. 'tc',
  1441. 'td',
  1442. 'tel',
  1443. 'test',
  1444. 'tf',
  1445. 'tg',
  1446. 'th',
  1447. 'tj',
  1448. 'tk',
  1449. 'tl',
  1450. 'tm',
  1451. 'tn',
  1452. 'to',
  1453. 'tp',
  1454. 'tr',
  1455. 'travel',
  1456. 'tt',
  1457. 'tv',
  1458. 'tw',
  1459. 'tz',
  1460. 'ua',
  1461. 'ug',
  1462. 'uk',
  1463. 'um',
  1464. 'us',
  1465. 'uy',
  1466. 'uz',
  1467. 'va',
  1468. 'vc',
  1469. 've',
  1470. 'vg',
  1471. 'vi',
  1472. 'vn',
  1473. 'vu',
  1474. 'wf',
  1475. 'ws',
  1476. 'xn--0zwm56d',
  1477. 'xn--11b5bs3a9aj6g',
  1478. 'xn--80akhbyknj4f',
  1479. 'xn--9t4b11yi5a',
  1480. 'xn--deba0ad',
  1481. 'xn--g6w251d',
  1482. 'xn--hgbk6aj7f53bba',
  1483. 'xn--hlcj6aya9esc7a',
  1484. 'xn--jxalpdlp',
  1485. 'xn--kgbechtv',
  1486. 'xn--zckzah',
  1487. 'ye',
  1488. 'yt',
  1489. 'yu',
  1490. 'za',
  1491. 'zm',
  1492. 'zw',
  1493. ]
  1494. class IS_HTTP_URL(Validator):
  1495. """
  1496. Rejects a URL string if any of the following is true:
  1497. * The string is empty or None
  1498. * The string uses characters that are not allowed in a URL
  1499. * The string breaks any of the HTTP syntactic rules
  1500. * The URL scheme specified (if one is specified) is not 'http' or 'https'
  1501. * The top-level domain (if a host name is specified) does not exist
  1502. Based on RFC 2616: http://www.faqs.org/rfcs/rfc2616.html
  1503. This function only checks the URL's syntax. It does not check that the URL
  1504. points to a real document, for example, or that it otherwise makes sense
  1505. semantically. This function does automatically prepend 'http://' in front
  1506. of a URL in the case of an abbreviated URL (e.g. 'google.ca').
  1507. The list of allowed schemes is customizable with the allowed_schemes
  1508. parameter. If you exclude None from the list, then abbreviated URLs
  1509. (lacking a scheme such as 'http') will be rejected.
  1510. The default prepended scheme is customizable with the prepend_scheme
  1511. parameter. If you set prepend_scheme to None then prepending will be
  1512. disabled. URLs that require prepending to parse will still be accepted,
  1513. but the return value will not be modified.
  1514. @author: Jonathan Benn
  1515. >>> IS_HTTP_URL()('http://1.2.3.4')
  1516. ('http://1.2.3.4', None)
  1517. >>> IS_HTTP_URL()('http://abc.com')
  1518. ('http://abc.com', None)
  1519. >>> IS_HTTP_URL()('https://abc.com')
  1520. ('https://abc.com', None)
  1521. >>> IS_HTTP_URL()('httpx://abc.com')
  1522. ('httpx…

Large files files are truncated, but you can click here to view the full file