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