validators.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732
  1. import ipaddress
  2. import math
  3. import re
  4. import uuid
  5. __all__ = (
  6. "DataRequired",
  7. "data_required",
  8. "Email",
  9. "email",
  10. "EqualTo",
  11. "equal_to",
  12. "IPAddress",
  13. "ip_address",
  14. "InputRequired",
  15. "input_required",
  16. "Length",
  17. "length",
  18. "NumberRange",
  19. "number_range",
  20. "Optional",
  21. "optional",
  22. "Regexp",
  23. "regexp",
  24. "URL",
  25. "url",
  26. "AnyOf",
  27. "any_of",
  28. "NoneOf",
  29. "none_of",
  30. "MacAddress",
  31. "mac_address",
  32. "UUID",
  33. "ValidationError",
  34. "StopValidation",
  35. "readonly",
  36. "ReadOnly",
  37. "disabled",
  38. "Disabled",
  39. )
  40. class ValidationError(ValueError):
  41. """
  42. Raised when a validator fails to validate its input.
  43. """
  44. def __init__(self, message="", *args, **kwargs):
  45. ValueError.__init__(self, message, *args, **kwargs)
  46. class StopValidation(Exception):
  47. """
  48. Causes the validation chain to stop.
  49. If StopValidation is raised, no more validators in the validation chain are
  50. called. If raised with a message, the message will be added to the errors
  51. list.
  52. """
  53. def __init__(self, message="", *args, **kwargs):
  54. Exception.__init__(self, message, *args, **kwargs)
  55. class EqualTo:
  56. """
  57. Compares the values of two fields.
  58. :param fieldname:
  59. The name of the other field to compare to.
  60. :param message:
  61. Error message to raise in case of a validation error. Can be
  62. interpolated with `%(other_label)s` and `%(other_name)s` to provide a
  63. more helpful error.
  64. """
  65. def __init__(self, fieldname, message=None):
  66. self.fieldname = fieldname
  67. self.message = message
  68. def __call__(self, form, field):
  69. try:
  70. other = form[self.fieldname]
  71. except KeyError as exc:
  72. raise ValidationError(
  73. field.gettext("Invalid field name '%s'.") % self.fieldname
  74. ) from exc
  75. if field.data == other.data:
  76. return
  77. d = {
  78. "other_label": hasattr(other, "label")
  79. and other.label.text
  80. or self.fieldname,
  81. "other_name": self.fieldname,
  82. }
  83. message = self.message
  84. if message is None:
  85. message = field.gettext("Field must be equal to %(other_name)s.")
  86. raise ValidationError(message % d)
  87. class Length:
  88. """
  89. Validates the length of a string.
  90. :param min:
  91. The minimum required length of the string. If not provided, minimum
  92. length will not be checked.
  93. :param max:
  94. The maximum length of the string. If not provided, maximum length
  95. will not be checked.
  96. :param message:
  97. Error message to raise in case of a validation error. Can be
  98. interpolated using `%(min)d` and `%(max)d` if desired. Useful defaults
  99. are provided depending on the existence of min and max.
  100. When supported, sets the `minlength` and `maxlength` attributes on widgets.
  101. """
  102. def __init__(self, min=-1, max=-1, message=None):
  103. assert (
  104. min != -1 or max != -1
  105. ), "At least one of `min` or `max` must be specified."
  106. assert max == -1 or min <= max, "`min` cannot be more than `max`."
  107. self.min = min
  108. self.max = max
  109. self.message = message
  110. self.field_flags = {}
  111. if self.min != -1:
  112. self.field_flags["minlength"] = self.min
  113. if self.max != -1:
  114. self.field_flags["maxlength"] = self.max
  115. def __call__(self, form, field):
  116. length = field.data and len(field.data) or 0
  117. if length >= self.min and (self.max == -1 or length <= self.max):
  118. return
  119. if self.message is not None:
  120. message = self.message
  121. elif self.max == -1:
  122. message = field.ngettext(
  123. "Field must be at least %(min)d character long.",
  124. "Field must be at least %(min)d characters long.",
  125. self.min,
  126. )
  127. elif self.min == -1:
  128. message = field.ngettext(
  129. "Field cannot be longer than %(max)d character.",
  130. "Field cannot be longer than %(max)d characters.",
  131. self.max,
  132. )
  133. elif self.min == self.max:
  134. message = field.ngettext(
  135. "Field must be exactly %(max)d character long.",
  136. "Field must be exactly %(max)d characters long.",
  137. self.max,
  138. )
  139. else:
  140. message = field.gettext(
  141. "Field must be between %(min)d and %(max)d characters long."
  142. )
  143. raise ValidationError(message % dict(min=self.min, max=self.max, length=length))
  144. class NumberRange:
  145. """
  146. Validates that a number is of a minimum and/or maximum value, inclusive.
  147. This will work with any comparable number type, such as floats and
  148. decimals, not just integers.
  149. :param min:
  150. The minimum required value of the number. If not provided, minimum
  151. value will not be checked.
  152. :param max:
  153. The maximum value of the number. If not provided, maximum value
  154. will not be checked.
  155. :param message:
  156. Error message to raise in case of a validation error. Can be
  157. interpolated using `%(min)s` and `%(max)s` if desired. Useful defaults
  158. are provided depending on the existence of min and max.
  159. When supported, sets the `min` and `max` attributes on widgets.
  160. """
  161. def __init__(self, min=None, max=None, message=None):
  162. self.min = min
  163. self.max = max
  164. self.message = message
  165. self.field_flags = {}
  166. if self.min is not None:
  167. self.field_flags["min"] = self.min
  168. if self.max is not None:
  169. self.field_flags["max"] = self.max
  170. def __call__(self, form, field):
  171. data = field.data
  172. if (
  173. data is not None
  174. and not math.isnan(data)
  175. and (self.min is None or data >= self.min)
  176. and (self.max is None or data <= self.max)
  177. ):
  178. return
  179. if self.message is not None:
  180. message = self.message
  181. # we use %(min)s interpolation to support floats, None, and
  182. # Decimals without throwing a formatting exception.
  183. elif self.max is None:
  184. message = field.gettext("Number must be at least %(min)s.")
  185. elif self.min is None:
  186. message = field.gettext("Number must be at most %(max)s.")
  187. else:
  188. message = field.gettext("Number must be between %(min)s and %(max)s.")
  189. raise ValidationError(message % dict(min=self.min, max=self.max))
  190. class Optional:
  191. """
  192. Allows empty input and stops the validation chain from continuing.
  193. If input is empty, also removes prior errors (such as processing errors)
  194. from the field.
  195. :param strip_whitespace:
  196. If True (the default) also stop the validation chain on input which
  197. consists of only whitespace.
  198. Sets the `optional` attribute on widgets.
  199. """
  200. def __init__(self, strip_whitespace=True):
  201. if strip_whitespace:
  202. self.string_check = lambda s: s.strip()
  203. else:
  204. self.string_check = lambda s: s
  205. self.field_flags = {"optional": True}
  206. def __call__(self, form, field):
  207. if (
  208. not field.raw_data
  209. or isinstance(field.raw_data[0], str)
  210. and not self.string_check(field.raw_data[0])
  211. ):
  212. field.errors[:] = []
  213. raise StopValidation()
  214. class DataRequired:
  215. """
  216. Checks the field's data is 'truthy' otherwise stops the validation chain.
  217. This validator checks that the ``data`` attribute on the field is a 'true'
  218. value (effectively, it does ``if field.data``.) Furthermore, if the data
  219. is a string type, a string containing only whitespace characters is
  220. considered false.
  221. If the data is empty, also removes prior errors (such as processing errors)
  222. from the field.
  223. **NOTE** this validator used to be called `Required` but the way it behaved
  224. (requiring coerced data, not input data) meant it functioned in a way
  225. which was not symmetric to the `Optional` validator and furthermore caused
  226. confusion with certain fields which coerced data to 'falsey' values like
  227. ``0``, ``Decimal(0)``, ``time(0)`` etc. Unless a very specific reason
  228. exists, we recommend using the :class:`InputRequired` instead.
  229. :param message:
  230. Error message to raise in case of a validation error.
  231. Sets the `required` attribute on widgets.
  232. """
  233. def __init__(self, message=None):
  234. self.message = message
  235. self.field_flags = {"required": True}
  236. def __call__(self, form, field):
  237. if field.data and (not isinstance(field.data, str) or field.data.strip()):
  238. return
  239. if self.message is None:
  240. message = field.gettext("This field is required.")
  241. else:
  242. message = self.message
  243. field.errors[:] = []
  244. raise StopValidation(message)
  245. class InputRequired:
  246. """
  247. Validates that input was provided for this field.
  248. Note there is a distinction between this and DataRequired in that
  249. InputRequired looks that form-input data was provided, and DataRequired
  250. looks at the post-coercion data. This means that this validator only checks
  251. whether non-empty data was sent, not whether non-empty data was coerced
  252. from that data. Initially populated data is not considered sent.
  253. Sets the `required` attribute on widgets.
  254. """
  255. def __init__(self, message=None):
  256. self.message = message
  257. self.field_flags = {"required": True}
  258. def __call__(self, form, field):
  259. if field.raw_data and field.raw_data[0]:
  260. return
  261. if self.message is None:
  262. message = field.gettext("This field is required.")
  263. else:
  264. message = self.message
  265. field.errors[:] = []
  266. raise StopValidation(message)
  267. class Regexp:
  268. """
  269. Validates the field against a user provided regexp.
  270. :param regex:
  271. The regular expression string to use. Can also be a compiled regular
  272. expression pattern.
  273. :param flags:
  274. The regexp flags to use, for example re.IGNORECASE. Ignored if
  275. `regex` is not a string.
  276. :param message:
  277. Error message to raise in case of a validation error.
  278. """
  279. def __init__(self, regex, flags=0, message=None):
  280. if isinstance(regex, str):
  281. regex = re.compile(regex, flags)
  282. self.regex = regex
  283. self.message = message
  284. def __call__(self, form, field, message=None):
  285. match = self.regex.match(field.data or "")
  286. if match:
  287. return match
  288. if message is None:
  289. if self.message is None:
  290. message = field.gettext("Invalid input.")
  291. else:
  292. message = self.message
  293. raise ValidationError(message)
  294. class Email:
  295. """
  296. Validates an email address. Requires email_validator package to be
  297. installed. For ex: pip install wtforms[email].
  298. :param message:
  299. Error message to raise in case of a validation error.
  300. :param granular_message:
  301. Use validation failed message from email_validator library
  302. (Default False).
  303. :param check_deliverability:
  304. Perform domain name resolution check (Default False).
  305. :param allow_smtputf8:
  306. Fail validation for addresses that would require SMTPUTF8
  307. (Default True).
  308. :param allow_empty_local:
  309. Allow an empty local part (i.e. @example.com), e.g. for validating
  310. Postfix aliases (Default False).
  311. """
  312. def __init__(
  313. self,
  314. message=None,
  315. granular_message=False,
  316. check_deliverability=False,
  317. allow_smtputf8=True,
  318. allow_empty_local=False,
  319. ):
  320. self.message = message
  321. self.granular_message = granular_message
  322. self.check_deliverability = check_deliverability
  323. self.allow_smtputf8 = allow_smtputf8
  324. self.allow_empty_local = allow_empty_local
  325. def __call__(self, form, field):
  326. try:
  327. import email_validator
  328. except ImportError as exc: # pragma: no cover
  329. raise Exception(
  330. "Install 'email_validator' for email validation support."
  331. ) from exc
  332. try:
  333. if field.data is None:
  334. raise email_validator.EmailNotValidError()
  335. email_validator.validate_email(
  336. field.data,
  337. check_deliverability=self.check_deliverability,
  338. allow_smtputf8=self.allow_smtputf8,
  339. allow_empty_local=self.allow_empty_local,
  340. )
  341. except email_validator.EmailNotValidError as e:
  342. message = self.message
  343. if message is None:
  344. if self.granular_message:
  345. message = field.gettext(e)
  346. else:
  347. message = field.gettext("Invalid email address.")
  348. raise ValidationError(message) from e
  349. class IPAddress:
  350. """
  351. Validates an IP address.
  352. :param ipv4:
  353. If True, accept IPv4 addresses as valid (default True)
  354. :param ipv6:
  355. If True, accept IPv6 addresses as valid (default False)
  356. :param message:
  357. Error message to raise in case of a validation error.
  358. """
  359. def __init__(self, ipv4=True, ipv6=False, message=None):
  360. if not ipv4 and not ipv6:
  361. raise ValueError(
  362. "IP Address Validator must have at least one of ipv4 or ipv6 enabled."
  363. )
  364. self.ipv4 = ipv4
  365. self.ipv6 = ipv6
  366. self.message = message
  367. def __call__(self, form, field):
  368. value = field.data
  369. valid = False
  370. if value:
  371. valid = (self.ipv4 and self.check_ipv4(value)) or (
  372. self.ipv6 and self.check_ipv6(value)
  373. )
  374. if valid:
  375. return
  376. message = self.message
  377. if message is None:
  378. message = field.gettext("Invalid IP address.")
  379. raise ValidationError(message)
  380. @classmethod
  381. def check_ipv4(cls, value):
  382. try:
  383. address = ipaddress.ip_address(value)
  384. except ValueError:
  385. return False
  386. if not isinstance(address, ipaddress.IPv4Address):
  387. return False
  388. return True
  389. @classmethod
  390. def check_ipv6(cls, value):
  391. try:
  392. address = ipaddress.ip_address(value)
  393. except ValueError:
  394. return False
  395. if not isinstance(address, ipaddress.IPv6Address):
  396. return False
  397. return True
  398. class MacAddress(Regexp):
  399. """
  400. Validates a MAC address.
  401. :param message:
  402. Error message to raise in case of a validation error.
  403. """
  404. def __init__(self, message=None):
  405. pattern = r"^(?:[0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$"
  406. super().__init__(pattern, message=message)
  407. def __call__(self, form, field):
  408. message = self.message
  409. if message is None:
  410. message = field.gettext("Invalid Mac address.")
  411. super().__call__(form, field, message)
  412. class URL(Regexp):
  413. """
  414. Simple regexp based url validation. Much like the email validator, you
  415. probably want to validate the url later by other means if the url must
  416. resolve.
  417. :param require_tld:
  418. If true, then the domain-name portion of the URL must contain a .tld
  419. suffix. Set this to false if you want to allow domains like
  420. `localhost`.
  421. :param allow_ip:
  422. If false, then give ip as host will fail validation
  423. :param message:
  424. Error message to raise in case of a validation error.
  425. """
  426. def __init__(self, require_tld=True, allow_ip=True, message=None):
  427. regex = (
  428. r"^[a-z]+://"
  429. r"(?P<host>[^\/\?:]+)"
  430. r"(?P<port>:[0-9]+)?"
  431. r"(?P<path>\/.*?)?"
  432. r"(?P<query>\?.*)?$"
  433. )
  434. super().__init__(regex, re.IGNORECASE, message)
  435. self.validate_hostname = HostnameValidation(
  436. require_tld=require_tld, allow_ip=allow_ip
  437. )
  438. def __call__(self, form, field):
  439. message = self.message
  440. if message is None:
  441. message = field.gettext("Invalid URL.")
  442. match = super().__call__(form, field, message)
  443. if not self.validate_hostname(match.group("host")):
  444. raise ValidationError(message)
  445. class UUID:
  446. """
  447. Validates a UUID.
  448. :param message:
  449. Error message to raise in case of a validation error.
  450. """
  451. def __init__(self, message=None):
  452. self.message = message
  453. def __call__(self, form, field):
  454. message = self.message
  455. if message is None:
  456. message = field.gettext("Invalid UUID.")
  457. try:
  458. uuid.UUID(field.data)
  459. except ValueError as exc:
  460. raise ValidationError(message) from exc
  461. class AnyOf:
  462. """
  463. Compares the incoming data to a sequence of valid inputs.
  464. :param values:
  465. A sequence of valid inputs.
  466. :param message:
  467. Error message to raise in case of a validation error. `%(values)s`
  468. contains the list of values.
  469. :param values_formatter:
  470. Function used to format the list of values in the error message.
  471. """
  472. def __init__(self, values, message=None, values_formatter=None):
  473. self.values = values
  474. self.message = message
  475. if values_formatter is None:
  476. values_formatter = self.default_values_formatter
  477. self.values_formatter = values_formatter
  478. def __call__(self, form, field):
  479. if field.data in self.values:
  480. return
  481. message = self.message
  482. if message is None:
  483. message = field.gettext("Invalid value, must be one of: %(values)s.")
  484. raise ValidationError(message % dict(values=self.values_formatter(self.values)))
  485. @staticmethod
  486. def default_values_formatter(values):
  487. return ", ".join(str(x) for x in values)
  488. class NoneOf:
  489. """
  490. Compares the incoming data to a sequence of invalid inputs.
  491. :param values:
  492. A sequence of invalid inputs.
  493. :param message:
  494. Error message to raise in case of a validation error. `%(values)s`
  495. contains the list of values.
  496. :param values_formatter:
  497. Function used to format the list of values in the error message.
  498. """
  499. def __init__(self, values, message=None, values_formatter=None):
  500. self.values = values
  501. self.message = message
  502. if values_formatter is None:
  503. values_formatter = self.default_values_formatter
  504. self.values_formatter = values_formatter
  505. def __call__(self, form, field):
  506. if field.data not in self.values:
  507. return
  508. message = self.message
  509. if message is None:
  510. message = field.gettext("Invalid value, can't be any of: %(values)s.")
  511. raise ValidationError(message % dict(values=self.values_formatter(self.values)))
  512. @staticmethod
  513. def default_values_formatter(v):
  514. return ", ".join(str(x) for x in v)
  515. class HostnameValidation:
  516. """
  517. Helper class for checking hostnames for validation.
  518. This is not a validator in and of itself, and as such is not exported.
  519. """
  520. hostname_part = re.compile(r"^(xn-|[a-z0-9_]+)(-[a-z0-9_-]+)*$", re.IGNORECASE)
  521. tld_part = re.compile(r"^([a-z]{2,20}|xn--([a-z0-9]+-)*[a-z0-9]+)$", re.IGNORECASE)
  522. def __init__(self, require_tld=True, allow_ip=False):
  523. self.require_tld = require_tld
  524. self.allow_ip = allow_ip
  525. def __call__(self, hostname):
  526. if self.allow_ip and (
  527. IPAddress.check_ipv4(hostname) or IPAddress.check_ipv6(hostname)
  528. ):
  529. return True
  530. # Encode out IDNA hostnames. This makes further validation easier.
  531. try:
  532. hostname = hostname.encode("idna")
  533. except UnicodeError:
  534. pass
  535. # Turn back into a string in Python 3x
  536. if not isinstance(hostname, str):
  537. hostname = hostname.decode("ascii")
  538. if len(hostname) > 253:
  539. return False
  540. # Check that all labels in the hostname are valid
  541. parts = hostname.split(".")
  542. for part in parts:
  543. if not part or len(part) > 63:
  544. return False
  545. if not self.hostname_part.match(part):
  546. return False
  547. if self.require_tld and (len(parts) < 2 or not self.tld_part.match(parts[-1])):
  548. return False
  549. return True
  550. class ReadOnly:
  551. """
  552. Set a field readonly.
  553. Validation fails if the form data is different than the
  554. field object data, or if unset, from the field default data.
  555. """
  556. def __init__(self):
  557. self.field_flags = {"readonly": True}
  558. def __call__(self, form, field):
  559. if field.data != field.object_data:
  560. raise ValidationError(field.gettext("This field cannot be edited"))
  561. class Disabled:
  562. """
  563. Set a field disabled.
  564. Validation fails if the form data has any value.
  565. """
  566. def __init__(self):
  567. self.field_flags = {"disabled": True}
  568. def __call__(self, form, field):
  569. if field.raw_data is not None:
  570. raise ValidationError(
  571. field.gettext("This field is disabled and cannot have a value")
  572. )
  573. email = Email
  574. equal_to = EqualTo
  575. ip_address = IPAddress
  576. mac_address = MacAddress
  577. length = Length
  578. number_range = NumberRange
  579. optional = Optional
  580. input_required = InputRequired
  581. data_required = DataRequired
  582. regexp = Regexp
  583. url = URL
  584. any_of = AnyOf
  585. none_of = NoneOf
  586. readonly = ReadOnly
  587. disabled = Disabled