/python/lib/django-1.4/django/core/validators.py
Python | 252 lines | 219 code | 22 blank | 11 comment | 20 complexity | 1a00e3f938e504e8fa39170746ec21b0 MD5 | raw file
1import platform
2import re
3import urllib
4import urllib2
5import urlparse
6
7from django.core.exceptions import ValidationError
8from django.utils.translation import ugettext_lazy as _
9from django.utils.encoding import smart_unicode
10from django.utils.ipv6 import is_valid_ipv6_address
11
12# These values, if given to validate(), will trigger the self.required check.
13EMPTY_VALUES = (None, '', [], (), {})
14
15try:
16 from django.conf import settings
17 URL_VALIDATOR_USER_AGENT = settings.URL_VALIDATOR_USER_AGENT
18except ImportError:
19 # It's OK if Django settings aren't configured.
20 URL_VALIDATOR_USER_AGENT = 'Django (http://www.djangoproject.com/)'
21
22class RegexValidator(object):
23 regex = ''
24 message = _(u'Enter a valid value.')
25 code = 'invalid'
26
27 def __init__(self, regex=None, message=None, code=None):
28 if regex is not None:
29 self.regex = regex
30 if message is not None:
31 self.message = message
32 if code is not None:
33 self.code = code
34
35 # Compile the regex if it was not passed pre-compiled.
36 if isinstance(self.regex, basestring):
37 self.regex = re.compile(self.regex)
38
39 def __call__(self, value):
40 """
41 Validates that the input matches the regular expression.
42 """
43 if not self.regex.search(smart_unicode(value)):
44 raise ValidationError(self.message, code=self.code)
45
46class URLValidator(RegexValidator):
47 regex = re.compile(
48 r'^(?:http|ftp)s?://' # http:// or https://
49 r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' #domain...
50 r'localhost|' #localhost...
51 r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip
52 r'(?::\d+)?' # optional port
53 r'(?:/?|[/?]\S+)$', re.IGNORECASE)
54
55 def __init__(self, verify_exists=False,
56 validator_user_agent=URL_VALIDATOR_USER_AGENT):
57 super(URLValidator, self).__init__()
58 self.verify_exists = verify_exists
59 self.user_agent = validator_user_agent
60
61 def __call__(self, value):
62 try:
63 super(URLValidator, self).__call__(value)
64 except ValidationError, e:
65 # Trivial case failed. Try for possible IDN domain
66 if value:
67 value = smart_unicode(value)
68 scheme, netloc, path, query, fragment = urlparse.urlsplit(value)
69 try:
70 netloc = netloc.encode('idna') # IDN -> ACE
71 except UnicodeError: # invalid domain part
72 raise e
73 url = urlparse.urlunsplit((scheme, netloc, path, query, fragment))
74 super(URLValidator, self).__call__(url)
75 else:
76 raise
77 else:
78 url = value
79
80 if self.verify_exists:
81 import warnings
82 warnings.warn(
83 "The URLField verify_exists argument has intractable security "
84 "and performance issues. Accordingly, it has been deprecated.",
85 DeprecationWarning
86 )
87
88 headers = {
89 "Accept": "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
90 "Accept-Language": "en-us,en;q=0.5",
91 "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7",
92 "Connection": "close",
93 "User-Agent": self.user_agent,
94 }
95 url = url.encode('utf-8')
96 # Quote characters from the unreserved set, refs #16812
97 url = urllib.quote(url, "!*'();:@&=+$,/?#[]")
98 broken_error = ValidationError(
99 _(u'This URL appears to be a broken link.'), code='invalid_link')
100 try:
101 req = urllib2.Request(url, None, headers)
102 req.get_method = lambda: 'HEAD'
103 #Create an opener that does not support local file access
104 opener = urllib2.OpenerDirector()
105
106 #Don't follow redirects, but don't treat them as errors either
107 error_nop = lambda *args, **kwargs: True
108 http_error_processor = urllib2.HTTPErrorProcessor()
109 http_error_processor.http_error_301 = error_nop
110 http_error_processor.http_error_302 = error_nop
111 http_error_processor.http_error_307 = error_nop
112
113 handlers = [urllib2.UnknownHandler(),
114 urllib2.HTTPHandler(),
115 urllib2.HTTPDefaultErrorHandler(),
116 urllib2.FTPHandler(),
117 http_error_processor]
118 try:
119 import ssl
120 except ImportError:
121 # Python isn't compiled with SSL support
122 pass
123 else:
124 handlers.append(urllib2.HTTPSHandler())
125 map(opener.add_handler, handlers)
126 if platform.python_version_tuple() >= (2, 6):
127 opener.open(req, timeout=10)
128 else:
129 opener.open(req)
130 except ValueError:
131 raise ValidationError(_(u'Enter a valid URL.'), code='invalid')
132 except: # urllib2.URLError, httplib.InvalidURL, etc.
133 raise broken_error
134
135
136def validate_integer(value):
137 try:
138 int(value)
139 except (ValueError, TypeError):
140 raise ValidationError('')
141
142class EmailValidator(RegexValidator):
143
144 def __call__(self, value):
145 try:
146 super(EmailValidator, self).__call__(value)
147 except ValidationError, e:
148 # Trivial case failed. Try for possible IDN domain-part
149 if value and u'@' in value:
150 parts = value.split(u'@')
151 try:
152 parts[-1] = parts[-1].encode('idna')
153 except UnicodeError:
154 raise e
155 super(EmailValidator, self).__call__(u'@'.join(parts))
156 else:
157 raise
158
159email_re = re.compile(
160 r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" # dot-atom
161 # quoted-string, see also http://tools.ietf.org/html/rfc2822#section-3.2.5
162 r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"'
163 r')@((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?$)' # domain
164 r'|\[(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\]$', re.IGNORECASE) # literal form, ipv4 address (SMTP 4.1.3)
165validate_email = EmailValidator(email_re, _(u'Enter a valid e-mail address.'), 'invalid')
166
167slug_re = re.compile(r'^[-\w]+$')
168validate_slug = RegexValidator(slug_re, _(u"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."), 'invalid')
169
170ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$')
171validate_ipv4_address = RegexValidator(ipv4_re, _(u'Enter a valid IPv4 address.'), 'invalid')
172
173def validate_ipv6_address(value):
174 if not is_valid_ipv6_address(value):
175 raise ValidationError(_(u'Enter a valid IPv6 address.'), code='invalid')
176
177def validate_ipv46_address(value):
178 try:
179 validate_ipv4_address(value)
180 except ValidationError:
181 try:
182 validate_ipv6_address(value)
183 except ValidationError:
184 raise ValidationError(_(u'Enter a valid IPv4 or IPv6 address.'), code='invalid')
185
186ip_address_validator_map = {
187 'both': ([validate_ipv46_address], _('Enter a valid IPv4 or IPv6 address.')),
188 'ipv4': ([validate_ipv4_address], _('Enter a valid IPv4 address.')),
189 'ipv6': ([validate_ipv6_address], _('Enter a valid IPv6 address.')),
190}
191
192def ip_address_validators(protocol, unpack_ipv4):
193 """
194 Depending on the given parameters returns the appropriate validators for
195 the GenericIPAddressField.
196
197 This code is here, because it is exactly the same for the model and the form field.
198 """
199 if protocol != 'both' and unpack_ipv4:
200 raise ValueError(
201 "You can only use `unpack_ipv4` if `protocol` is set to 'both'")
202 try:
203 return ip_address_validator_map[protocol.lower()]
204 except KeyError:
205 raise ValueError("The protocol '%s' is unknown. Supported: %s"
206 % (protocol, ip_address_validator_map.keys()))
207
208comma_separated_int_list_re = re.compile('^[\d,]+$')
209validate_comma_separated_integer_list = RegexValidator(comma_separated_int_list_re, _(u'Enter only digits separated by commas.'), 'invalid')
210
211
212class BaseValidator(object):
213 compare = lambda self, a, b: a is not b
214 clean = lambda self, x: x
215 message = _(u'Ensure this value is %(limit_value)s (it is %(show_value)s).')
216 code = 'limit_value'
217
218 def __init__(self, limit_value):
219 self.limit_value = limit_value
220
221 def __call__(self, value):
222 cleaned = self.clean(value)
223 params = {'limit_value': self.limit_value, 'show_value': cleaned}
224 if self.compare(cleaned, self.limit_value):
225 raise ValidationError(
226 self.message % params,
227 code=self.code,
228 params=params,
229 )
230
231class MaxValueValidator(BaseValidator):
232 compare = lambda self, a, b: a > b
233 message = _(u'Ensure this value is less than or equal to %(limit_value)s.')
234 code = 'max_value'
235
236class MinValueValidator(BaseValidator):
237 compare = lambda self, a, b: a < b
238 message = _(u'Ensure this value is greater than or equal to %(limit_value)s.')
239 code = 'min_value'
240
241class MinLengthValidator(BaseValidator):
242 compare = lambda self, a, b: a < b
243 clean = lambda self, x: len(x)
244 message = _(u'Ensure this value has at least %(limit_value)d characters (it has %(show_value)d).')
245 code = 'min_length'
246
247class MaxLengthValidator(BaseValidator):
248 compare = lambda self, a, b: a > b
249 clean = lambda self, x: len(x)
250 message = _(u'Ensure this value has at most %(limit_value)d characters (it has %(show_value)d).')
251 code = 'max_length'
252