25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

354 lines
13KB

  1. """Module containing the implementation of the URIMixin class."""
  2. import warnings
  3. from . import exceptions as exc
  4. from . import misc
  5. from . import normalizers
  6. from . import validators
  7. class URIMixin(object):
  8. """Mixin with all shared methods for URIs and IRIs."""
  9. __hash__ = tuple.__hash__
  10. def authority_info(self):
  11. """Return a dictionary with the ``userinfo``, ``host``, and ``port``.
  12. If the authority is not valid, it will raise a
  13. :class:`~rfc3986.exceptions.InvalidAuthority` Exception.
  14. :returns:
  15. ``{'userinfo': 'username:password', 'host': 'www.example.com',
  16. 'port': '80'}``
  17. :rtype: dict
  18. :raises rfc3986.exceptions.InvalidAuthority:
  19. If the authority is not ``None`` and can not be parsed.
  20. """
  21. if not self.authority:
  22. return {'userinfo': None, 'host': None, 'port': None}
  23. match = self._match_subauthority()
  24. if match is None:
  25. # In this case, we have an authority that was parsed from the URI
  26. # Reference, but it cannot be further parsed by our
  27. # misc.SUBAUTHORITY_MATCHER. In this case it must not be a valid
  28. # authority.
  29. raise exc.InvalidAuthority(self.authority.encode(self.encoding))
  30. # We had a match, now let's ensure that it is actually a valid host
  31. # address if it is IPv4
  32. matches = match.groupdict()
  33. host = matches.get('host')
  34. if (host and misc.IPv4_MATCHER.match(host) and not
  35. validators.valid_ipv4_host_address(host)):
  36. # If we have a host, it appears to be IPv4 and it does not have
  37. # valid bytes, it is an InvalidAuthority.
  38. raise exc.InvalidAuthority(self.authority.encode(self.encoding))
  39. return matches
  40. def _match_subauthority(self):
  41. return misc.SUBAUTHORITY_MATCHER.match(self.authority)
  42. @property
  43. def host(self):
  44. """If present, a string representing the host."""
  45. try:
  46. authority = self.authority_info()
  47. except exc.InvalidAuthority:
  48. return None
  49. return authority['host']
  50. @property
  51. def port(self):
  52. """If present, the port extracted from the authority."""
  53. try:
  54. authority = self.authority_info()
  55. except exc.InvalidAuthority:
  56. return None
  57. return authority['port']
  58. @property
  59. def userinfo(self):
  60. """If present, the userinfo extracted from the authority."""
  61. try:
  62. authority = self.authority_info()
  63. except exc.InvalidAuthority:
  64. return None
  65. return authority['userinfo']
  66. def is_absolute(self):
  67. """Determine if this URI Reference is an absolute URI.
  68. See http://tools.ietf.org/html/rfc3986#section-4.3 for explanation.
  69. :returns: ``True`` if it is an absolute URI, ``False`` otherwise.
  70. :rtype: bool
  71. """
  72. return bool(misc.ABSOLUTE_URI_MATCHER.match(self.unsplit()))
  73. def is_valid(self, **kwargs):
  74. """Determine if the URI is valid.
  75. .. deprecated:: 1.1.0
  76. Use the :class:`~rfc3986.validators.Validator` object instead.
  77. :param bool require_scheme: Set to ``True`` if you wish to require the
  78. presence of the scheme component.
  79. :param bool require_authority: Set to ``True`` if you wish to require
  80. the presence of the authority component.
  81. :param bool require_path: Set to ``True`` if you wish to require the
  82. presence of the path component.
  83. :param bool require_query: Set to ``True`` if you wish to require the
  84. presence of the query component.
  85. :param bool require_fragment: Set to ``True`` if you wish to require
  86. the presence of the fragment component.
  87. :returns: ``True`` if the URI is valid. ``False`` otherwise.
  88. :rtype: bool
  89. """
  90. warnings.warn("Please use rfc3986.validators.Validator instead. "
  91. "This method will be eventually removed.",
  92. DeprecationWarning)
  93. validators = [
  94. (self.scheme_is_valid, kwargs.get('require_scheme', False)),
  95. (self.authority_is_valid, kwargs.get('require_authority', False)),
  96. (self.path_is_valid, kwargs.get('require_path', False)),
  97. (self.query_is_valid, kwargs.get('require_query', False)),
  98. (self.fragment_is_valid, kwargs.get('require_fragment', False)),
  99. ]
  100. return all(v(r) for v, r in validators)
  101. def authority_is_valid(self, require=False):
  102. """Determine if the authority component is valid.
  103. .. deprecated:: 1.1.0
  104. Use the :class:`~rfc3986.validators.Validator` object instead.
  105. :param bool require:
  106. Set to ``True`` to require the presence of this component.
  107. :returns:
  108. ``True`` if the authority is valid. ``False`` otherwise.
  109. :rtype:
  110. bool
  111. """
  112. warnings.warn("Please use rfc3986.validators.Validator instead. "
  113. "This method will be eventually removed.",
  114. DeprecationWarning)
  115. try:
  116. self.authority_info()
  117. except exc.InvalidAuthority:
  118. return False
  119. return validators.authority_is_valid(
  120. self.authority,
  121. host=self.host,
  122. require=require,
  123. )
  124. def scheme_is_valid(self, require=False):
  125. """Determine if the scheme component is valid.
  126. .. deprecated:: 1.1.0
  127. Use the :class:`~rfc3986.validators.Validator` object instead.
  128. :param str require: Set to ``True`` to require the presence of this
  129. component.
  130. :returns: ``True`` if the scheme is valid. ``False`` otherwise.
  131. :rtype: bool
  132. """
  133. warnings.warn("Please use rfc3986.validators.Validator instead. "
  134. "This method will be eventually removed.",
  135. DeprecationWarning)
  136. return validators.scheme_is_valid(self.scheme, require)
  137. def path_is_valid(self, require=False):
  138. """Determine if the path component is valid.
  139. .. deprecated:: 1.1.0
  140. Use the :class:`~rfc3986.validators.Validator` object instead.
  141. :param str require: Set to ``True`` to require the presence of this
  142. component.
  143. :returns: ``True`` if the path is valid. ``False`` otherwise.
  144. :rtype: bool
  145. """
  146. warnings.warn("Please use rfc3986.validators.Validator instead. "
  147. "This method will be eventually removed.",
  148. DeprecationWarning)
  149. return validators.path_is_valid(self.path, require)
  150. def query_is_valid(self, require=False):
  151. """Determine if the query component is valid.
  152. .. deprecated:: 1.1.0
  153. Use the :class:`~rfc3986.validators.Validator` object instead.
  154. :param str require: Set to ``True`` to require the presence of this
  155. component.
  156. :returns: ``True`` if the query is valid. ``False`` otherwise.
  157. :rtype: bool
  158. """
  159. warnings.warn("Please use rfc3986.validators.Validator instead. "
  160. "This method will be eventually removed.",
  161. DeprecationWarning)
  162. return validators.query_is_valid(self.query, require)
  163. def fragment_is_valid(self, require=False):
  164. """Determine if the fragment component is valid.
  165. .. deprecated:: 1.1.0
  166. Use the Validator object instead.
  167. :param str require: Set to ``True`` to require the presence of this
  168. component.
  169. :returns: ``True`` if the fragment is valid. ``False`` otherwise.
  170. :rtype: bool
  171. """
  172. warnings.warn("Please use rfc3986.validators.Validator instead. "
  173. "This method will be eventually removed.",
  174. DeprecationWarning)
  175. return validators.fragment_is_valid(self.fragment, require)
  176. def normalized_equality(self, other_ref):
  177. """Compare this URIReference to another URIReference.
  178. :param URIReference other_ref: (required), The reference with which
  179. we're comparing.
  180. :returns: ``True`` if the references are equal, ``False`` otherwise.
  181. :rtype: bool
  182. """
  183. return tuple(self.normalize()) == tuple(other_ref.normalize())
  184. def resolve_with(self, base_uri, strict=False):
  185. """Use an absolute URI Reference to resolve this relative reference.
  186. Assuming this is a relative reference that you would like to resolve,
  187. use the provided base URI to resolve it.
  188. See http://tools.ietf.org/html/rfc3986#section-5 for more information.
  189. :param base_uri: Either a string or URIReference. It must be an
  190. absolute URI or it will raise an exception.
  191. :returns: A new URIReference which is the result of resolving this
  192. reference using ``base_uri``.
  193. :rtype: :class:`URIReference`
  194. :raises rfc3986.exceptions.ResolutionError:
  195. If the ``base_uri`` is not an absolute URI.
  196. """
  197. if not isinstance(base_uri, URIMixin):
  198. base_uri = type(self).from_string(base_uri)
  199. if not base_uri.is_absolute():
  200. raise exc.ResolutionError(base_uri)
  201. # This is optional per
  202. # http://tools.ietf.org/html/rfc3986#section-5.2.1
  203. base_uri = base_uri.normalize()
  204. # The reference we're resolving
  205. resolving = self
  206. if not strict and resolving.scheme == base_uri.scheme:
  207. resolving = resolving.copy_with(scheme=None)
  208. # http://tools.ietf.org/html/rfc3986#page-32
  209. if resolving.scheme is not None:
  210. target = resolving.copy_with(
  211. path=normalizers.normalize_path(resolving.path)
  212. )
  213. else:
  214. if resolving.authority is not None:
  215. target = resolving.copy_with(
  216. scheme=base_uri.scheme,
  217. path=normalizers.normalize_path(resolving.path)
  218. )
  219. else:
  220. if resolving.path is None:
  221. if resolving.query is not None:
  222. query = resolving.query
  223. else:
  224. query = base_uri.query
  225. target = resolving.copy_with(
  226. scheme=base_uri.scheme,
  227. authority=base_uri.authority,
  228. path=base_uri.path,
  229. query=query
  230. )
  231. else:
  232. if resolving.path.startswith('/'):
  233. path = normalizers.normalize_path(resolving.path)
  234. else:
  235. path = normalizers.normalize_path(
  236. misc.merge_paths(base_uri, resolving.path)
  237. )
  238. target = resolving.copy_with(
  239. scheme=base_uri.scheme,
  240. authority=base_uri.authority,
  241. path=path,
  242. query=resolving.query
  243. )
  244. return target
  245. def unsplit(self):
  246. """Create a URI string from the components.
  247. :returns: The URI Reference reconstituted as a string.
  248. :rtype: str
  249. """
  250. # See http://tools.ietf.org/html/rfc3986#section-5.3
  251. result_list = []
  252. if self.scheme:
  253. result_list.extend([self.scheme, ':'])
  254. if self.authority:
  255. result_list.extend(['//', self.authority])
  256. if self.path:
  257. result_list.append(self.path)
  258. if self.query is not None:
  259. result_list.extend(['?', self.query])
  260. if self.fragment is not None:
  261. result_list.extend(['#', self.fragment])
  262. return ''.join(result_list)
  263. def copy_with(self, scheme=misc.UseExisting, authority=misc.UseExisting,
  264. path=misc.UseExisting, query=misc.UseExisting,
  265. fragment=misc.UseExisting):
  266. """Create a copy of this reference with the new components.
  267. :param str scheme:
  268. (optional) The scheme to use for the new reference.
  269. :param str authority:
  270. (optional) The authority to use for the new reference.
  271. :param str path:
  272. (optional) The path to use for the new reference.
  273. :param str query:
  274. (optional) The query to use for the new reference.
  275. :param str fragment:
  276. (optional) The fragment to use for the new reference.
  277. :returns:
  278. New URIReference with provided components.
  279. :rtype:
  280. URIReference
  281. """
  282. attributes = {
  283. 'scheme': scheme,
  284. 'authority': authority,
  285. 'path': path,
  286. 'query': query,
  287. 'fragment': fragment,
  288. }
  289. for key, value in list(attributes.items()):
  290. if value is misc.UseExisting:
  291. del attributes[key]
  292. uri = self._replace(**attributes)
  293. uri.encoding = self.encoding
  294. return uri