374 lines
13 KiB
Python
374 lines
13 KiB
Python
"""Module containing the implementation of the URIMixin class."""
|
|
import warnings
|
|
|
|
from . import exceptions as exc
|
|
from . import misc
|
|
from . import normalizers
|
|
from . import validators
|
|
|
|
|
|
class URIMixin:
|
|
"""Mixin with all shared methods for URIs and IRIs."""
|
|
|
|
__hash__ = tuple.__hash__
|
|
|
|
def authority_info(self):
|
|
"""Return a dictionary with the ``userinfo``, ``host``, and ``port``.
|
|
|
|
If the authority is not valid, it will raise a
|
|
:class:`~rfc3986.exceptions.InvalidAuthority` Exception.
|
|
|
|
:returns:
|
|
``{'userinfo': 'username:password', 'host': 'www.example.com',
|
|
'port': '80'}``
|
|
:rtype: dict
|
|
:raises rfc3986.exceptions.InvalidAuthority:
|
|
If the authority is not ``None`` and can not be parsed.
|
|
"""
|
|
if not self.authority:
|
|
return {"userinfo": None, "host": None, "port": None}
|
|
|
|
match = self._match_subauthority()
|
|
|
|
if match is None:
|
|
# In this case, we have an authority that was parsed from the URI
|
|
# Reference, but it cannot be further parsed by our
|
|
# misc.SUBAUTHORITY_MATCHER. In this case it must not be a valid
|
|
# authority.
|
|
raise exc.InvalidAuthority(self.authority.encode(self.encoding))
|
|
|
|
# We had a match, now let's ensure that it is actually a valid host
|
|
# address if it is IPv4
|
|
matches = match.groupdict()
|
|
host = matches.get("host")
|
|
|
|
if (
|
|
host
|
|
and misc.IPv4_MATCHER.match(host)
|
|
and not validators.valid_ipv4_host_address(host)
|
|
):
|
|
# If we have a host, it appears to be IPv4 and it does not have
|
|
# valid bytes, it is an InvalidAuthority.
|
|
raise exc.InvalidAuthority(self.authority.encode(self.encoding))
|
|
|
|
return matches
|
|
|
|
def _match_subauthority(self):
|
|
return misc.SUBAUTHORITY_MATCHER.match(self.authority)
|
|
|
|
@property
|
|
def host(self):
|
|
"""If present, a string representing the host."""
|
|
try:
|
|
authority = self.authority_info()
|
|
except exc.InvalidAuthority:
|
|
return None
|
|
return authority["host"]
|
|
|
|
@property
|
|
def port(self):
|
|
"""If present, the port extracted from the authority."""
|
|
try:
|
|
authority = self.authority_info()
|
|
except exc.InvalidAuthority:
|
|
return None
|
|
return authority["port"]
|
|
|
|
@property
|
|
def userinfo(self):
|
|
"""If present, the userinfo extracted from the authority."""
|
|
try:
|
|
authority = self.authority_info()
|
|
except exc.InvalidAuthority:
|
|
return None
|
|
return authority["userinfo"]
|
|
|
|
def is_absolute(self):
|
|
"""Determine if this URI Reference is an absolute URI.
|
|
|
|
See http://tools.ietf.org/html/rfc3986#section-4.3 for explanation.
|
|
|
|
:returns: ``True`` if it is an absolute URI, ``False`` otherwise.
|
|
:rtype: bool
|
|
"""
|
|
return bool(misc.ABSOLUTE_URI_MATCHER.match(self.unsplit()))
|
|
|
|
def is_valid(self, **kwargs):
|
|
"""Determine if the URI is valid.
|
|
|
|
.. deprecated:: 1.1.0
|
|
|
|
Use the :class:`~rfc3986.validators.Validator` object instead.
|
|
|
|
:param bool require_scheme: Set to ``True`` if you wish to require the
|
|
presence of the scheme component.
|
|
:param bool require_authority: Set to ``True`` if you wish to require
|
|
the presence of the authority component.
|
|
:param bool require_path: Set to ``True`` if you wish to require the
|
|
presence of the path component.
|
|
:param bool require_query: Set to ``True`` if you wish to require the
|
|
presence of the query component.
|
|
:param bool require_fragment: Set to ``True`` if you wish to require
|
|
the presence of the fragment component.
|
|
:returns: ``True`` if the URI is valid. ``False`` otherwise.
|
|
:rtype: bool
|
|
"""
|
|
warnings.warn(
|
|
"Please use rfc3986.validators.Validator instead. "
|
|
"This method will be eventually removed.",
|
|
DeprecationWarning,
|
|
)
|
|
validators = [
|
|
(self.scheme_is_valid, kwargs.get("require_scheme", False)),
|
|
(self.authority_is_valid, kwargs.get("require_authority", False)),
|
|
(self.path_is_valid, kwargs.get("require_path", False)),
|
|
(self.query_is_valid, kwargs.get("require_query", False)),
|
|
(self.fragment_is_valid, kwargs.get("require_fragment", False)),
|
|
]
|
|
return all(v(r) for v, r in validators)
|
|
|
|
def authority_is_valid(self, require=False):
|
|
"""Determine if the authority component is valid.
|
|
|
|
.. deprecated:: 1.1.0
|
|
|
|
Use the :class:`~rfc3986.validators.Validator` object instead.
|
|
|
|
:param bool require:
|
|
Set to ``True`` to require the presence of this component.
|
|
:returns:
|
|
``True`` if the authority is valid. ``False`` otherwise.
|
|
:rtype:
|
|
bool
|
|
"""
|
|
warnings.warn(
|
|
"Please use rfc3986.validators.Validator instead. "
|
|
"This method will be eventually removed.",
|
|
DeprecationWarning,
|
|
)
|
|
try:
|
|
self.authority_info()
|
|
except exc.InvalidAuthority:
|
|
return False
|
|
|
|
return validators.authority_is_valid(
|
|
self.authority,
|
|
host=self.host,
|
|
require=require,
|
|
)
|
|
|
|
def scheme_is_valid(self, require=False):
|
|
"""Determine if the scheme component is valid.
|
|
|
|
.. deprecated:: 1.1.0
|
|
|
|
Use the :class:`~rfc3986.validators.Validator` object instead.
|
|
|
|
:param str require: Set to ``True`` to require the presence of this
|
|
component.
|
|
:returns: ``True`` if the scheme is valid. ``False`` otherwise.
|
|
:rtype: bool
|
|
"""
|
|
warnings.warn(
|
|
"Please use rfc3986.validators.Validator instead. "
|
|
"This method will be eventually removed.",
|
|
DeprecationWarning,
|
|
)
|
|
return validators.scheme_is_valid(self.scheme, require)
|
|
|
|
def path_is_valid(self, require=False):
|
|
"""Determine if the path component is valid.
|
|
|
|
.. deprecated:: 1.1.0
|
|
|
|
Use the :class:`~rfc3986.validators.Validator` object instead.
|
|
|
|
:param str require: Set to ``True`` to require the presence of this
|
|
component.
|
|
:returns: ``True`` if the path is valid. ``False`` otherwise.
|
|
:rtype: bool
|
|
"""
|
|
warnings.warn(
|
|
"Please use rfc3986.validators.Validator instead. "
|
|
"This method will be eventually removed.",
|
|
DeprecationWarning,
|
|
)
|
|
return validators.path_is_valid(self.path, require)
|
|
|
|
def query_is_valid(self, require=False):
|
|
"""Determine if the query component is valid.
|
|
|
|
.. deprecated:: 1.1.0
|
|
|
|
Use the :class:`~rfc3986.validators.Validator` object instead.
|
|
|
|
:param str require: Set to ``True`` to require the presence of this
|
|
component.
|
|
:returns: ``True`` if the query is valid. ``False`` otherwise.
|
|
:rtype: bool
|
|
"""
|
|
warnings.warn(
|
|
"Please use rfc3986.validators.Validator instead. "
|
|
"This method will be eventually removed.",
|
|
DeprecationWarning,
|
|
)
|
|
return validators.query_is_valid(self.query, require)
|
|
|
|
def fragment_is_valid(self, require=False):
|
|
"""Determine if the fragment component is valid.
|
|
|
|
.. deprecated:: 1.1.0
|
|
|
|
Use the Validator object instead.
|
|
|
|
:param str require: Set to ``True`` to require the presence of this
|
|
component.
|
|
:returns: ``True`` if the fragment is valid. ``False`` otherwise.
|
|
:rtype: bool
|
|
"""
|
|
warnings.warn(
|
|
"Please use rfc3986.validators.Validator instead. "
|
|
"This method will be eventually removed.",
|
|
DeprecationWarning,
|
|
)
|
|
return validators.fragment_is_valid(self.fragment, require)
|
|
|
|
def normalized_equality(self, other_ref):
|
|
"""Compare this URIReference to another URIReference.
|
|
|
|
:param URIReference other_ref: (required), The reference with which
|
|
we're comparing.
|
|
:returns: ``True`` if the references are equal, ``False`` otherwise.
|
|
:rtype: bool
|
|
"""
|
|
return tuple(self.normalize()) == tuple(other_ref.normalize())
|
|
|
|
def resolve_with(self, base_uri, strict=False):
|
|
"""Use an absolute URI Reference to resolve this relative reference.
|
|
|
|
Assuming this is a relative reference that you would like to resolve,
|
|
use the provided base URI to resolve it.
|
|
|
|
See http://tools.ietf.org/html/rfc3986#section-5 for more information.
|
|
|
|
:param base_uri: Either a string or URIReference. It must be an
|
|
absolute URI or it will raise an exception.
|
|
:returns: A new URIReference which is the result of resolving this
|
|
reference using ``base_uri``.
|
|
:rtype: :class:`URIReference`
|
|
:raises rfc3986.exceptions.ResolutionError:
|
|
If the ``base_uri`` does not at least have a scheme.
|
|
"""
|
|
if not isinstance(base_uri, URIMixin):
|
|
base_uri = type(self).from_string(base_uri)
|
|
|
|
if not base_uri.is_valid(require_scheme=True):
|
|
raise exc.ResolutionError(base_uri)
|
|
|
|
# This is optional per
|
|
# http://tools.ietf.org/html/rfc3986#section-5.2.1
|
|
base_uri = base_uri.normalize()
|
|
|
|
# The reference we're resolving
|
|
resolving = self
|
|
|
|
if not strict and resolving.scheme == base_uri.scheme:
|
|
resolving = resolving.copy_with(scheme=None)
|
|
|
|
# http://tools.ietf.org/html/rfc3986#page-32
|
|
if resolving.scheme is not None:
|
|
target = resolving.copy_with(
|
|
path=normalizers.normalize_path(resolving.path)
|
|
)
|
|
else:
|
|
if resolving.authority is not None:
|
|
target = resolving.copy_with(
|
|
scheme=base_uri.scheme,
|
|
path=normalizers.normalize_path(resolving.path),
|
|
)
|
|
else:
|
|
if resolving.path is None:
|
|
if resolving.query is not None:
|
|
query = resolving.query
|
|
else:
|
|
query = base_uri.query
|
|
target = resolving.copy_with(
|
|
scheme=base_uri.scheme,
|
|
authority=base_uri.authority,
|
|
path=base_uri.path,
|
|
query=query,
|
|
)
|
|
else:
|
|
if resolving.path.startswith("/"):
|
|
path = normalizers.normalize_path(resolving.path)
|
|
else:
|
|
path = normalizers.normalize_path(
|
|
misc.merge_paths(base_uri, resolving.path)
|
|
)
|
|
target = resolving.copy_with(
|
|
scheme=base_uri.scheme,
|
|
authority=base_uri.authority,
|
|
path=path,
|
|
query=resolving.query,
|
|
)
|
|
return target
|
|
|
|
def unsplit(self):
|
|
"""Create a URI string from the components.
|
|
|
|
:returns: The URI Reference reconstituted as a string.
|
|
:rtype: str
|
|
"""
|
|
# See http://tools.ietf.org/html/rfc3986#section-5.3
|
|
result_list = []
|
|
if self.scheme:
|
|
result_list.extend([self.scheme, ":"])
|
|
if self.authority:
|
|
result_list.extend(["//", self.authority])
|
|
if self.path:
|
|
result_list.append(self.path)
|
|
if self.query is not None:
|
|
result_list.extend(["?", self.query])
|
|
if self.fragment is not None:
|
|
result_list.extend(["#", self.fragment])
|
|
return "".join(result_list)
|
|
|
|
def copy_with(
|
|
self,
|
|
scheme=misc.UseExisting,
|
|
authority=misc.UseExisting,
|
|
path=misc.UseExisting,
|
|
query=misc.UseExisting,
|
|
fragment=misc.UseExisting,
|
|
):
|
|
"""Create a copy of this reference with the new components.
|
|
|
|
:param str scheme:
|
|
(optional) The scheme to use for the new reference.
|
|
:param str authority:
|
|
(optional) The authority to use for the new reference.
|
|
:param str path:
|
|
(optional) The path to use for the new reference.
|
|
:param str query:
|
|
(optional) The query to use for the new reference.
|
|
:param str fragment:
|
|
(optional) The fragment to use for the new reference.
|
|
:returns:
|
|
New URIReference with provided components.
|
|
:rtype:
|
|
URIReference
|
|
"""
|
|
attributes = {
|
|
"scheme": scheme,
|
|
"authority": authority,
|
|
"path": path,
|
|
"query": query,
|
|
"fragment": fragment,
|
|
}
|
|
for key, value in list(attributes.items()):
|
|
if value is misc.UseExisting:
|
|
del attributes[key]
|
|
uri = self._replace(**attributes)
|
|
uri.encoding = self.encoding
|
|
return uri
|