Created
February 4, 2021 08:59
-
-
Save technige/3bb8e37ec2d238d5687f172da500a9c8 to your computer and use it in GitHub Desktop.
Implementation of RFC 3986 -- Uniform Resource Identifier (URI): Generic Syntax
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# -*- coding: utf-8 -*- | |
# Copyright 2012-2015, Nigel Small | |
# | |
# Licensed under the Apache License, Version 2.0 (the "License"); | |
# you may not use this file except in compliance with the License. | |
# You may obtain a copy of the License at | |
# | |
# http://www.apache.org/licenses/LICENSE-2.0 | |
# | |
# Unless required by applicable law or agreed to in writing, software | |
# distributed under the License is distributed on an "AS IS" BASIS, | |
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
# See the License for the specific language governing permissions and | |
# limitations under the License. | |
""" | |
An implementation of URIs from RFC 3986 (URI Generic Syntax). | |
See: http://www.ietf.org/rfc/rfc3986.txt | |
""" | |
from __future__ import unicode_literals | |
from .kvlist import KeyValueList | |
from .util import bstr, ustr | |
__all__ = ["general_delimiters", "subcomponent_delimiters", | |
"reserved", "unreserved", "percent_encode", "percent_decode", | |
"ParameterString", "Authority", "Path", "Query", "URI"] | |
percent_codes = dict(zip(bytes(bytearray(range(256))), | |
[bstr("%" + hex(n)[2:].upper().zfill(2)) for n in range(256)])) | |
unhex = dict(zip(b"0123456789ABCDEFabcdef", list(range(0, 16)) + list(range(10, 16)))) | |
# RFC 3986 § 2.2. | |
general_delimiters = ":/?#[]@" | |
subcomponent_delimiters = "!$&'()*+,;=" | |
reserved = general_delimiters + subcomponent_delimiters | |
# RFC 3986 § 2.3. | |
unreserved = bstr("ABCDEFGHIJKLMNOPQRSTUVWXYZ" | |
"abcdefghijklmnopqrstuvwxyz" | |
"0123456789-._~") | |
# RFC 3986 § 2.1. | |
def percent_encode(data, safe=None): | |
""" Percent encode a string of data, optionally keeping certain characters | |
unencoded. | |
""" | |
if data is None: | |
return None | |
if isinstance(data, (tuple, list, set)): | |
return "&".join( | |
percent_encode(value, safe=safe) | |
for value in data | |
) | |
if isinstance(data, dict): | |
return "&".join( | |
key + "=" + percent_encode(value, safe=safe) | |
for key, value in data.items() | |
) | |
unreserved_and_safe = unreserved + bstr(safe or b"") | |
chars = bstr(data) | |
out = [] | |
p = 0 | |
for i, char in enumerate(chars): | |
if char == b"%" or char not in unreserved_and_safe: | |
out.append(chars[p:i]) | |
out.append(percent_codes[char]) | |
p = i + 1 | |
out.append(chars[p:]) | |
if isinstance(data, (bytes, bytearray)): | |
return b"".join(out) | |
else: | |
return u"".join(map(ustr, out)) | |
def percent_decode(data): | |
""" Percent decode a string of data. | |
""" | |
if data is None: | |
return None | |
parts = bstr(data).split(b"%") | |
out = [parts[0].replace(b"+", b" ")] | |
for i, part in enumerate(parts[1:], start=1): | |
out.append(bytes(bytearray([(unhex[part[0]] << 4) + unhex[part[1]]]))) | |
out.append(part[2:].replace(b"+", b" ")) | |
return b"".join(out).decode("utf-8") | |
class Part(object): | |
""" Internal base class for all URI component parts. | |
""" | |
@classmethod | |
def _cast(cls, obj): | |
""" Convert the object supplied to an instance of this class, if | |
possible. | |
""" | |
if obj is None: | |
return cls(None) | |
elif isinstance(obj, cls): | |
return obj | |
else: | |
return cls(ustr(obj)) | |
def __init__(self): | |
pass | |
def __repr__(self): | |
if self.string is None: | |
return "%s" % self.__class__.__name__ | |
else: | |
return "%s(%r)" % (self.__class__.__name__, self.string) | |
def __str__(self): | |
return self.string or "" | |
def __eq__(self, other): | |
if other is None: | |
return self.string is None | |
try: | |
other_string = other.string | |
except AttributeError: | |
other_string = self._cast(other).string | |
return self.string == other_string | |
def __ne__(self, other): | |
return not self.__eq__(other) | |
def __bool__(self): | |
return bool(self.string) | |
def __nonzero__(self): | |
return bool(self.string) | |
def __len__(self): | |
return len(ustr(self)) | |
def __iter__(self): | |
return iter(self.string) | |
@property | |
def string(self): | |
raise NotImplementedError() | |
class ParameterString(Part): | |
__string = NotImplemented | |
def __init__(self, string, separator): | |
super(ParameterString, self).__init__() | |
self.__separator = separator | |
self.__none = string is None | |
self.__parameters = KeyValueList() | |
if string: | |
bits = string.split(self.__separator) | |
for bit in bits: | |
if "=" in bit: | |
key, value = map(percent_decode, bit.partition("=")[0::2]) | |
else: | |
key, value = percent_decode(bit), None | |
self.__parameters.append(key, value) | |
def __len__(self): | |
return self.__parameters.__len__() | |
def __bool__(self): | |
return bool(self.__parameters) | |
def __nonzero__(self): | |
return bool(self.__parameters) | |
def __contains__(self, item): | |
return self.__parameters.__contains__(item) | |
def __hash__(self): | |
return hash(self.string) | |
def __iter__(self): | |
return self.__parameters.__iter__() | |
def __getitem__(self, index): | |
if isinstance(index, slice): | |
out = ParameterString("", self.__separator) | |
out.__parameters.extend(self.__parameters.__getitem__(index)) | |
return out | |
else: | |
return self.__parameters.__getitem__(index) | |
def __getslice__(self, start, stop): | |
out = ParameterString("", self.__separator) | |
out.__parameters.extend(self.__parameters.__getslice__(start, stop)) | |
return out | |
def get(self, name, index=0): | |
if not self.__parameters.has_key(name): | |
raise KeyError(name) | |
for i, value in enumerate(self.__parameters.get(name)): | |
if i == index: | |
return value | |
raise IndexError("Parameter {0} does not have {1} " | |
"values".format(name, index)) | |
def get_all(self, name): | |
if not self.__parameters.has_key(name): | |
raise KeyError(name) | |
return list(self.__parameters.get(name)) | |
@property | |
def string(self): | |
if self.__string is NotImplemented: | |
if self.__none: | |
self.__string = None | |
else: | |
bits = [] | |
for key, value in self.__parameters: | |
if value is None: | |
bits.append(percent_encode(key)) | |
else: | |
bits.append(percent_encode(key) + "=" + percent_encode(value)) | |
self.__string = self.__separator.join(bits) | |
return self.__string | |
class Authority(Part): | |
""" A host name plus optional port and user information detail. | |
**Syntax** | |
``authority := [ user_info "@" ] host [ ":" port ]`` | |
.. seealso:: | |
`RFC 3986 § 3.2`_ | |
.. _`RFC 3986 § 3.2`: http://tools.ietf.org/html/rfc3986#section-3.2 | |
""" | |
@classmethod | |
def _parse_host_port(cls, string): | |
host, colon, port = string.partition(":") | |
if colon: | |
return host, int(port) | |
else: | |
return host, None | |
__instances = {} | |
def __new__(cls, string=None): | |
if string is None: | |
return super(Authority, cls).__new__(cls) | |
try: | |
inst = cls.__instances[string] | |
except KeyError: | |
inst = super(Authority, cls).__new__(cls) | |
user_info, at, host_port = string.rpartition("@") | |
if at: | |
inst.__user_info = percent_decode(user_info) | |
inst.__host, inst.__port = cls._parse_host_port(host_port) | |
cls.__instances[string] = inst | |
return inst | |
__user_info = None | |
__host = None | |
__port = None | |
__string = NotImplemented | |
def __init__(self, string=None): | |
super(Authority, self).__init__() | |
def __bool__(self): | |
return bool(self.__user_info or self.__host or self.__port) | |
def __nonzero__(self): | |
return bool(self.__user_info or self.__host or self.__port) | |
def __hash__(self): | |
return hash(self.string) | |
@property | |
def host(self): | |
""" The host part of this authority component, an empty string if host | |
is empty or :py:const:`None` if undefined. | |
:: | |
>>> Authority(None).host | |
None | |
>>> Authority("").host | |
'' | |
>>> Authority("example.com").host | |
'example.com' | |
>>> Authority("example.com:8080").host | |
'example.com' | |
>>> Authority("[email protected]").host | |
'example.com' | |
>>> Authority("[email protected]:8080").host | |
'example.com' | |
:return: | |
""" | |
return self.__host | |
@property | |
def host_port(self): | |
""" The host and port parts of this authority component or | |
:py:const:`None` if undefined. | |
:: | |
>>> Authority(None).host_port | |
None | |
>>> Authority("").host_port | |
'' | |
>>> Authority("example.com").host_port | |
'example.com' | |
>>> Authority("example.com:8080").host_port | |
'example.com:8080' | |
>>> Authority("[email protected]").host_port | |
'example.com' | |
>>> Authority("[email protected]:8080").host_port | |
'example.com:8080' | |
:return: | |
""" | |
u = [self.__host] | |
if self.__port is not None: | |
u += [":", ustr(self.__port)] | |
return "".join(u) | |
@property | |
def port(self): | |
""" The port part of this authority component or :py:const:`None` if | |
undefined. | |
:: | |
>>> Authority(None).port | |
None | |
>>> Authority("").port | |
None | |
>>> Authority("example.com").port | |
None | |
>>> Authority("example.com:8080").port | |
8080 | |
>>> Authority("[email protected]").port | |
None | |
>>> Authority("[email protected]:8080").port | |
8080 | |
:return: | |
""" | |
return self.__port | |
@property | |
def string(self): | |
""" The full string value of this authority component or | |
:`py:const:`None` if undefined. | |
:: | |
>>> Authority(None).string | |
None | |
>>> Authority("").string | |
'' | |
>>> Authority("example.com").string | |
'example.com' | |
>>> Authority("example.com:8080").string | |
'example.com:8080' | |
>>> Authority("[email protected]").string | |
'[email protected]' | |
>>> Authority("[email protected]:8080").string | |
'[email protected]:8080' | |
:return: | |
""" | |
if self.__string is NotImplemented: | |
if self.__host is None: | |
self.__string = None | |
else: | |
u = [] | |
if self.__user_info is not None: | |
u += [percent_encode(self.__user_info), "@"] | |
u += [self.__host] | |
if self.__port is not None: | |
u += [":", ustr(self.__port)] | |
self.__string = "".join(u) | |
return self.__string | |
@property | |
def user_info(self): | |
""" The user information part of this authority component or | |
:py:const:`None` if undefined. | |
:: | |
>>> Authority(None).user_info | |
None | |
>>> Authority("").user_info | |
None | |
>>> Authority("example.com").user_info | |
None | |
>>> Authority("example.com:8080").user_info | |
None | |
>>> Authority("[email protected]").user_info | |
'bob' | |
>>> Authority("[email protected]:8080").user_info | |
'bob' | |
:return: | |
""" | |
return self.__user_info | |
def with_user_info(self, string): | |
target = Authority(None) | |
target.__user_info = percent_decode(string) | |
target.__host = self.__host | |
target.__port = self.__port | |
return target | |
def with_host_port(self, string): | |
target = Authority(None) | |
target.__user_info = self.__user_info | |
target.__host, target.__port = self._parse_host_port(string) | |
return target | |
def with_host(self, string): | |
target = Authority(None) | |
target.__user_info = self.__user_info | |
target.__host = string | |
target.__port = self.__port | |
return target | |
def with_port(self, value): | |
target = Authority(None) | |
target.__user_info = self.__user_info | |
target.__host = self.__host | |
target.__port = int(value) | |
return target | |
class Path(Part): | |
__segments = None | |
__string = NotImplemented | |
def __init__(self, string): | |
super(Path, self).__init__() | |
if string is not None: | |
self.__segments = tuple(map(percent_decode, string.split("/"))) | |
def __hash__(self): | |
return hash(self.string) | |
@property | |
def string(self): | |
if self.__string is NotImplemented: | |
if self.__segments is None: | |
self.__string = None | |
else: | |
self.__string = "/".join(map(percent_encode, self.__segments)) | |
return self.__string | |
@property | |
def segments(self): | |
if self.__segments is None: | |
return () | |
else: | |
return self.__segments | |
def __iter__(self): | |
return iter(self.__segments) | |
def remove_dot_segments(self): | |
""" Implementation of RFC3986, section 5.2.4 | |
""" | |
inp = self.string | |
out = "" | |
while inp: | |
if inp.startswith("../"): | |
inp = inp[3:] | |
elif inp.startswith("./"): | |
inp = inp[2:] | |
elif inp.startswith("/./"): | |
inp = inp[2:] | |
elif inp == "/.": | |
inp = "/" | |
elif inp.startswith("/../"): | |
inp = inp[3:] | |
out = out.rpartition("/")[0] | |
elif inp == "/..": | |
inp = "/" | |
out = out.rpartition("/")[0] | |
elif inp in (".", ".."): | |
inp = "" | |
else: | |
if inp.startswith("/"): | |
inp = inp[1:] | |
out += "/" | |
seg, slash, inp = inp.partition("/") | |
out += seg | |
inp = slash + inp | |
return Path(out) | |
def with_trailing_slash(self): | |
if self.__segments is None: | |
return self | |
s = self.string | |
if s.endswith("/"): | |
return self | |
else: | |
return Path(s + "/") | |
def without_trailing_slash(self): | |
if self.__segments is None: | |
return self | |
s = self.string | |
if s.endswith("/"): | |
return Path(s[:-1]) | |
else: | |
return self | |
class Query(ParameterString): | |
SEPARATOR = "&" | |
def __init__(self, string): | |
super(Query, self).__init__(string, self.SEPARATOR) | |
def __hash__(self): | |
return hash(self.string) | |
class URI(Part): | |
""" Uniform Resource Identifier. | |
.. seealso:: | |
`RFC 3986`_ | |
.. _`RFC 3986`: http://tools.ietf.org/html/rfc3986 | |
""" | |
@classmethod | |
def build(cls, **parts): | |
""" Build a URI object from named parts. The part names available are: | |
- string | |
- hierarchical_part | |
- absolute_path_reference | |
- authority | |
- host_port | |
- scheme | |
- user_info | |
- host | |
- port | |
- path | |
- query | |
- fragment | |
""" | |
uri = URI(parts.get("string")) | |
uri.__set_hierarchical_part(parts.get("hierarchical_part")) | |
uri.__set_absolute_path_reference(parts.get("absolute_path_reference")) | |
uri.__set_authority(parts.get("authority")) | |
uri.__set_host_port(parts.get("host_port")) | |
uri.__set_scheme(parts.get("scheme")) | |
uri.__set_user_info(parts.get("user_info")) | |
uri.__set_host(parts.get("host")) | |
uri.__set_port(parts.get("port")) | |
uri.__set_path(parts.get("path")) | |
uri.__set_query(parts.get("query")) | |
uri.__set_fragment(parts.get("fragment")) | |
if parts and uri.__path is None: | |
uri.__path = Path("") | |
return uri | |
@classmethod | |
def _partition_fragment(cls, value): | |
value, _, fragment = value.partition("#") | |
if fragment: | |
return value, percent_decode(fragment) | |
else: | |
return value, None | |
@classmethod | |
def _partition_query(cls, value): | |
value, question_mark, query = value.partition("?") | |
if query: | |
return value, Query(query) | |
else: | |
return value, None | |
@classmethod | |
def _parse_hierarchical_part(cls, value): | |
if value.startswith("//"): | |
authority, slash, path = value[2:].partition("/") | |
if slash: | |
return Authority(authority), Path(slash + path) | |
else: | |
return Authority(authority), Path("") | |
else: | |
return None, Path(value) | |
def __new__(cls, value=None): | |
if isinstance(value, cls): | |
return value | |
inst = super(cls, URI).__new__(cls) | |
if value is None: | |
return inst | |
try: | |
if value.__uri__ is None: | |
return inst | |
except AttributeError: | |
pass | |
try: | |
value = ustr(value.__uri__) | |
except AttributeError: | |
value = ustr(value) | |
# scheme | |
if ":" in value: | |
inst.__scheme, _, value = value.partition(":") | |
inst.__scheme = percent_decode(inst.__scheme) | |
else: | |
inst.__scheme = None | |
# fragment | |
value, inst.__fragment = cls._partition_fragment(value) | |
# query | |
value, inst.__query = cls._partition_query(value) | |
# hierarchical part | |
inst.__authority, inst.__path = cls._parse_hierarchical_part(value) | |
return inst | |
__scheme = None | |
__authority = None | |
__path = None | |
__query = None | |
__fragment = None | |
__string = NotImplemented | |
def __init__(self, value=None): | |
Part.__init__(self) | |
def __hash__(self): | |
return hash(self.string) | |
def __add__(self, other): | |
return URI("{0}{1}".format(self, other)) | |
@property | |
def __uri__(self): | |
return self.string | |
def __set_hierarchical_part(self, string): | |
if string is not None: | |
self.__authority, self.__path = self._parse_hierarchical_part(string) | |
def __set_absolute_path_reference(self, string): | |
if string is not None: | |
string, self.__fragment = self._partition_fragment(string) | |
string, self.__query = self._partition_query(string) | |
self.__path = Path(string) | |
def __set_authority(self, string): | |
if string is not None: | |
self.__authority = Authority(string) | |
def __set_host_port(self, string): | |
if string is not None: | |
if self.__authority is None: | |
self.__authority = Authority(string) | |
else: | |
host, port = Authority._parse_host_port(string) | |
self.__authority._Authority__host = host | |
self.__authority._Authority__port = port | |
def __set_scheme(self, string): | |
if string is not None: | |
self.__scheme = string | |
def __set_user_info(self, string): | |
if string is not None: | |
if self.__authority is None: | |
self.__authority = Authority("") | |
self.__authority._Authority__user_info = string | |
def __set_host(self, string): | |
if string is not None: | |
if self.__authority is None: | |
self.__authority = Authority(string) | |
else: | |
self.__authority._Authority__host = string | |
def __set_port(self, number): | |
if number is not None: | |
if self.__authority is None: | |
self.__authority = Authority("") | |
self.__authority._Authority__port = number | |
def __set_path(self, string): | |
if string is not None: | |
self.__path = Path(string) | |
def __set_query(self, string): | |
if string is not None: | |
self.__query = Query(string) | |
def __set_fragment(self, string): | |
if string is not None: | |
self.__fragment = string | |
@property | |
def string(self): | |
""" The full percent-encoded string value of this URI or | |
:py:const:`None` if undefined. | |
:: | |
>>> URI(None).string | |
None | |
>>> URI("").string | |
'' | |
>>> URI("http://example.com").string | |
'example.com' | |
>>> URI("foo/bar").string | |
'foo/bar' | |
>>> URI("http://[email protected]:8080/data/report.html?date=2000-12-25#summary").string | |
'http://[email protected]:8080/data/report.html?date=2000-12-25#summary' | |
**Component Definition:** | |
:: | |
https://[email protected]:8080/data/report.html?date=2000-12-25#summary | |
\___________________________________________________________________/ | |
| | |
string | |
:rtype: percent-encoded string or :py:const:`None` | |
.. note:: | |
Unlike ``string``, the ``__str__`` method will always return a | |
string, even when the URI is undefined; in this case, an empty | |
string is returned instead of :py:const:`None`. | |
""" | |
if self.__string is NotImplemented: | |
if self.__path is None: | |
self.__string = None | |
else: | |
u = [] | |
if self.__scheme is not None: | |
u += [percent_encode(self.__scheme), ":"] | |
if self.__authority is not None: | |
u += ["//", ustr(self.__authority)] | |
u += [ustr(self.__path)] | |
if self.__query is not None: | |
u += ["?", ustr(self.__query)] | |
if self.__fragment is not None: | |
u += ["#", percent_encode(self.__fragment)] | |
self.__string = "".join(u) | |
return self.__string | |
@property | |
def scheme(self): | |
""" The scheme part of this URI or :py:const:`None` if undefined. | |
**Component Definition:** | |
:: | |
https://[email protected]:8080/data/report.html?date=2000-12-25#summary | |
\___/ | |
| | |
scheme | |
:rtype: unencoded string or :py:const:`None` | |
""" | |
return self.__scheme | |
@property | |
def authority(self): | |
""" The authority part of this URI or :py:const:`None` if undefined. | |
**Component Definition:** | |
:: | |
https://[email protected]:8080/data/report.html?date=2000-12-25#summary | |
\__________________/ | |
| | |
authority | |
:rtype: :py:class:`Authority <httpstream.uri.Authority>` instance or | |
:py:const:`None` | |
""" | |
return self.__authority | |
@property | |
def user_info(self): | |
""" The user information part of this URI or :py:const:`None` if | |
undefined. | |
**Component Definition:** | |
:: | |
https://[email protected]:8080/data/report.html?date=2000-12-25#summary | |
\_/ | |
| | |
user_info | |
:return: string value of user information part or :py:const:`None` | |
:rtype: unencoded string or :py:const:`None` | |
""" | |
if self.__authority is None: | |
return None | |
else: | |
return self.__authority.user_info | |
@property | |
def host(self): | |
""" The *host* part of this URI or :py:const:`None` if undefined. | |
:: | |
>>> URI(None).host | |
None | |
>>> URI("").host | |
None | |
>>> URI("http://example.com").host | |
'example.com' | |
>>> URI("http://example.com:8080/data").host | |
'example.com' | |
**Component Definition:** | |
:: | |
https://[email protected]:8080/data/report.html?date=2000-12-25#summary | |
\_________/ | |
| | |
host | |
:return: | |
:rtype: unencoded string or :py:const:`None` | |
""" | |
if self.__authority is None: | |
return None | |
else: | |
return self.__authority.host | |
@property | |
def port(self): | |
""" The *port* part of this URI or :py:const:`None` if undefined. | |
:: | |
>>> URI(None).port | |
None | |
>>> URI("").port | |
None | |
>>> URI("http://example.com").port | |
None | |
>>> URI("http://example.com:8080/data").port | |
8080 | |
**Component Definition:** | |
:: | |
https://[email protected]:8080/data/report.html?date=2000-12-25#summary | |
\__/ | |
| | |
port | |
:return: | |
:rtype: integer or :py:const:`None` | |
""" | |
if self.__authority is None: | |
return None | |
else: | |
return self.__authority.port | |
@property | |
def host_port(self): | |
""" The *host* and *port* parts of this URI separated by a colon or | |
:py:const:`None` if both are undefined. | |
:: | |
>>> URI(None).host_port | |
None | |
>>> URI("").host_port | |
None | |
>>> URI("http://example.com").host_port | |
'example.com' | |
>>> URI("http://example.com:8080/data").host_port | |
'example.com:8080' | |
>>> URI("http://[email protected]:8080/data").host_port | |
'example.com:8080' | |
**Component Definition:** | |
:: | |
https://[email protected]:8080/data/report.html?date=2000-12-25#summary | |
\______________/ | |
| | |
host_port | |
:return: | |
:rtype: percent-encoded string or :py:const:`None` | |
""" | |
if self.__authority is None: | |
return None | |
else: | |
return self.__authority.host_port | |
@property | |
def path(self): | |
""" The *path* part of this URI or :py:const:`None` if undefined. | |
**Component Definition:** | |
:: | |
https://[email protected]:8080/data/report.html?date=2000-12-25#summary | |
\_______________/ | |
| | |
path | |
:return: | |
:rtype: :py:class:`Path <httpstream.uri.Path>` instance or | |
:py:const:`None` | |
""" | |
return self.__path | |
@property | |
def query(self): | |
""" The *query* part of this URI or :py:const:`None` if undefined. | |
**Component Definition:** | |
:: | |
https://[email protected]:8080/data/report.html?date=2000-12-25#summary | |
\_____________/ | |
| | |
query | |
:rtype: :py:class:`Query <httpstream.uri.Query>` instance or | |
:py:const:`None` | |
""" | |
return self.__query | |
@property | |
def fragment(self): | |
""" The *fragment* part of this URI or :py:const:`None` if undefined. | |
**Component Definition:** | |
:: | |
https://[email protected]:8080/data/report.html?date=2000-12-25#summary | |
\_____/ | |
| | |
fragment | |
:return: | |
:rtype: unencoded string or :py:const:`None` | |
""" | |
return self.__fragment | |
@property | |
def hierarchical_part(self): | |
""" The authority and path parts of this URI or :py:const:`None` if | |
undefined. | |
**Component Definition:** | |
:: | |
https://[email protected]:8080/data/report.html?date=2000-12-25#summary | |
\___________________________________/ | |
| | |
hierarchical_part | |
:return: combined string values of authority and path parts or | |
:py:const:`None` | |
:rtype: percent-encoded string or :py:const:`None` | |
""" | |
if self.__path is None: | |
return None | |
u = [] | |
if self.__authority is not None: | |
u += ["//", ustr(self.__authority)] | |
u += [ustr(self.__path)] | |
return "".join(u) | |
@property | |
def absolute_path_reference(self): | |
""" The path, query and fragment parts of this URI or :py:const:`None` | |
if undefined. | |
**Component Definition:** | |
:: | |
https://[email protected]:8080/data/report.html?date=2000-12-25#summary | |
\_______________________________________/ | |
| | |
absolute_path_reference | |
:return: combined string values of path, query and fragment parts or | |
:py:const:`None` | |
:rtype: percent-encoded string or :py:const:`None` | |
""" | |
if self.__path is None: | |
return None | |
u = [ustr(self.__path)] | |
if self.__query is not None: | |
u += ["?", ustr(self.__query)] | |
if self.__fragment is not None: | |
u += ["#", percent_encode(self.__fragment)] | |
return "".join(u) | |
def _merge_path(self, relative_path_reference): | |
if self.__authority is not None and not self.__path: | |
return Path("/" + ustr(relative_path_reference)) | |
elif "/" in self.__path.string: | |
segments = self.__path.segments[:-1] + ("",) | |
return Path("/".join(segments) + ustr(relative_path_reference)) | |
else: | |
return relative_path_reference | |
def resolve(self, reference, strict=True): | |
""" Transform a reference relative to this URI to produce a full target | |
URI. | |
.. seealso:: | |
`RFC 3986 § 5.2.2`_ | |
.. _`RFC 3986 § 5.2.2`: http://tools.ietf.org/html/rfc3986#section-5.2.2 | |
""" | |
if reference is None: | |
return None | |
reference = self._cast(reference) | |
target = URI(None) | |
if not strict and reference.__scheme == self.__scheme: | |
reference_scheme = None | |
else: | |
reference_scheme = reference.__scheme | |
if reference_scheme is not None: | |
target.__scheme = reference_scheme | |
target.__authority = reference.__authority | |
target.__path = reference.__path.remove_dot_segments() | |
target.__query = reference.__query | |
else: | |
if reference.__authority is not None: | |
target.__authority = reference.__authority | |
target.__path = reference.__path.remove_dot_segments() | |
target.__query = reference.__query | |
else: | |
if not reference.path: | |
target.__path = self.__path | |
if reference.__query is not None: | |
target.__query = reference.__query | |
else: | |
target.__query = self.__query | |
else: | |
if ustr(reference.__path).startswith("/"): | |
target.__path = reference.__path.remove_dot_segments() | |
else: | |
target.__path = self._merge_path(reference.__path) | |
target.__path = target.__path.remove_dot_segments() | |
target.__query = reference.__query | |
target.__authority = self.__authority | |
target.__scheme = self.__scheme | |
target.__fragment = reference.__fragment | |
return target | |
def with_hierarchical_part(self, string): | |
target = URI(None) | |
target.__scheme = self.__scheme | |
target.__authority, target.__path = self._parse_hierarchical_part(string) | |
target.__query = self.__query | |
target.__fragment = self.__fragment | |
return target | |
def with_absolute_path_reference(self, string): | |
target = URI(None) | |
target.__scheme = self.__scheme | |
target.__authority = self.__authority | |
string, target.__fragment = self._partition_fragment(string) | |
string, target.__query = self._partition_query(string) | |
target.__path = Path(string) | |
return target | |
def with_authority(self, string): | |
target = URI(None) | |
target.__scheme = self.__scheme | |
target.__authority = Authority(string) | |
target.__path = self.__path | |
target.__query = self.__query | |
target.__fragment = self.__fragment | |
return target | |
def with_scheme(self, string): | |
target = URI(None) | |
target.__scheme = percent_decode(string) | |
target.__authority = self.__authority | |
target.__path = self.__path or Path("") | |
target.__query = self.__query | |
target.__fragment = self.__fragment | |
return target | |
def with_user_info(self, string): | |
target = URI(None) | |
target.__scheme = self.__scheme | |
if self.__authority: | |
target.__authority = self.__authority.with_user_info(string) | |
else: | |
target.__authority = Authority().with_user_info(string) | |
target.__path = self.__path or Path("") | |
target.__query = self.__query | |
target.__fragment = self.__fragment | |
return target | |
def with_host_port(self, string): | |
target = URI(None) | |
target.__scheme = self.__scheme | |
if self.__authority: | |
target.__authority = self.__authority.with_host_port(string) | |
else: | |
target.__authority = Authority().with_host_port(string) | |
target.__path = self.__path or Path("") | |
target.__query = self.__query | |
target.__fragment = self.__fragment | |
return target | |
def with_host(self, string): | |
target = URI(None) | |
target.__scheme = self.__scheme | |
if self.__authority: | |
target.__authority = self.__authority.with_host(string) | |
else: | |
target.__authority = Authority().with_host(string) | |
target.__path = self.__path or Path("") | |
target.__query = self.__query | |
target.__fragment = self.__fragment | |
return target | |
def with_port(self, value): | |
target = URI(None) | |
target.__scheme = self.__scheme | |
if self.__authority: | |
target.__authority = self.__authority.with_port(value) | |
else: | |
target.__authority = Authority().with_port(value) | |
target.__path = self.__path or Path("") | |
target.__query = self.__query | |
target.__fragment = self.__fragment | |
return target | |
def with_path(self, string): | |
target = URI(None) | |
target.__scheme = self.__scheme | |
target.__authority = self.__authority | |
target.__path = Path(string) | |
target.__query = self.__query | |
target.__fragment = self.__fragment | |
return target | |
def with_query(self, string): | |
target = URI(None) | |
target.__scheme = self.__scheme | |
target.__authority = self.__authority | |
target.__path = self.__path or Path("") | |
target.__query = Query(string) | |
target.__fragment = self.__fragment | |
return target | |
def with_fragment(self, string): | |
target = URI(None) | |
target.__scheme = self.__scheme | |
target.__authority = self.__authority | |
target.__path = self.__path or Path("") | |
target.__query = self.__query | |
target.__fragment = percent_decode(string) | |
return target |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment