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