Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.

451 wiersze
14KB

  1. # -*- coding: utf-8 -*-
  2. # Copyright (c) 2017 Ian Stapleton Cordasco
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
  12. # implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. """Module containing the validation logic for rfc3986."""
  16. from . import exceptions
  17. from . import misc
  18. from . import normalizers
  19. class Validator(object):
  20. """Object used to configure validation of all objects in rfc3986.
  21. .. versionadded:: 1.0
  22. Example usage::
  23. >>> from rfc3986 import api, validators
  24. >>> uri = api.uri_reference('https://github.com/')
  25. >>> validator = validators.Validator().require_presence_of(
  26. ... 'scheme', 'host', 'path',
  27. ... ).allow_schemes(
  28. ... 'http', 'https',
  29. ... ).allow_hosts(
  30. ... '127.0.0.1', 'github.com',
  31. ... )
  32. >>> validator.validate(uri)
  33. >>> invalid_uri = rfc3986.uri_reference('imap://mail.google.com')
  34. >>> validator.validate(invalid_uri)
  35. Traceback (most recent call last):
  36. ...
  37. rfc3986.exceptions.MissingComponentError: ('path was required but
  38. missing', URIReference(scheme=u'imap', authority=u'mail.google.com',
  39. path=None, query=None, fragment=None), ['path'])
  40. """
  41. COMPONENT_NAMES = frozenset([
  42. 'scheme',
  43. 'userinfo',
  44. 'host',
  45. 'port',
  46. 'path',
  47. 'query',
  48. 'fragment',
  49. ])
  50. def __init__(self):
  51. """Initialize our default validations."""
  52. self.allowed_schemes = set()
  53. self.allowed_hosts = set()
  54. self.allowed_ports = set()
  55. self.allow_password = True
  56. self.required_components = {
  57. 'scheme': False,
  58. 'userinfo': False,
  59. 'host': False,
  60. 'port': False,
  61. 'path': False,
  62. 'query': False,
  63. 'fragment': False,
  64. }
  65. self.validated_components = self.required_components.copy()
  66. def allow_schemes(self, *schemes):
  67. """Require the scheme to be one of the provided schemes.
  68. .. versionadded:: 1.0
  69. :param schemes:
  70. Schemes, without ``://`` that are allowed.
  71. :returns:
  72. The validator instance.
  73. :rtype:
  74. Validator
  75. """
  76. for scheme in schemes:
  77. self.allowed_schemes.add(normalizers.normalize_scheme(scheme))
  78. return self
  79. def allow_hosts(self, *hosts):
  80. """Require the host to be one of the provided hosts.
  81. .. versionadded:: 1.0
  82. :param hosts:
  83. Hosts that are allowed.
  84. :returns:
  85. The validator instance.
  86. :rtype:
  87. Validator
  88. """
  89. for host in hosts:
  90. self.allowed_hosts.add(normalizers.normalize_host(host))
  91. return self
  92. def allow_ports(self, *ports):
  93. """Require the port to be one of the provided ports.
  94. .. versionadded:: 1.0
  95. :param ports:
  96. Ports that are allowed.
  97. :returns:
  98. The validator instance.
  99. :rtype:
  100. Validator
  101. """
  102. for port in ports:
  103. port_int = int(port, base=10)
  104. if 0 <= port_int <= 65535:
  105. self.allowed_ports.add(port)
  106. return self
  107. def allow_use_of_password(self):
  108. """Allow passwords to be present in the URI.
  109. .. versionadded:: 1.0
  110. :returns:
  111. The validator instance.
  112. :rtype:
  113. Validator
  114. """
  115. self.allow_password = True
  116. return self
  117. def forbid_use_of_password(self):
  118. """Prevent passwords from being included in the URI.
  119. .. versionadded:: 1.0
  120. :returns:
  121. The validator instance.
  122. :rtype:
  123. Validator
  124. """
  125. self.allow_password = False
  126. return self
  127. def check_validity_of(self, *components):
  128. """Check the validity of the components provided.
  129. This can be specified repeatedly.
  130. .. versionadded:: 1.1
  131. :param components:
  132. Names of components from :attr:`Validator.COMPONENT_NAMES`.
  133. :returns:
  134. The validator instance.
  135. :rtype:
  136. Validator
  137. """
  138. components = [c.lower() for c in components]
  139. for component in components:
  140. if component not in self.COMPONENT_NAMES:
  141. raise ValueError(
  142. '"{}" is not a valid component'.format(component)
  143. )
  144. self.validated_components.update({
  145. component: True for component in components
  146. })
  147. return self
  148. def require_presence_of(self, *components):
  149. """Require the components provided.
  150. This can be specified repeatedly.
  151. .. versionadded:: 1.0
  152. :param components:
  153. Names of components from :attr:`Validator.COMPONENT_NAMES`.
  154. :returns:
  155. The validator instance.
  156. :rtype:
  157. Validator
  158. """
  159. components = [c.lower() for c in components]
  160. for component in components:
  161. if component not in self.COMPONENT_NAMES:
  162. raise ValueError(
  163. '"{}" is not a valid component'.format(component)
  164. )
  165. self.required_components.update({
  166. component: True for component in components
  167. })
  168. return self
  169. def validate(self, uri):
  170. """Check a URI for conditions specified on this validator.
  171. .. versionadded:: 1.0
  172. :param uri:
  173. Parsed URI to validate.
  174. :type uri:
  175. rfc3986.uri.URIReference
  176. :raises MissingComponentError:
  177. When a required component is missing.
  178. :raises UnpermittedComponentError:
  179. When a component is not one of those allowed.
  180. :raises PasswordForbidden:
  181. When a password is present in the userinfo component but is
  182. not permitted by configuration.
  183. :raises InvalidComponentsError:
  184. When a component was found to be invalid.
  185. """
  186. if not self.allow_password:
  187. check_password(uri)
  188. required_components = [
  189. component
  190. for component, required in self.required_components.items()
  191. if required
  192. ]
  193. validated_components = [
  194. component
  195. for component, required in self.validated_components.items()
  196. if required
  197. ]
  198. if required_components:
  199. ensure_required_components_exist(uri, required_components)
  200. if validated_components:
  201. ensure_components_are_valid(uri, validated_components)
  202. ensure_one_of(self.allowed_schemes, uri, 'scheme')
  203. ensure_one_of(self.allowed_hosts, uri, 'host')
  204. ensure_one_of(self.allowed_ports, uri, 'port')
  205. def check_password(uri):
  206. """Assert that there is no password present in the uri."""
  207. userinfo = uri.userinfo
  208. if not userinfo:
  209. return
  210. credentials = userinfo.split(':', 1)
  211. if len(credentials) <= 1:
  212. return
  213. raise exceptions.PasswordForbidden(uri)
  214. def ensure_one_of(allowed_values, uri, attribute):
  215. """Assert that the uri's attribute is one of the allowed values."""
  216. value = getattr(uri, attribute)
  217. if value is not None and allowed_values and value not in allowed_values:
  218. raise exceptions.UnpermittedComponentError(
  219. attribute, value, allowed_values,
  220. )
  221. def ensure_required_components_exist(uri, required_components):
  222. """Assert that all required components are present in the URI."""
  223. missing_components = sorted([
  224. component
  225. for component in required_components
  226. if getattr(uri, component) is None
  227. ])
  228. if missing_components:
  229. raise exceptions.MissingComponentError(uri, *missing_components)
  230. def is_valid(value, matcher, require):
  231. """Determine if a value is valid based on the provided matcher.
  232. :param str value:
  233. Value to validate.
  234. :param matcher:
  235. Compiled regular expression to use to validate the value.
  236. :param require:
  237. Whether or not the value is required.
  238. """
  239. if require:
  240. return (value is not None
  241. and matcher.match(value))
  242. # require is False and value is not None
  243. return value is None or matcher.match(value)
  244. def authority_is_valid(authority, host=None, require=False):
  245. """Determine if the authority string is valid.
  246. :param str authority:
  247. The authority to validate.
  248. :param str host:
  249. (optional) The host portion of the authority to validate.
  250. :param bool require:
  251. (optional) Specify if authority must not be None.
  252. :returns:
  253. ``True`` if valid, ``False`` otherwise
  254. :rtype:
  255. bool
  256. """
  257. validated = is_valid(authority, misc.SUBAUTHORITY_MATCHER, require)
  258. if validated and host is not None:
  259. return host_is_valid(host, require)
  260. return validated
  261. def host_is_valid(host, require=False):
  262. """Determine if the host string is valid.
  263. :param str host:
  264. The host to validate.
  265. :param bool require:
  266. (optional) Specify if host must not be None.
  267. :returns:
  268. ``True`` if valid, ``False`` otherwise
  269. :rtype:
  270. bool
  271. """
  272. validated = is_valid(host, misc.HOST_MATCHER, require)
  273. if validated and host is not None and misc.IPv4_MATCHER.match(host):
  274. return valid_ipv4_host_address(host)
  275. elif validated and host is not None and misc.IPv6_MATCHER.match(host):
  276. return misc.IPv6_NO_RFC4007_MATCHER.match(host) is not None
  277. return validated
  278. def scheme_is_valid(scheme, require=False):
  279. """Determine if the scheme is valid.
  280. :param str scheme:
  281. The scheme string to validate.
  282. :param bool require:
  283. (optional) Set to ``True`` to require the presence of a scheme.
  284. :returns:
  285. ``True`` if the scheme is valid. ``False`` otherwise.
  286. :rtype:
  287. bool
  288. """
  289. return is_valid(scheme, misc.SCHEME_MATCHER, require)
  290. def path_is_valid(path, require=False):
  291. """Determine if the path component is valid.
  292. :param str path:
  293. The path string to validate.
  294. :param bool require:
  295. (optional) Set to ``True`` to require the presence of a path.
  296. :returns:
  297. ``True`` if the path is valid. ``False`` otherwise.
  298. :rtype:
  299. bool
  300. """
  301. return is_valid(path, misc.PATH_MATCHER, require)
  302. def query_is_valid(query, require=False):
  303. """Determine if the query component is valid.
  304. :param str query:
  305. The query string to validate.
  306. :param bool require:
  307. (optional) Set to ``True`` to require the presence of a query.
  308. :returns:
  309. ``True`` if the query is valid. ``False`` otherwise.
  310. :rtype:
  311. bool
  312. """
  313. return is_valid(query, misc.QUERY_MATCHER, require)
  314. def fragment_is_valid(fragment, require=False):
  315. """Determine if the fragment component is valid.
  316. :param str fragment:
  317. The fragment string to validate.
  318. :param bool require:
  319. (optional) Set to ``True`` to require the presence of a fragment.
  320. :returns:
  321. ``True`` if the fragment is valid. ``False`` otherwise.
  322. :rtype:
  323. bool
  324. """
  325. return is_valid(fragment, misc.FRAGMENT_MATCHER, require)
  326. def valid_ipv4_host_address(host):
  327. """Determine if the given host is a valid IPv4 address."""
  328. # If the host exists, and it might be IPv4, check each byte in the
  329. # address.
  330. return all([0 <= int(byte, base=10) <= 255 for byte in host.split('.')])
  331. _COMPONENT_VALIDATORS = {
  332. 'scheme': scheme_is_valid,
  333. 'path': path_is_valid,
  334. 'query': query_is_valid,
  335. 'fragment': fragment_is_valid,
  336. }
  337. _SUBAUTHORITY_VALIDATORS = set(['userinfo', 'host', 'port'])
  338. def subauthority_component_is_valid(uri, component):
  339. """Determine if the userinfo, host, and port are valid."""
  340. try:
  341. subauthority_dict = uri.authority_info()
  342. except exceptions.InvalidAuthority:
  343. return False
  344. # If we can parse the authority into sub-components and we're not
  345. # validating the port, we can assume it's valid.
  346. if component == 'host':
  347. return host_is_valid(subauthority_dict['host'])
  348. elif component != 'port':
  349. return True
  350. try:
  351. port = int(subauthority_dict['port'])
  352. except TypeError:
  353. # If the port wasn't provided it'll be None and int(None) raises a
  354. # TypeError
  355. return True
  356. return (0 <= port <= 65535)
  357. def ensure_components_are_valid(uri, validated_components):
  358. """Assert that all components are valid in the URI."""
  359. invalid_components = set([])
  360. for component in validated_components:
  361. if component in _SUBAUTHORITY_VALIDATORS:
  362. if not subauthority_component_is_valid(uri, component):
  363. invalid_components.add(component)
  364. # Python's peephole optimizer means that while this continue *is*
  365. # actually executed, coverage.py cannot detect that. See also,
  366. # https://bitbucket.org/ned/coveragepy/issues/198/continue-marked-as-not-covered
  367. continue # nocov: Python 2.7, 3.3, 3.4
  368. validator = _COMPONENT_VALIDATORS[component]
  369. if not validator(getattr(uri, component)):
  370. invalid_components.add(component)
  371. if invalid_components:
  372. raise exceptions.InvalidComponentsError(uri, *invalid_components)