107 changed files with 8925 additions and 8425 deletions
@ -0,0 +1,8 @@ |
|||
If you are planning to submit a pull request to requests with any changes in |
|||
this library do not go any further. These are independent libraries which we |
|||
vendor into requests. Any changes necessary to these libraries must be made in |
|||
them and submitted as separate pull requests to those libraries. |
|||
|
|||
urllib3 pull requests go here: https://github.com/shazow/urllib3 |
|||
|
|||
chardet pull requests go here: https://github.com/chardet/chardet |
@ -1,7 +0,0 @@ |
|||
''' |
|||
support ';python -m charade <file1> [file2] ...' package execution syntax (2.7+) |
|||
''' |
|||
|
|||
from charade import charade_cli |
|||
|
|||
charade_cli() |
@ -0,0 +1,46 @@ |
|||
#!/usr/bin/env python |
|||
""" |
|||
Script which takes one or more file paths and reports on their detected |
|||
encodings |
|||
|
|||
Example:: |
|||
|
|||
% chardetect somefile someotherfile |
|||
somefile: windows-1252 with confidence 0.5 |
|||
someotherfile: ascii with confidence 1.0 |
|||
|
|||
If no paths are provided, it takes its input from stdin. |
|||
|
|||
""" |
|||
from io import open |
|||
from sys import argv, stdin |
|||
|
|||
from chardet.universaldetector import UniversalDetector |
|||
|
|||
|
|||
def description_of(file, name='stdin'): |
|||
"""Return a string describing the probable encoding of a file.""" |
|||
u = UniversalDetector() |
|||
for line in file: |
|||
u.feed(line) |
|||
u.close() |
|||
result = u.result |
|||
if result['encoding']: |
|||
return '%s: %s with confidence %s' % (name, |
|||
result['encoding'], |
|||
result['confidence']) |
|||
else: |
|||
return '%s: no result' % name |
|||
|
|||
|
|||
def main(): |
|||
if len(argv) <= 1: |
|||
print(description_of(stdin)) |
|||
else: |
|||
for path in argv[1:]: |
|||
with open(path, 'rb') as f: |
|||
print(description_of(f, path)) |
|||
|
|||
|
|||
if __name__ == '__main__': |
|||
main() |
@ -1,643 +0,0 @@ |
|||
# urllib3/util.py |
|||
# Copyright 2008-2013 Andrey Petrov and contributors (see CONTRIBUTORS.txt) |
|||
# |
|||
# This module is part of urllib3 and is released under |
|||
# the MIT License: http://www.opensource.org/licenses/mit-license.php |
|||
|
|||
|
|||
from base64 import b64encode |
|||
from binascii import hexlify, unhexlify |
|||
from collections import namedtuple |
|||
from hashlib import md5, sha1 |
|||
from socket import error as SocketError, _GLOBAL_DEFAULT_TIMEOUT |
|||
import time |
|||
|
|||
try: |
|||
from select import poll, POLLIN |
|||
except ImportError: # `poll` doesn't exist on OSX and other platforms |
|||
poll = False |
|||
try: |
|||
from select import select |
|||
except ImportError: # `select` doesn't exist on AppEngine. |
|||
select = False |
|||
|
|||
try: # Test for SSL features |
|||
SSLContext = None |
|||
HAS_SNI = False |
|||
|
|||
import ssl |
|||
from ssl import wrap_socket, CERT_NONE, PROTOCOL_SSLv23 |
|||
from ssl import SSLContext # Modern SSL? |
|||
from ssl import HAS_SNI # Has SNI? |
|||
except ImportError: |
|||
pass |
|||
|
|||
from .packages import six |
|||
from .exceptions import LocationParseError, SSLError, TimeoutStateError |
|||
|
|||
|
|||
_Default = object() |
|||
# The default timeout to use for socket connections. This is the attribute used |
|||
# by httplib to define the default timeout |
|||
|
|||
|
|||
def current_time(): |
|||
""" |
|||
Retrieve the current time, this function is mocked out in unit testing. |
|||
""" |
|||
return time.time() |
|||
|
|||
|
|||
class Timeout(object): |
|||
""" |
|||
Utility object for storing timeout values. |
|||
|
|||
Example usage: |
|||
|
|||
.. code-block:: python |
|||
|
|||
timeout = urllib3.util.Timeout(connect=2.0, read=7.0) |
|||
pool = HTTPConnectionPool('www.google.com', 80, timeout=timeout) |
|||
pool.request(...) # Etc, etc |
|||
|
|||
:param connect: |
|||
The maximum amount of time to wait for a connection attempt to a server |
|||
to succeed. Omitting the parameter will default the connect timeout to |
|||
the system default, probably `the global default timeout in socket.py |
|||
<http://hg.python.org/cpython/file/603b4d593758/Lib/socket.py#l535>`_. |
|||
None will set an infinite timeout for connection attempts. |
|||
|
|||
:type connect: integer, float, or None |
|||
|
|||
:param read: |
|||
The maximum amount of time to wait between consecutive |
|||
read operations for a response from the server. Omitting |
|||
the parameter will default the read timeout to the system |
|||
default, probably `the global default timeout in socket.py |
|||
<http://hg.python.org/cpython/file/603b4d593758/Lib/socket.py#l535>`_. |
|||
None will set an infinite timeout. |
|||
|
|||
:type read: integer, float, or None |
|||
|
|||
:param total: |
|||
This combines the connect and read timeouts into one; the read timeout |
|||
will be set to the time leftover from the connect attempt. In the |
|||
event that both a connect timeout and a total are specified, or a read |
|||
timeout and a total are specified, the shorter timeout will be applied. |
|||
|
|||
Defaults to None. |
|||
|
|||
:type total: integer, float, or None |
|||
|
|||
.. note:: |
|||
|
|||
Many factors can affect the total amount of time for urllib3 to return |
|||
an HTTP response. Specifically, Python's DNS resolver does not obey the |
|||
timeout specified on the socket. Other factors that can affect total |
|||
request time include high CPU load, high swap, the program running at a |
|||
low priority level, or other behaviors. The observed running time for |
|||
urllib3 to return a response may be greater than the value passed to |
|||
`total`. |
|||
|
|||
In addition, the read and total timeouts only measure the time between |
|||
read operations on the socket connecting the client and the server, |
|||
not the total amount of time for the request to return a complete |
|||
response. For most requests, the timeout is raised because the server |
|||
has not sent the first byte in the specified time. This is not always |
|||
the case; if a server streams one byte every fifteen seconds, a timeout |
|||
of 20 seconds will not ever trigger, even though the request will |
|||
take several minutes to complete. |
|||
|
|||
If your goal is to cut off any request after a set amount of wall clock |
|||
time, consider having a second "watcher" thread to cut off a slow |
|||
request. |
|||
""" |
|||
|
|||
#: A sentinel object representing the default timeout value |
|||
DEFAULT_TIMEOUT = _GLOBAL_DEFAULT_TIMEOUT |
|||
|
|||
def __init__(self, total=None, connect=_Default, read=_Default): |
|||
self._connect = self._validate_timeout(connect, 'connect') |
|||
self._read = self._validate_timeout(read, 'read') |
|||
self.total = self._validate_timeout(total, 'total') |
|||
self._start_connect = None |
|||
|
|||
def __str__(self): |
|||
return '%s(connect=%r, read=%r, total=%r)' % ( |
|||
type(self).__name__, self._connect, self._read, self.total) |
|||
|
|||
|
|||
@classmethod |
|||
def _validate_timeout(cls, value, name): |
|||
""" Check that a timeout attribute is valid |
|||
|
|||
:param value: The timeout value to validate |
|||
:param name: The name of the timeout attribute to validate. This is used |
|||
for clear error messages |
|||
:return: the value |
|||
:raises ValueError: if the type is not an integer or a float, or if it |
|||
is a numeric value less than zero |
|||
""" |
|||
if value is _Default: |
|||
return cls.DEFAULT_TIMEOUT |
|||
|
|||
if value is None or value is cls.DEFAULT_TIMEOUT: |
|||
return value |
|||
|
|||
try: |
|||
float(value) |
|||
except (TypeError, ValueError): |
|||
raise ValueError("Timeout value %s was %s, but it must be an " |
|||
"int or float." % (name, value)) |
|||
|
|||
try: |
|||
if value < 0: |
|||
raise ValueError("Attempted to set %s timeout to %s, but the " |
|||
"timeout cannot be set to a value less " |
|||
"than 0." % (name, value)) |
|||
except TypeError: # Python 3 |
|||
raise ValueError("Timeout value %s was %s, but it must be an " |
|||
"int or float." % (name, value)) |
|||
|
|||
return value |
|||
|
|||
@classmethod |
|||
def from_float(cls, timeout): |
|||
""" Create a new Timeout from a legacy timeout value. |
|||
|
|||
The timeout value used by httplib.py sets the same timeout on the |
|||
connect(), and recv() socket requests. This creates a :class:`Timeout` |
|||
object that sets the individual timeouts to the ``timeout`` value passed |
|||
to this function. |
|||
|
|||
:param timeout: The legacy timeout value |
|||
:type timeout: integer, float, sentinel default object, or None |
|||
:return: a Timeout object |
|||
:rtype: :class:`Timeout` |
|||
""" |
|||
return Timeout(read=timeout, connect=timeout) |
|||
|
|||
def clone(self): |
|||
""" Create a copy of the timeout object |
|||
|
|||
Timeout properties are stored per-pool but each request needs a fresh |
|||
Timeout object to ensure each one has its own start/stop configured. |
|||
|
|||
:return: a copy of the timeout object |
|||
:rtype: :class:`Timeout` |
|||
""" |
|||
# We can't use copy.deepcopy because that will also create a new object |
|||
# for _GLOBAL_DEFAULT_TIMEOUT, which socket.py uses as a sentinel to |
|||
# detect the user default. |
|||
return Timeout(connect=self._connect, read=self._read, |
|||
total=self.total) |
|||
|
|||
def start_connect(self): |
|||
""" Start the timeout clock, used during a connect() attempt |
|||
|
|||
:raises urllib3.exceptions.TimeoutStateError: if you attempt |
|||
to start a timer that has been started already. |
|||
""" |
|||
if self._start_connect is not None: |
|||
raise TimeoutStateError("Timeout timer has already been started.") |
|||
self._start_connect = current_time() |
|||
return self._start_connect |
|||
|
|||
def get_connect_duration(self): |
|||
""" Gets the time elapsed since the call to :meth:`start_connect`. |
|||
|
|||
:return: the elapsed time |
|||
:rtype: float |
|||
:raises urllib3.exceptions.TimeoutStateError: if you attempt |
|||
to get duration for a timer that hasn't been started. |
|||
""" |
|||
if self._start_connect is None: |
|||
raise TimeoutStateError("Can't get connect duration for timer " |
|||
"that has not started.") |
|||
return current_time() - self._start_connect |
|||
|
|||
@property |
|||
def connect_timeout(self): |
|||
""" Get the value to use when setting a connection timeout. |
|||
|
|||
This will be a positive float or integer, the value None |
|||
(never timeout), or the default system timeout. |
|||
|
|||
:return: the connect timeout |
|||
:rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None |
|||
""" |
|||
if self.total is None: |
|||
return self._connect |
|||
|
|||
if self._connect is None or self._connect is self.DEFAULT_TIMEOUT: |
|||
return self.total |
|||
|
|||
return min(self._connect, self.total) |
|||
|
|||
@property |
|||
def read_timeout(self): |
|||
""" Get the value for the read timeout. |
|||
|
|||
This assumes some time has elapsed in the connection timeout and |
|||
computes the read timeout appropriately. |
|||
|
|||
If self.total is set, the read timeout is dependent on the amount of |
|||
time taken by the connect timeout. If the connection time has not been |
|||
established, a :exc:`~urllib3.exceptions.TimeoutStateError` will be |
|||
raised. |
|||
|
|||
:return: the value to use for the read timeout |
|||
:rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None |
|||
:raises urllib3.exceptions.TimeoutStateError: If :meth:`start_connect` |
|||
has not yet been called on this object. |
|||
""" |
|||
if (self.total is not None and |
|||
self.total is not self.DEFAULT_TIMEOUT and |
|||
self._read is not None and |
|||
self._read is not self.DEFAULT_TIMEOUT): |
|||
# in case the connect timeout has not yet been established. |
|||
if self._start_connect is None: |
|||
return self._read |
|||
return max(0, min(self.total - self.get_connect_duration(), |
|||
self._read)) |
|||
elif self.total is not None and self.total is not self.DEFAULT_TIMEOUT: |
|||
return max(0, self.total - self.get_connect_duration()) |
|||
else: |
|||
return self._read |
|||
|
|||
|
|||
class Url(namedtuple('Url', ['scheme', 'auth', 'host', 'port', 'path', 'query', 'fragment'])): |
|||
""" |
|||
Datastructure for representing an HTTP URL. Used as a return value for |
|||
:func:`parse_url`. |
|||
""" |
|||
slots = () |
|||
|
|||
def __new__(cls, scheme=None, auth=None, host=None, port=None, path=None, query=None, fragment=None): |
|||
return super(Url, cls).__new__(cls, scheme, auth, host, port, path, query, fragment) |
|||
|
|||
@property |
|||
def hostname(self): |
|||
"""For backwards-compatibility with urlparse. We're nice like that.""" |
|||
return self.host |
|||
|
|||
@property |
|||
def request_uri(self): |
|||
"""Absolute path including the query string.""" |
|||
uri = self.path or '/' |
|||
|
|||
if self.query is not None: |
|||
uri += '?' + self.query |
|||
|
|||
return uri |
|||
|
|||
@property |
|||
def netloc(self): |
|||
"""Network location including host and port""" |
|||
if self.port: |
|||
return '%s:%d' % (self.host, self.port) |
|||
return self.host |
|||
|
|||
|
|||
def split_first(s, delims): |
|||
""" |
|||
Given a string and an iterable of delimiters, split on the first found |
|||
delimiter. Return two split parts and the matched delimiter. |
|||
|
|||
If not found, then the first part is the full input string. |
|||
|
|||
Example: :: |
|||
|
|||
>>> split_first('foo/bar?baz', '?/=') |
|||
('foo', 'bar?baz', '/') |
|||
>>> split_first('foo/bar?baz', '123') |
|||
('foo/bar?baz', '', None) |
|||
|
|||
Scales linearly with number of delims. Not ideal for large number of delims. |
|||
""" |
|||
min_idx = None |
|||
min_delim = None |
|||
for d in delims: |
|||
idx = s.find(d) |
|||
if idx < 0: |
|||
continue |
|||
|
|||
if min_idx is None or idx < min_idx: |
|||
min_idx = idx |
|||
min_delim = d |
|||
|
|||
if min_idx is None or min_idx < 0: |
|||
return s, '', None |
|||
|
|||
return s[:min_idx], s[min_idx+1:], min_delim |
|||
|
|||
|
|||
def parse_url(url): |
|||
""" |
|||
Given a url, return a parsed :class:`.Url` namedtuple. Best-effort is |
|||
performed to parse incomplete urls. Fields not provided will be None. |
|||
|
|||
Partly backwards-compatible with :mod:`urlparse`. |
|||
|
|||
Example: :: |
|||
|
|||
>>> parse_url('http://google.com/mail/') |
|||
Url(scheme='http', host='google.com', port=None, path='/', ...) |
|||
>>> parse_url('google.com:80') |
|||
Url(scheme=None, host='google.com', port=80, path=None, ...) |
|||
>>> parse_url('/foo?bar') |
|||
Url(scheme=None, host=None, port=None, path='/foo', query='bar', ...) |
|||
""" |
|||
|
|||
# While this code has overlap with stdlib's urlparse, it is much |
|||
# simplified for our needs and less annoying. |
|||
# Additionally, this implementations does silly things to be optimal |
|||
# on CPython. |
|||
|
|||
scheme = None |
|||
auth = None |
|||
host = None |
|||
port = None |
|||
path = None |
|||
fragment = None |
|||
query = None |
|||
|
|||
# Scheme |
|||
if '://' in url: |
|||
scheme, url = url.split('://', 1) |
|||
|
|||
# Find the earliest Authority Terminator |
|||
# (http://tools.ietf.org/html/rfc3986#section-3.2) |
|||
url, path_, delim = split_first(url, ['/', '?', '#']) |
|||
|
|||
if delim: |
|||
# Reassemble the path |
|||
path = delim + path_ |
|||
|
|||
# Auth |
|||
if '@' in url: |
|||
# Last '@' denotes end of auth part |
|||
auth, url = url.rsplit('@', 1) |
|||
|
|||
# IPv6 |
|||
if url and url[0] == '[': |
|||
host, url = url.split(']', 1) |
|||
host += ']' |
|||
|
|||
# Port |
|||
if ':' in url: |
|||
_host, port = url.split(':', 1) |
|||
|
|||
if not host: |
|||
host = _host |
|||
|
|||
if port: |
|||
# If given, ports must be integers. |
|||
if not port.isdigit(): |
|||
raise LocationParseError("Failed to parse: %s" % url) |
|||
port = int(port) |
|||
else: |
|||
# Blank ports are cool, too. (rfc3986#section-3.2.3) |
|||
port = None |
|||
|
|||
elif not host and url: |
|||
host = url |
|||
|
|||
if not path: |
|||
return Url(scheme, auth, host, port, path, query, fragment) |
|||
|
|||
# Fragment |
|||
if '#' in path: |
|||
path, fragment = path.split('#', 1) |
|||
|
|||
# Query |
|||
if '?' in path: |
|||
path, query = path.split('?', 1) |
|||
|
|||
return Url(scheme, auth, host, port, path, query, fragment) |
|||
|
|||
|
|||
def get_host(url): |
|||
""" |
|||
Deprecated. Use :func:`.parse_url` instead. |
|||
""" |
|||
p = parse_url(url) |
|||
return p.scheme or 'http', p.hostname, p.port |
|||
|
|||
|
|||
def make_headers(keep_alive=None, accept_encoding=None, user_agent=None, |
|||
basic_auth=None, proxy_basic_auth=None): |
|||
""" |
|||
Shortcuts for generating request headers. |
|||
|
|||
:param keep_alive: |
|||
If ``True``, adds 'connection: keep-alive' header. |
|||
|
|||
:param accept_encoding: |
|||
Can be a boolean, list, or string. |
|||
``True`` translates to 'gzip,deflate'. |
|||
List will get joined by comma. |
|||
String will be used as provided. |
|||
|
|||
:param user_agent: |
|||
String representing the user-agent you want, such as |
|||
"python-urllib3/0.6" |
|||
|
|||
:param basic_auth: |
|||
Colon-separated username:password string for 'authorization: basic ...' |
|||
auth header. |
|||
|
|||
:param proxy_basic_auth: |
|||
Colon-separated username:password string for 'proxy-authorization: basic ...' |
|||
auth header. |
|||
|
|||
Example: :: |
|||
|
|||
>>> make_headers(keep_alive=True, user_agent="Batman/1.0") |
|||
{'connection': 'keep-alive', 'user-agent': 'Batman/1.0'} |
|||
>>> make_headers(accept_encoding=True) |
|||
{'accept-encoding': 'gzip,deflate'} |
|||
""" |
|||
headers = {} |
|||
if accept_encoding: |
|||
if isinstance(accept_encoding, str): |
|||
pass |
|||
elif isinstance(accept_encoding, list): |
|||
accept_encoding = ','.join(accept_encoding) |
|||
else: |
|||
accept_encoding = 'gzip,deflate' |
|||
headers['accept-encoding'] = accept_encoding |
|||
|
|||
if user_agent: |
|||
headers['user-agent'] = user_agent |
|||
|
|||
if keep_alive: |
|||
headers['connection'] = 'keep-alive' |
|||
|
|||
if basic_auth: |
|||
headers['authorization'] = 'Basic ' + \ |
|||
b64encode(six.b(basic_auth)).decode('utf-8') |
|||
|
|||
if proxy_basic_auth: |
|||
headers['proxy-authorization'] = 'Basic ' + \ |
|||
b64encode(six.b(proxy_basic_auth)).decode('utf-8') |
|||
|
|||
return headers |
|||
|
|||
|
|||
def is_connection_dropped(conn): # Platform-specific |
|||
""" |
|||
Returns True if the connection is dropped and should be closed. |
|||
|
|||
:param conn: |
|||
:class:`httplib.HTTPConnection` object. |
|||
|
|||
Note: For platforms like AppEngine, this will always return ``False`` to |
|||
let the platform handle connection recycling transparently for us. |
|||
""" |
|||
sock = getattr(conn, 'sock', False) |
|||
if not sock: # Platform-specific: AppEngine |
|||
return False |
|||
|
|||
if not poll: |
|||
if not select: # Platform-specific: AppEngine |
|||
return False |
|||
|
|||
try: |
|||
return select([sock], [], [], 0.0)[0] |
|||
except SocketError: |
|||
return True |
|||
|
|||
# This version is better on platforms that support it. |
|||
p = poll() |
|||
p.register(sock, POLLIN) |
|||
for (fno, ev) in p.poll(0.0): |
|||
if fno == sock.fileno(): |
|||
# Either data is buffered (bad), or the connection is dropped. |
|||
return True |
|||
|
|||
|
|||
def resolve_cert_reqs(candidate): |
|||
""" |
|||
Resolves the argument to a numeric constant, which can be passed to |
|||
the wrap_socket function/method from the ssl module. |
|||
Defaults to :data:`ssl.CERT_NONE`. |
|||
If given a string it is assumed to be the name of the constant in the |
|||
:mod:`ssl` module or its abbrevation. |
|||
(So you can specify `REQUIRED` instead of `CERT_REQUIRED`. |
|||
If it's neither `None` nor a string we assume it is already the numeric |
|||
constant which can directly be passed to wrap_socket. |
|||
""" |
|||
if candidate is None: |
|||
return CERT_NONE |
|||
|
|||
if isinstance(candidate, str): |
|||
res = getattr(ssl, candidate, None) |
|||
if res is None: |
|||
res = getattr(ssl, 'CERT_' + candidate) |
|||
return res |
|||
|
|||
return candidate |
|||
|
|||
|
|||
def resolve_ssl_version(candidate): |
|||
""" |
|||
like resolve_cert_reqs |
|||
""" |
|||
if candidate is None: |
|||
return PROTOCOL_SSLv23 |
|||
|
|||
if isinstance(candidate, str): |
|||
res = getattr(ssl, candidate, None) |
|||
if res is None: |
|||
res = getattr(ssl, 'PROTOCOL_' + candidate) |
|||
return res |
|||
|
|||
return candidate |
|||
|
|||
|
|||
def assert_fingerprint(cert, fingerprint): |
|||
""" |
|||
Checks if given fingerprint matches the supplied certificate. |
|||
|
|||
:param cert: |
|||
Certificate as bytes object. |
|||
:param fingerprint: |
|||
Fingerprint as string of hexdigits, can be interspersed by colons. |
|||
""" |
|||
|
|||
# Maps the length of a digest to a possible hash function producing |
|||
# this digest. |
|||
hashfunc_map = { |
|||
16: md5, |
|||
20: sha1 |
|||
} |
|||
|
|||
fingerprint = fingerprint.replace(':', '').lower() |
|||
|
|||
digest_length, rest = divmod(len(fingerprint), 2) |
|||
|
|||
if rest or digest_length not in hashfunc_map: |
|||
raise SSLError('Fingerprint is of invalid length.') |
|||
|
|||
# We need encode() here for py32; works on py2 and p33. |
|||
fingerprint_bytes = unhexlify(fingerprint.encode()) |
|||
|
|||
hashfunc = hashfunc_map[digest_length] |
|||
|
|||
cert_digest = hashfunc(cert).digest() |
|||
|
|||
if not cert_digest == fingerprint_bytes: |
|||
raise SSLError('Fingerprints did not match. Expected "{0}", got "{1}".' |
|||
.format(hexlify(fingerprint_bytes), |
|||
hexlify(cert_digest))) |
|||
|
|||
def is_fp_closed(obj): |
|||
""" |
|||
Checks whether a given file-like object is closed. |
|||
|
|||
:param obj: |
|||
The file-like object to check. |
|||
""" |
|||
if hasattr(obj, 'fp'): |
|||
# Object is a container for another file-like object that gets released |
|||
# on exhaustion (e.g. HTTPResponse) |
|||
return obj.fp is None |
|||
|
|||
return obj.closed |
|||
|
|||
|
|||
if SSLContext is not None: # Python 3.2+ |
|||
def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None, |
|||
ca_certs=None, server_hostname=None, |
|||
ssl_version=None): |
|||
""" |
|||
All arguments except `server_hostname` have the same meaning as for |
|||
:func:`ssl.wrap_socket` |
|||
|
|||
:param server_hostname: |
|||
Hostname of the expected certificate |
|||
""" |
|||
context = SSLContext(ssl_version) |
|||
context.verify_mode = cert_reqs |
|||
if ca_certs: |
|||
try: |
|||
context.load_verify_locations(ca_certs) |
|||
# Py32 raises IOError |
|||
# Py33 raises FileNotFoundError |
|||
except Exception as e: # Reraise as SSLError |
|||
raise SSLError(e) |
|||
if certfile: |
|||
# FIXME: This block needs a test. |
|||
context.load_cert_chain(certfile, keyfile) |
|||
if HAS_SNI: # Platform-specific: OpenSSL with enabled SNI |
|||
return context.wrap_socket(sock, server_hostname=server_hostname) |
|||
return context.wrap_socket(sock) |
|||
|
|||
else: # Python 3.1 and earlier |
|||
def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None, |
|||
ca_certs=None, server_hostname=None, |
|||
ssl_version=None): |
|||
return wrap_socket(sock, keyfile=keyfile, certfile=certfile, |
|||
ca_certs=ca_certs, cert_reqs=cert_reqs, |
|||
ssl_version=ssl_version) |
@ -0,0 +1,27 @@ |
|||
# urllib3/util/__init__.py |
|||
# Copyright 2008-2014 Andrey Petrov and contributors (see CONTRIBUTORS.txt) |
|||
# |
|||
# This module is part of urllib3 and is released under |
|||
# the MIT License: http://www.opensource.org/licenses/mit-license.php |
|||
|
|||
from .connection import is_connection_dropped |
|||
from .request import make_headers |
|||
from .response import is_fp_closed |
|||
from .ssl_ import ( |
|||
SSLContext, |
|||
HAS_SNI, |
|||
assert_fingerprint, |
|||
resolve_cert_reqs, |
|||
resolve_ssl_version, |
|||
ssl_wrap_socket, |
|||
) |
|||
from .timeout import ( |
|||
current_time, |
|||
Timeout, |
|||
) |
|||
from .url import ( |
|||
get_host, |
|||
parse_url, |
|||
split_first, |
|||
Url, |
|||
) |
@ -0,0 +1,45 @@ |
|||
from socket import error as SocketError |
|||
try: |
|||
from select import poll, POLLIN |
|||
except ImportError: # `poll` doesn't exist on OSX and other platforms |
|||
poll = False |
|||
try: |
|||
from select import select |
|||
except ImportError: # `select` doesn't exist on AppEngine. |
|||
select = False |
|||
|
|||
def is_connection_dropped(conn): # Platform-specific |
|||
""" |
|||
Returns True if the connection is dropped and should be closed. |
|||
|
|||
:param conn: |
|||
:class:`httplib.HTTPConnection` object. |
|||
|
|||
Note: For platforms like AppEngine, this will always return ``False`` to |
|||
let the platform handle connection recycling transparently for us. |
|||
""" |
|||
sock = getattr(conn, 'sock', False) |
|||
if sock is False: # Platform-specific: AppEngine |
|||
return False |
|||
if sock is None: # Connection already closed (such as by httplib). |
|||
return False |
|||
|
|||
if not poll: |
|||
if not select: # Platform-specific: AppEngine |
|||
return False |
|||
|
|||
try: |
|||
return select([sock], [], [], 0.0)[0] |
|||
except SocketError: |
|||
return True |
|||
|
|||
# This version is better on platforms that support it. |
|||
p = poll() |
|||
p.register(sock, POLLIN) |
|||
for (fno, ev) in p.poll(0.0): |
|||
if fno == sock.fileno(): |
|||
# Either data is buffered (bad), or the connection is dropped. |
|||
return True |
|||
|
|||
|
|||
|
@ -0,0 +1,68 @@ |
|||
from base64 import b64encode |
|||
|
|||
from ..packages import six |
|||
|
|||
|
|||
ACCEPT_ENCODING = 'gzip,deflate' |
|||
|
|||
|
|||
def make_headers(keep_alive=None, accept_encoding=None, user_agent=None, |
|||
basic_auth=None, proxy_basic_auth=None): |
|||
""" |
|||
Shortcuts for generating request headers. |
|||
|
|||
:param keep_alive: |
|||
If ``True``, adds 'connection: keep-alive' header. |
|||
|
|||
:param accept_encoding: |
|||
Can be a boolean, list, or string. |
|||
``True`` translates to 'gzip,deflate'. |
|||
List will get joined by comma. |
|||
String will be used as provided. |
|||
|
|||
:param user_agent: |
|||
String representing the user-agent you want, such as |
|||
"python-urllib3/0.6" |
|||
|
|||
:param basic_auth: |
|||
Colon-separated username:password string for 'authorization: basic ...' |
|||
auth header. |
|||
|
|||
:param proxy_basic_auth: |
|||
Colon-separated username:password string for 'proxy-authorization: basic ...' |
|||
auth header. |
|||
|
|||
Example: :: |
|||
|
|||
>>> make_headers(keep_alive=True, user_agent="Batman/1.0") |
|||
{'connection': 'keep-alive', 'user-agent': 'Batman/1.0'} |
|||
>>> make_headers(accept_encoding=True) |
|||
{'accept-encoding': 'gzip,deflate'} |
|||
""" |
|||
headers = {} |
|||
if accept_encoding: |
|||
if isinstance(accept_encoding, str): |
|||
pass |
|||
elif isinstance(accept_encoding, list): |
|||
accept_encoding = ','.join(accept_encoding) |
|||
else: |
|||
accept_encoding = ACCEPT_ENCODING |
|||
headers['accept-encoding'] = accept_encoding |
|||
|
|||
if user_agent: |
|||
headers['user-agent'] = user_agent |
|||
|
|||
if keep_alive: |
|||
headers['connection'] = 'keep-alive' |
|||
|
|||
if basic_auth: |
|||
headers['authorization'] = 'Basic ' + \ |
|||
b64encode(six.b(basic_auth)).decode('utf-8') |
|||
|
|||
if proxy_basic_auth: |
|||
headers['proxy-authorization'] = 'Basic ' + \ |
|||
b64encode(six.b(proxy_basic_auth)).decode('utf-8') |
|||
|
|||
return headers |
|||
|
|||
|
@ -0,0 +1,13 @@ |
|||
def is_fp_closed(obj): |
|||
""" |
|||
Checks whether a given file-like object is closed. |
|||
|
|||
:param obj: |
|||
The file-like object to check. |
|||
""" |
|||
if hasattr(obj, 'fp'): |
|||
# Object is a container for another file-like object that gets released |
|||
# on exhaustion (e.g. HTTPResponse) |
|||
return obj.fp is None |
|||
|
|||
return obj.closed |
@ -0,0 +1,133 @@ |
|||
from binascii import hexlify, unhexlify |
|||
from hashlib import md5, sha1 |
|||
|
|||
from ..exceptions import SSLError |
|||
|
|||
|
|||
try: # Test for SSL features |
|||
SSLContext = None |
|||
HAS_SNI = False |
|||
|
|||
import ssl |
|||
from ssl import wrap_socket, CERT_NONE, PROTOCOL_SSLv23 |
|||
from ssl import SSLContext # Modern SSL? |
|||
from ssl import HAS_SNI # Has SNI? |
|||
except ImportError: |
|||
pass |
|||
|
|||
|
|||
def assert_fingerprint(cert, fingerprint): |
|||
""" |
|||
Checks if given fingerprint matches the supplied certificate. |
|||
|
|||
:param cert: |
|||
Certificate as bytes object. |
|||
:param fingerprint: |
|||
Fingerprint as string of hexdigits, can be interspersed by colons. |
|||
""" |
|||
|
|||
# Maps the length of a digest to a possible hash function producing |
|||
# this digest. |
|||
hashfunc_map = { |
|||
16: md5, |
|||
20: sha1 |
|||
} |
|||
|
|||
fingerprint = fingerprint.replace(':', '').lower() |
|||
|
|||
digest_length, rest = divmod(len(fingerprint), 2) |
|||
|
|||
if rest or digest_length not in hashfunc_map: |
|||
raise SSLError('Fingerprint is of invalid length.') |
|||
|
|||
# We need encode() here for py32; works on py2 and p33. |
|||
fingerprint_bytes = unhexlify(fingerprint.encode()) |
|||
|
|||
hashfunc = hashfunc_map[digest_length] |
|||
|
|||
cert_digest = hashfunc(cert).digest() |
|||
|
|||
if not cert_digest == fingerprint_bytes: |
|||
raise SSLError('Fingerprints did not match. Expected "{0}", got "{1}".' |
|||
.format(hexlify(fingerprint_bytes), |
|||
hexlify(cert_digest))) |
|||
|
|||
|
|||
def resolve_cert_reqs(candidate): |
|||
""" |
|||
Resolves the argument to a numeric constant, which can be passed to |
|||
the wrap_socket function/method from the ssl module. |
|||
Defaults to :data:`ssl.CERT_NONE`. |
|||
If given a string it is assumed to be the name of the constant in the |
|||
:mod:`ssl` module or its abbrevation. |
|||
(So you can specify `REQUIRED` instead of `CERT_REQUIRED`. |
|||
If it's neither `None` nor a string we assume it is already the numeric |
|||
constant which can directly be passed to wrap_socket. |
|||
""" |
|||
if candidate is None: |
|||
return CERT_NONE |
|||
|
|||
if isinstance(candidate, str): |
|||
res = getattr(ssl, candidate, None) |
|||
if res is None: |
|||
res = getattr(ssl, 'CERT_' + candidate) |
|||
return res |
|||
|
|||
return candidate |
|||
|
|||
|
|||
def resolve_ssl_version(candidate): |
|||
""" |
|||
like resolve_cert_reqs |
|||
""" |
|||
if candidate is None: |
|||
return PROTOCOL_SSLv23 |
|||
|
|||
if isinstance(candidate, str): |
|||
res = getattr(ssl, candidate, None) |
|||
if res is None: |
|||
res = getattr(ssl, 'PROTOCOL_' + candidate) |
|||
return res |
|||
|
|||
return candidate |
|||
|
|||
|
|||
if SSLContext is not None: # Python 3.2+ |
|||
def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None, |
|||
ca_certs=None, server_hostname=None, |
|||
ssl_version=None): |
|||
""" |
|||
All arguments except `server_hostname` have the same meaning as for |
|||
:func:`ssl.wrap_socket` |
|||
|
|||
:param server_hostname: |
|||
Hostname of the expected certificate |
|||
""" |
|||
context = SSLContext(ssl_version) |
|||
context.verify_mode = cert_reqs |
|||
|
|||
# Disable TLS compression to migitate CRIME attack (issue #309) |
|||
OP_NO_COMPRESSION = 0x20000 |
|||
context.options |= OP_NO_COMPRESSION |
|||
|
|||
if ca_certs: |
|||
try: |
|||
context.load_verify_locations(ca_certs) |
|||
# Py32 raises IOError |
|||
# Py33 raises FileNotFoundError |
|||
except Exception as e: # Reraise as SSLError |
|||
raise SSLError(e) |
|||
if certfile: |
|||
# FIXME: This block needs a test. |
|||
context.load_cert_chain(certfile, keyfile) |
|||
if HAS_SNI: # Platform-specific: OpenSSL with enabled SNI |
|||
return context.wrap_socket(sock, server_hostname=server_hostname) |
|||
return context.wrap_socket(sock) |
|||
|
|||
else: # Python 3.1 and earlier |
|||
def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None, |
|||
ca_certs=None, server_hostname=None, |
|||
ssl_version=None): |
|||
return wrap_socket(sock, keyfile=keyfile, certfile=certfile, |
|||
ca_certs=ca_certs, cert_reqs=cert_reqs, |
|||
ssl_version=ssl_version) |
@ -0,0 +1,234 @@ |
|||
from socket import _GLOBAL_DEFAULT_TIMEOUT |
|||
import time |
|||
|
|||
from ..exceptions import TimeoutStateError |
|||
|
|||
|
|||
def current_time(): |
|||
""" |
|||
Retrieve the current time, this function is mocked out in unit testing. |
|||
""" |
|||
return time.time() |
|||
|
|||
|
|||
_Default = object() |
|||
# The default timeout to use for socket connections. This is the attribute used |
|||
# by httplib to define the default timeout |
|||
|
|||
|
|||
class Timeout(object): |
|||
""" |
|||
Utility object for storing timeout values. |
|||
|
|||
Example usage: |
|||
|
|||
.. code-block:: python |
|||
|
|||
timeout = urllib3.util.Timeout(connect=2.0, read=7.0) |
|||
pool = HTTPConnectionPool('www.google.com', 80, timeout=timeout) |
|||
pool.request(...) # Etc, etc |
|||
|
|||
:param connect: |
|||
The maximum amount of time to wait for a connection attempt to a server |
|||
to succeed. Omitting the parameter will default the connect timeout to |
|||
the system default, probably `the global default timeout in socket.py |
|||
<http://hg.python.org/cpython/file/603b4d593758/Lib/socket.py#l535>`_. |
|||
None will set an infinite timeout for connection attempts. |
|||
|
|||
:type connect: integer, float, or None |
|||
|
|||
:param read: |
|||
The maximum amount of time to wait between consecutive |
|||
read operations for a response from the server. Omitting |
|||
the parameter will default the read timeout to the system |
|||
default, probably `the global default timeout in socket.py |
|||
<http://hg.python.org/cpython/file/603b4d593758/Lib/socket.py#l535>`_. |
|||
None will set an infinite timeout. |
|||
|
|||
:type read: integer, float, or None |
|||
|
|||
:param total: |
|||
This combines the connect and read timeouts into one; the read timeout |
|||
will be set to the time leftover from the connect attempt. In the |
|||
event that both a connect timeout and a total are specified, or a read |
|||
timeout and a total are specified, the shorter timeout will be applied. |
|||
|
|||
Defaults to None. |
|||
|
|||
:type total: integer, float, or None |
|||
|
|||
.. note:: |
|||
|
|||
Many factors can affect the total amount of time for urllib3 to return |
|||
an HTTP response. Specifically, Python's DNS resolver does not obey the |
|||
timeout specified on the socket. Other factors that can affect total |
|||
request time include high CPU load, high swap, the program running at a |
|||
low priority level, or other behaviors. The observed running time for |
|||
urllib3 to return a response may be greater than the value passed to |
|||
`total`. |
|||
|
|||
In addition, the read and total timeouts only measure the time between |
|||
read operations on the socket connecting the client and the server, |
|||
not the total amount of time for the request to return a complete |
|||
response. For most requests, the timeout is raised because the server |
|||
has not sent the first byte in the specified time. This is not always |
|||
the case; if a server streams one byte every fifteen seconds, a timeout |
|||
of 20 seconds will not ever trigger, even though the request will |
|||
take several minutes to complete. |
|||
|
|||
If your goal is to cut off any request after a set amount of wall clock |
|||
time, consider having a second "watcher" thread to cut off a slow |
|||
request. |
|||
""" |
|||
|
|||
#: A sentinel object representing the default timeout value |
|||
DEFAULT_TIMEOUT = _GLOBAL_DEFAULT_TIMEOUT |
|||
|
|||
def __init__(self, total=None, connect=_Default, read=_Default): |
|||
self._connect = self._validate_timeout(connect, 'connect') |
|||
self._read = self._validate_timeout(read, 'read') |
|||
self.total = self._validate_timeout(total, 'total') |
|||
self._start_connect = None |
|||
|
|||
def __str__(self): |
|||
return '%s(connect=%r, read=%r, total=%r)' % ( |
|||
type(self).__name__, self._connect, self._read, self.total) |
|||
|
|||
|
|||
@classmethod |
|||
def _validate_timeout(cls, value, name): |
|||
""" Check that a timeout attribute is valid |
|||
|
|||
:param value: The timeout value to validate |
|||
:param name: The name of the timeout attribute to validate. This is used |
|||
for clear error messages |
|||
:return: the value |
|||
:raises ValueError: if the type is not an integer or a float, or if it |
|||
is a numeric value less than zero |
|||
""" |
|||
if value is _Default: |
|||
return cls.DEFAULT_TIMEOUT |
|||
|
|||
if value is None or value is cls.DEFAULT_TIMEOUT: |
|||
return value |
|||
|
|||
try: |
|||
float(value) |
|||
except (TypeError, ValueError): |
|||
raise ValueError("Timeout value %s was %s, but it must be an " |
|||
"int or float." % (name, value)) |
|||
|
|||
try: |
|||
if value < 0: |
|||
raise ValueError("Attempted to set %s timeout to %s, but the " |
|||
"timeout cannot be set to a value less " |
|||
"than 0." % (name, value)) |
|||
except TypeError: # Python 3 |
|||
raise ValueError("Timeout value %s was %s, but it must be an " |
|||
"int or float." % (name, value)) |
|||
|
|||
return value |
|||
|
|||
@classmethod |
|||
def from_float(cls, timeout): |
|||
""" Create a new Timeout from a legacy timeout value. |
|||
|
|||
The timeout value used by httplib.py sets the same timeout on the |
|||
connect(), and recv() socket requests. This creates a :class:`Timeout` |
|||
object that sets the individual timeouts to the ``timeout`` value passed |
|||
to this function. |
|||
|
|||
:param timeout: The legacy timeout value |
|||
:type timeout: integer, float, sentinel default object, or None |
|||
:return: a Timeout object |
|||
:rtype: :class:`Timeout` |
|||
""" |
|||
return Timeout(read=timeout, connect=timeout) |
|||
|
|||
def clone(self): |
|||
""" Create a copy of the timeout object |
|||
|
|||
Timeout properties are stored per-pool but each request needs a fresh |
|||
Timeout object to ensure each one has its own start/stop configured. |
|||
|
|||
:return: a copy of the timeout object |
|||
:rtype: :class:`Timeout` |
|||
""" |
|||
# We can't use copy.deepcopy because that will also create a new object |
|||
# for _GLOBAL_DEFAULT_TIMEOUT, which socket.py uses as a sentinel to |
|||
# detect the user default. |
|||
return Timeout(connect=self._connect, read=self._read, |
|||
total=self.total) |
|||
|
|||
def start_connect(self): |
|||
""" Start the timeout clock, used during a connect() attempt |
|||
|
|||
:raises urllib3.exceptions.TimeoutStateError: if you attempt |
|||
to start a timer that has been started already. |
|||
""" |
|||
if self._start_connect is not None: |
|||
raise TimeoutStateError("Timeout timer has already been started.") |
|||
self._start_connect = current_time() |
|||
return self._start_connect |
|||
|
|||
def get_connect_duration(self): |
|||
""" Gets the time elapsed since the call to :meth:`start_connect`. |
|||
|
|||
:return: the elapsed time |
|||
:rtype: float |
|||
:raises urllib3.exceptions.TimeoutStateError: if you attempt |
|||
to get duration for a timer that hasn't been started. |
|||
""" |
|||
if self._start_connect is None: |
|||
raise TimeoutStateError("Can't get connect duration for timer " |
|||
"that has not started.") |
|||
return current_time() - self._start_connect |
|||
|
|||
@property |
|||
def connect_timeout(self): |
|||
""" Get the value to use when setting a connection timeout. |
|||
|
|||
This will be a positive float or integer, the value None |
|||
(never timeout), or the default system timeout. |
|||
|
|||
:return: the connect timeout |
|||
:rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None |
|||
""" |
|||
if self.total is None: |
|||
return self._connect |
|||
|
|||
if self._connect is None or self._connect is self.DEFAULT_TIMEOUT: |
|||
return self.total |
|||
|
|||
return min(self._connect, self.total) |
|||
|
|||
@property |
|||
def read_timeout(self): |
|||
""" Get the value for the read timeout. |
|||
|
|||
This assumes some time has elapsed in the connection timeout and |
|||
computes the read timeout appropriately. |
|||
|
|||
If self.total is set, the read timeout is dependent on the amount of |
|||
time taken by the connect timeout. If the connection time has not been |
|||
established, a :exc:`~urllib3.exceptions.TimeoutStateError` will be |
|||
raised. |
|||
|
|||
:return: the value to use for the read timeout |
|||
:rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None |
|||
:raises urllib3.exceptions.TimeoutStateError: If :meth:`start_connect` |
|||
has not yet been called on this object. |
|||
""" |
|||
if (self.total is not None and |
|||
self.total is not self.DEFAULT_TIMEOUT and |
|||
self._read is not None and |
|||
self._read is not self.DEFAULT_TIMEOUT): |
|||
# in case the connect timeout has not yet been established. |
|||
if self._start_connect is None: |
|||
return self._read |
|||
return max(0, min(self.total - self.get_connect_duration(), |
|||
self._read)) |
|||
elif self.total is not None and self.total is not self.DEFAULT_TIMEOUT: |
|||
return max(0, self.total - self.get_connect_duration()) |
|||
else: |
|||
return self._read |
@ -0,0 +1,162 @@ |
|||
from collections import namedtuple |
|||
|
|||
from ..exceptions import LocationParseError |
|||
|
|||
|
|||
class Url(namedtuple('Url', ['scheme', 'auth', 'host', 'port', 'path', 'query', 'fragment'])): |
|||
""" |
|||
Datastructure for representing an HTTP URL. Used as a return value for |
|||
:func:`parse_url`. |
|||
""" |
|||
slots = () |
|||
|
|||
def __new__(cls, scheme=None, auth=None, host=None, port=None, path=None, query=None, fragment=None): |
|||
return super(Url, cls).__new__(cls, scheme, auth, host, port, path, query, fragment) |
|||
|
|||
@property |
|||
def hostname(self): |
|||
"""For backwards-compatibility with urlparse. We're nice like that.""" |
|||
return self.host |
|||
|
|||
@property |
|||
def request_uri(self): |
|||
"""Absolute path including the query string.""" |
|||
uri = self.path or '/' |
|||
|
|||
if self.query is not None: |
|||
uri += '?' + self.query |
|||
|
|||
return uri |
|||
|
|||
@property |
|||
def netloc(self): |
|||
"""Network location including host and port""" |
|||
if self.port: |
|||
return '%s:%d' % (self.host, self.port) |
|||
return self.host |
|||
|
|||
|
|||
def split_first(s, delims): |
|||
""" |
|||
Given a string and an iterable of delimiters, split on the first found |
|||
delimiter. Return two split parts and the matched delimiter. |
|||
|
|||
If not found, then the first part is the full input string. |
|||
|
|||
Example: :: |
|||
|
|||
>>> split_first('foo/bar?baz', '?/=') |
|||
('foo', 'bar?baz', '/') |
|||
>>> split_first('foo/bar?baz', '123') |
|||
('foo/bar?baz', '', None) |
|||
|
|||
Scales linearly with number of delims. Not ideal for large number of delims. |
|||
""" |
|||
min_idx = None |
|||
min_delim = None |
|||
for d in delims: |
|||
idx = s.find(d) |
|||
if idx < 0: |
|||
continue |
|||
|
|||
if min_idx is None or idx < min_idx: |
|||
min_idx = idx |
|||
min_delim = d |
|||
|
|||
if min_idx is None or min_idx < 0: |
|||
return s, '', None |
|||
|
|||
return s[:min_idx], s[min_idx+1:], min_delim |
|||
|
|||
|
|||
def parse_url(url): |
|||
""" |
|||
Given a url, return a parsed :class:`.Url` namedtuple. Best-effort is |
|||
performed to parse incomplete urls. Fields not provided will be None. |
|||
|
|||
Partly backwards-compatible with :mod:`urlparse`. |
|||
|
|||
Example: :: |
|||
|
|||
>>> parse_url('http://google.com/mail/') |
|||
Url(scheme='http', host='google.com', port=None, path='/', ...) |
|||
>>> parse_url('google.com:80') |
|||
Url(scheme=None, host='google.com', port=80, path=None, ...) |
|||
>>> parse_url('/foo?bar') |
|||
Url(scheme=None, host=None, port=None, path='/foo', query='bar', ...) |
|||
""" |
|||
|
|||
# While this code has overlap with stdlib's urlparse, it is much |
|||
# simplified for our needs and less annoying. |
|||
# Additionally, this implementations does silly things to be optimal |
|||
# on CPython. |
|||
|
|||
scheme = None |
|||
auth = None |
|||
host = None |
|||
port = None |
|||
path = None |
|||
fragment = None |
|||
query = None |
|||
|
|||
# Scheme |
|||
if '://' in url: |
|||
scheme, url = url.split('://', 1) |
|||
|
|||
# Find the earliest Authority Terminator |
|||
# (http://tools.ietf.org/html/rfc3986#section-3.2) |
|||
url, path_, delim = split_first(url, ['/', '?', '#']) |
|||
|
|||
if delim: |
|||
# Reassemble the path |
|||
path = delim + path_ |
|||
|
|||
# Auth |
|||
if '@' in url: |
|||
# Last '@' denotes end of auth part |
|||
auth, url = url.rsplit('@', 1) |
|||
|
|||
# IPv6 |
|||
if url and url[0] == '[': |
|||
host, url = url.split(']', 1) |
|||
host += ']' |
|||
|
|||
# Port |
|||
if ':' in url: |
|||
_host, port = url.split(':', 1) |
|||
|
|||
if not host: |
|||
host = _host |
|||
|
|||
if port: |
|||
# If given, ports must be integers. |
|||
if not port.isdigit(): |
|||
raise LocationParseError(url) |
|||
port = int(port) |
|||
else: |
|||
# Blank ports are cool, too. (rfc3986#section-3.2.3) |
|||
port = None |
|||
|
|||
elif not host and url: |
|||
host = url |
|||
|
|||
if not path: |
|||
return Url(scheme, auth, host, port, path, query, fragment) |
|||
|
|||
# Fragment |
|||
if '#' in path: |
|||
path, fragment = path.split('#', 1) |
|||
|
|||
# Query |
|||
if '?' in path: |
|||
path, query = path.split('?', 1) |
|||
|
|||
return Url(scheme, auth, host, port, path, query, fragment) |
|||
|
|||
|
|||
def get_host(url): |
|||
""" |
|||
Deprecated. Use :func:`.parse_url` instead. |
|||
""" |
|||
p = parse_url(url) |
|||
return p.scheme or 'http', p.hostname, p.port |
Loading…
Reference in new issue