PageRenderTime 28ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/django/core/validators.py

https://gitlab.com/Guy1394/django
Python | 425 lines | 343 code | 55 blank | 27 comment | 48 complexity | 62f8e8d65b849c60cd79835a566aca7d MD5 | raw file
  1. from __future__ import unicode_literals
  2. import re
  3. from django.core.exceptions import ValidationError
  4. from django.utils import six
  5. from django.utils.deconstruct import deconstructible
  6. from django.utils.encoding import force_text
  7. from django.utils.functional import SimpleLazyObject
  8. from django.utils.ipv6 import is_valid_ipv6_address
  9. from django.utils.six.moves.urllib.parse import urlsplit, urlunsplit
  10. from django.utils.translation import ugettext_lazy as _, ungettext_lazy
  11. # These values, if given to validate(), will trigger the self.required check.
  12. EMPTY_VALUES = (None, '', [], (), {})
  13. def _lazy_re_compile(regex, flags=0):
  14. """Lazily compile a regex with flags."""
  15. def _compile():
  16. # Compile the regex if it was not passed pre-compiled.
  17. if isinstance(regex, six.string_types):
  18. return re.compile(regex, flags)
  19. else:
  20. assert not flags, "flags must be empty if regex is passed pre-compiled"
  21. return regex
  22. return SimpleLazyObject(_compile)
  23. @deconstructible
  24. class RegexValidator(object):
  25. regex = ''
  26. message = _('Enter a valid value.')
  27. code = 'invalid'
  28. inverse_match = False
  29. flags = 0
  30. def __init__(self, regex=None, message=None, code=None, inverse_match=None, flags=None):
  31. if regex is not None:
  32. self.regex = regex
  33. if message is not None:
  34. self.message = message
  35. if code is not None:
  36. self.code = code
  37. if inverse_match is not None:
  38. self.inverse_match = inverse_match
  39. if flags is not None:
  40. self.flags = flags
  41. if self.flags and not isinstance(self.regex, six.string_types):
  42. raise TypeError("If the flags are set, regex must be a regular expression string.")
  43. self.regex = _lazy_re_compile(self.regex, self.flags)
  44. def __call__(self, value):
  45. """
  46. Validates that the input matches the regular expression
  47. if inverse_match is False, otherwise raises ValidationError.
  48. """
  49. if not (self.inverse_match is not bool(self.regex.search(
  50. force_text(value)))):
  51. raise ValidationError(self.message, code=self.code)
  52. def __eq__(self, other):
  53. return (
  54. isinstance(other, RegexValidator) and
  55. self.regex.pattern == other.regex.pattern and
  56. self.regex.flags == other.regex.flags and
  57. (self.message == other.message) and
  58. (self.code == other.code) and
  59. (self.inverse_match == other.inverse_match)
  60. )
  61. def __ne__(self, other):
  62. return not (self == other)
  63. @deconstructible
  64. class URLValidator(RegexValidator):
  65. ul = '\u00a1-\uffff' # unicode letters range (must be a unicode string, not a raw string)
  66. # IP patterns
  67. ipv4_re = r'(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)(?:\.(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}'
  68. ipv6_re = r'\[[0-9a-f:\.]+\]' # (simple regex, validated later)
  69. # Host patterns
  70. hostname_re = r'[a-z' + ul + r'0-9](?:[a-z' + ul + r'0-9-]{0,61}[a-z' + ul + r'0-9])?'
  71. # Max length for domain name labels is 63 characters per RFC 1034 sec. 3.1
  72. domain_re = r'(?:\.(?!-)[a-z' + ul + r'0-9-]{1,63}(?<!-))*'
  73. tld_re = r'\.(?:[a-z' + ul + r']{2,63}|xn--[a-z0-9]{1,59})\.?'
  74. host_re = '(' + hostname_re + domain_re + tld_re + '|localhost)'
  75. regex = _lazy_re_compile(
  76. r'^(?:[a-z0-9\.\-\+]*)://' # scheme is validated separately
  77. r'(?:\S+(?::\S*)?@)?' # user:pass authentication
  78. r'(?:' + ipv4_re + '|' + ipv6_re + '|' + host_re + ')'
  79. r'(?::\d{2,5})?' # port
  80. r'(?:[/?#][^\s]*)?' # resource path
  81. r'\Z', re.IGNORECASE)
  82. message = _('Enter a valid URL.')
  83. schemes = ['http', 'https', 'ftp', 'ftps']
  84. def __init__(self, schemes=None, **kwargs):
  85. super(URLValidator, self).__init__(**kwargs)
  86. if schemes is not None:
  87. self.schemes = schemes
  88. def __call__(self, value):
  89. value = force_text(value)
  90. # Check first if the scheme is valid
  91. scheme = value.split('://')[0].lower()
  92. if scheme not in self.schemes:
  93. raise ValidationError(self.message, code=self.code)
  94. # Then check full URL
  95. try:
  96. super(URLValidator, self).__call__(value)
  97. except ValidationError as e:
  98. # Trivial case failed. Try for possible IDN domain
  99. if value:
  100. scheme, netloc, path, query, fragment = urlsplit(value)
  101. try:
  102. netloc = netloc.encode('idna').decode('ascii') # IDN -> ACE
  103. except UnicodeError: # invalid domain part
  104. raise e
  105. url = urlunsplit((scheme, netloc, path, query, fragment))
  106. super(URLValidator, self).__call__(url)
  107. else:
  108. raise
  109. else:
  110. # Now verify IPv6 in the netloc part
  111. host_match = re.search(r'^\[(.+)\](?::\d{2,5})?$', urlsplit(value).netloc)
  112. if host_match:
  113. potential_ip = host_match.groups()[0]
  114. try:
  115. validate_ipv6_address(potential_ip)
  116. except ValidationError:
  117. raise ValidationError(self.message, code=self.code)
  118. url = value
  119. # The maximum length of a full host name is 253 characters per RFC 1034
  120. # section 3.1. It's defined to be 255 bytes or less, but this includes
  121. # one byte for the length of the name and one byte for the trailing dot
  122. # that's used to indicate absolute names in DNS.
  123. if len(urlsplit(value).netloc) > 253:
  124. raise ValidationError(self.message, code=self.code)
  125. integer_validator = RegexValidator(
  126. _lazy_re_compile('^-?\d+\Z'),
  127. message=_('Enter a valid integer.'),
  128. code='invalid',
  129. )
  130. def validate_integer(value):
  131. return integer_validator(value)
  132. @deconstructible
  133. class EmailValidator(object):
  134. message = _('Enter a valid email address.')
  135. code = 'invalid'
  136. user_regex = _lazy_re_compile(
  137. r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*\Z" # dot-atom
  138. r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"\Z)', # quoted-string
  139. re.IGNORECASE)
  140. domain_regex = _lazy_re_compile(
  141. # max length for domain name labels is 63 characters per RFC 1034
  142. r'((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+)(?:[A-Z0-9-]{2,63}(?<!-))\Z',
  143. re.IGNORECASE)
  144. literal_regex = _lazy_re_compile(
  145. # literal form, ipv4 or ipv6 address (SMTP 4.1.3)
  146. r'\[([A-f0-9:\.]+)\]\Z',
  147. re.IGNORECASE)
  148. domain_whitelist = ['localhost']
  149. def __init__(self, message=None, code=None, whitelist=None):
  150. if message is not None:
  151. self.message = message
  152. if code is not None:
  153. self.code = code
  154. if whitelist is not None:
  155. self.domain_whitelist = whitelist
  156. def __call__(self, value):
  157. value = force_text(value)
  158. if not value or '@' not in value:
  159. raise ValidationError(self.message, code=self.code)
  160. user_part, domain_part = value.rsplit('@', 1)
  161. if not self.user_regex.match(user_part):
  162. raise ValidationError(self.message, code=self.code)
  163. if (domain_part not in self.domain_whitelist and
  164. not self.validate_domain_part(domain_part)):
  165. # Try for possible IDN domain-part
  166. try:
  167. domain_part = domain_part.encode('idna').decode('ascii')
  168. if self.validate_domain_part(domain_part):
  169. return
  170. except UnicodeError:
  171. pass
  172. raise ValidationError(self.message, code=self.code)
  173. def validate_domain_part(self, domain_part):
  174. if self.domain_regex.match(domain_part):
  175. return True
  176. literal_match = self.literal_regex.match(domain_part)
  177. if literal_match:
  178. ip_address = literal_match.group(1)
  179. try:
  180. validate_ipv46_address(ip_address)
  181. return True
  182. except ValidationError:
  183. pass
  184. return False
  185. def __eq__(self, other):
  186. return (
  187. isinstance(other, EmailValidator) and
  188. (self.domain_whitelist == other.domain_whitelist) and
  189. (self.message == other.message) and
  190. (self.code == other.code)
  191. )
  192. validate_email = EmailValidator()
  193. slug_re = _lazy_re_compile(r'^[-a-zA-Z0-9_]+\Z')
  194. validate_slug = RegexValidator(
  195. slug_re,
  196. _("Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."),
  197. 'invalid'
  198. )
  199. slug_unicode_re = _lazy_re_compile(r'^[-\w]+\Z', re.U)
  200. validate_unicode_slug = RegexValidator(
  201. slug_unicode_re,
  202. _("Enter a valid 'slug' consisting of Unicode letters, numbers, underscores, or hyphens."),
  203. 'invalid'
  204. )
  205. ipv4_re = _lazy_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}\Z')
  206. validate_ipv4_address = RegexValidator(ipv4_re, _('Enter a valid IPv4 address.'), 'invalid')
  207. def validate_ipv6_address(value):
  208. if not is_valid_ipv6_address(value):
  209. raise ValidationError(_('Enter a valid IPv6 address.'), code='invalid')
  210. def validate_ipv46_address(value):
  211. try:
  212. validate_ipv4_address(value)
  213. except ValidationError:
  214. try:
  215. validate_ipv6_address(value)
  216. except ValidationError:
  217. raise ValidationError(_('Enter a valid IPv4 or IPv6 address.'), code='invalid')
  218. ip_address_validator_map = {
  219. 'both': ([validate_ipv46_address], _('Enter a valid IPv4 or IPv6 address.')),
  220. 'ipv4': ([validate_ipv4_address], _('Enter a valid IPv4 address.')),
  221. 'ipv6': ([validate_ipv6_address], _('Enter a valid IPv6 address.')),
  222. }
  223. def ip_address_validators(protocol, unpack_ipv4):
  224. """
  225. Depending on the given parameters returns the appropriate validators for
  226. the GenericIPAddressField.
  227. This code is here, because it is exactly the same for the model and the form field.
  228. """
  229. if protocol != 'both' and unpack_ipv4:
  230. raise ValueError(
  231. "You can only use `unpack_ipv4` if `protocol` is set to 'both'")
  232. try:
  233. return ip_address_validator_map[protocol.lower()]
  234. except KeyError:
  235. raise ValueError("The protocol '%s' is unknown. Supported: %s"
  236. % (protocol, list(ip_address_validator_map)))
  237. def int_list_validator(sep=',', message=None, code='invalid'):
  238. regexp = _lazy_re_compile('^\d+(?:%s\d+)*\Z' % re.escape(sep))
  239. return RegexValidator(regexp, message=message, code=code)
  240. validate_comma_separated_integer_list = int_list_validator(
  241. message=_('Enter only digits separated by commas.'),
  242. )
  243. @deconstructible
  244. class BaseValidator(object):
  245. compare = lambda self, a, b: a is not b
  246. clean = lambda self, x: x
  247. message = _('Ensure this value is %(limit_value)s (it is %(show_value)s).')
  248. code = 'limit_value'
  249. def __init__(self, limit_value, message=None):
  250. self.limit_value = limit_value
  251. if message:
  252. self.message = message
  253. def __call__(self, value):
  254. cleaned = self.clean(value)
  255. params = {'limit_value': self.limit_value, 'show_value': cleaned, 'value': value}
  256. if self.compare(cleaned, self.limit_value):
  257. raise ValidationError(self.message, code=self.code, params=params)
  258. def __eq__(self, other):
  259. return (
  260. isinstance(other, self.__class__) and
  261. (self.limit_value == other.limit_value)
  262. and (self.message == other.message)
  263. and (self.code == other.code)
  264. )
  265. @deconstructible
  266. class MaxValueValidator(BaseValidator):
  267. compare = lambda self, a, b: a > b
  268. message = _('Ensure this value is less than or equal to %(limit_value)s.')
  269. code = 'max_value'
  270. @deconstructible
  271. class MinValueValidator(BaseValidator):
  272. compare = lambda self, a, b: a < b
  273. message = _('Ensure this value is greater than or equal to %(limit_value)s.')
  274. code = 'min_value'
  275. @deconstructible
  276. class MinLengthValidator(BaseValidator):
  277. compare = lambda self, a, b: a < b
  278. clean = lambda self, x: len(x)
  279. message = ungettext_lazy(
  280. 'Ensure this value has at least %(limit_value)d character (it has %(show_value)d).',
  281. 'Ensure this value has at least %(limit_value)d characters (it has %(show_value)d).',
  282. 'limit_value')
  283. code = 'min_length'
  284. @deconstructible
  285. class MaxLengthValidator(BaseValidator):
  286. compare = lambda self, a, b: a > b
  287. clean = lambda self, x: len(x)
  288. message = ungettext_lazy(
  289. 'Ensure this value has at most %(limit_value)d character (it has %(show_value)d).',
  290. 'Ensure this value has at most %(limit_value)d characters (it has %(show_value)d).',
  291. 'limit_value')
  292. code = 'max_length'
  293. @deconstructible
  294. class DecimalValidator(object):
  295. """
  296. Validate that the input does not exceed the maximum number of digits
  297. expected, otherwise raise ValidationError.
  298. """
  299. messages = {
  300. 'max_digits': ungettext_lazy(
  301. 'Ensure that there are no more than %(max)s digit in total.',
  302. 'Ensure that there are no more than %(max)s digits in total.',
  303. 'max'
  304. ),
  305. 'max_decimal_places': ungettext_lazy(
  306. 'Ensure that there are no more than %(max)s decimal place.',
  307. 'Ensure that there are no more than %(max)s decimal places.',
  308. 'max'
  309. ),
  310. 'max_whole_digits': ungettext_lazy(
  311. 'Ensure that there are no more than %(max)s digit before the decimal point.',
  312. 'Ensure that there are no more than %(max)s digits before the decimal point.',
  313. 'max'
  314. ),
  315. }
  316. def __init__(self, max_digits, decimal_places):
  317. self.max_digits = max_digits
  318. self.decimal_places = decimal_places
  319. def __call__(self, value):
  320. digit_tuple, exponent = value.as_tuple()[1:]
  321. decimals = abs(exponent)
  322. # digit_tuple doesn't include any leading zeros.
  323. digits = len(digit_tuple)
  324. if decimals > digits:
  325. # We have leading zeros up to or past the decimal point. Count
  326. # everything past the decimal point as a digit. We do not count
  327. # 0 before the decimal point as a digit since that would mean
  328. # we would not allow max_digits = decimal_places.
  329. digits = decimals
  330. whole_digits = digits - decimals
  331. if self.max_digits is not None and digits > self.max_digits:
  332. raise ValidationError(
  333. self.messages['max_digits'],
  334. code='max_digits',
  335. params={'max': self.max_digits},
  336. )
  337. if self.decimal_places is not None and decimals > self.decimal_places:
  338. raise ValidationError(
  339. self.messages['max_decimal_places'],
  340. code='max_decimal_places',
  341. params={'max': self.decimal_places},
  342. )
  343. if (self.max_digits is not None and self.decimal_places is not None
  344. and whole_digits > (self.max_digits - self.decimal_places)):
  345. raise ValidationError(
  346. self.messages['max_whole_digits'],
  347. code='max_whole_digits',
  348. params={'max': (self.max_digits - self.decimal_places)},
  349. )
  350. def __eq__(self, other):
  351. return (
  352. isinstance(other, self.__class__) and
  353. self.max_digits == other.max_digits and
  354. self.decimal_places == other.decimal_places
  355. )