diff options
author | Jesse Shapiro <jesse@jesseshapiro.net> | 2017-12-15 09:15:48 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-12-15 09:15:48 -0500 |
commit | 4d7f4edd6e1b4b7e51df57787dec20dc07c69c51 (patch) | |
tree | f658f3909aa542837caa1e160f647b82de365a40 | |
parent | 9bce238ddc0bde520c5b15abba63902c7e7bedc5 (diff) | |
parent | da05fe2497e18b367cab159195a1ace9a74119e3 (diff) | |
download | urllib3-4d7f4edd6e1b4b7e51df57787dec20dc07c69c51.tar.gz |
Merge branch 'master' into employer-credit
-rw-r--r-- | dummyserver/testcase.py | 17 | ||||
-rw-r--r-- | test/test_connectionpool.py | 2 | ||||
-rw-r--r-- | test/test_poolmanager.py | 8 | ||||
-rw-r--r-- | test/with_dummyserver/test_https.py | 5 | ||||
-rw-r--r-- | test/with_dummyserver/test_socketlevel.py | 31 | ||||
-rw-r--r-- | tox.ini | 7 | ||||
-rw-r--r-- | urllib3/connection.py | 31 |
7 files changed, 93 insertions, 8 deletions
diff --git a/dummyserver/testcase.py b/dummyserver/testcase.py index 3eb6e7be..f73f028e 100644 --- a/dummyserver/testcase.py +++ b/dummyserver/testcase.py @@ -76,6 +76,23 @@ class SocketDummyServerTestCase(unittest.TestCase): if hasattr(cls, 'server_thread'): cls.server_thread.join(0.1) + def assert_header_received( + self, + received_headers, + header_name, + expected_value=None + ): + header_name = header_name.encode('ascii') + if expected_value is not None: + expected_value = expected_value.encode('ascii') + header_titles = [] + for header in received_headers: + key, value = header.split(b': ') + header_titles.append(key) + if key == header_name and expected_value is not None: + self.assertEqual(value, expected_value) + self.assertIn(header_name, header_titles) + class IPV4SocketDummyServerTestCase(SocketDummyServerTestCase): @classmethod diff --git a/test/test_connectionpool.py b/test/test_connectionpool.py index d8b8a839..db861990 100644 --- a/test/test_connectionpool.py +++ b/test/test_connectionpool.py @@ -118,6 +118,7 @@ class TestConnectionPool(object): ('google.com', 'https://google.com/'), ('yahoo.com', 'http://google.com/'), ('google.com', 'https://google.net/'), + ('google.com', 'http://google.com./'), ]) def test_not_same_host_no_port_http(self, a, b): with HTTPConnectionPool(a) as c: @@ -130,6 +131,7 @@ class TestConnectionPool(object): ('google.com', 'http://google.com/'), ('yahoo.com', 'https://google.com/'), ('google.com', 'https://google.net/'), + ('google.com', 'https://google.com./'), ]) def test_not_same_host_no_port_https(self, a, b): with HTTPSConnectionPool(a) as c: diff --git a/test/test_poolmanager.py b/test/test_poolmanager.py index 8e253c7c..a233f013 100644 --- a/test/test_poolmanager.py +++ b/test/test_poolmanager.py @@ -31,6 +31,14 @@ class TestPoolManager(object): assert conn1 == conn2 + # Ensure that FQDNs are handled separately from relative domains + p = PoolManager(2) + + conn1 = p.connection_from_url('http://localhost.:8081/foo') + conn2 = p.connection_from_url('http://localhost:8081/bar') + + assert conn1 != conn2 + def test_many_urls(self): urls = [ "http://localhost:8081/foo", diff --git a/test/with_dummyserver/test_https.py b/test/with_dummyserver/test_https.py index ba947ea9..7b8d57b1 100644 --- a/test/with_dummyserver/test_https.py +++ b/test/with_dummyserver/test_https.py @@ -65,6 +65,11 @@ class TestHTTPS(HTTPSDummyServerTestCase): r = self._pool.request('GET', '/') self.assertEqual(r.status, 200, r.data) + def test_dotted_fqdn(self): + pool = HTTPSConnectionPool(self.host + '.', self.port) + r = pool.request('GET', '/') + self.assertEqual(r.status, 200, r.data) + def test_set_ssl_version_to_tlsv1(self): self._pool.ssl_version = ssl.PROTOCOL_TLSv1 r = self._pool.request('GET', '/') diff --git a/test/with_dummyserver/test_socketlevel.py b/test/with_dummyserver/test_socketlevel.py index 5656c218..5bc127f2 100644 --- a/test/with_dummyserver/test_socketlevel.py +++ b/test/with_dummyserver/test_socketlevel.py @@ -1225,6 +1225,37 @@ class TestHeaders(SocketDummyServerTestCase): pool.request('GET', '/', headers=OrderedDict(expected_request_headers)) self.assertEqual(expected_request_headers, actual_request_headers) + def test_request_host_header_ignores_fqdn_dot(self): + + received_headers = [] + + def socket_handler(listener): + sock = listener.accept()[0] + + buf = b'' + while not buf.endswith(b'\r\n\r\n'): + buf += sock.recv(65536) + + for header in buf.split(b'\r\n')[1:]: + if header: + received_headers.append(header) + + sock.send(( + u'HTTP/1.1 204 No Content\r\n' + u'Content-Length: 0\r\n' + u'\r\n').encode('ascii')) + + sock.close() + + self._start_server(socket_handler) + + pool = HTTPConnectionPool(self.host + '.', self.port, retries=False) + self.addCleanup(pool.close) + pool.request('GET', '/') + self.assert_header_received( + received_headers, 'Host', '%s:%s' % (self.host, self.port) + ) + def test_response_headers_are_returned_in_the_original_order(self): # NOTE: Probability this test gives a false negative is 1/(K!) K = 16 @@ -2,13 +2,6 @@ envlist = flake8-py3, py26, py27, py34, py35, py36, pypy [testenv] -basepython = - py26: {env:TOXPY26:python2.6} - py27: {env:TOXPY27:python2.7} - py34: {env:TOXPY34:python3.4} - py35: {env:TOXPY35:python3.5} - py36: {env:TOXPY36:python3.6} - pypy: {env:TOXPYPY:pypy} deps= -r{toxinidir}/dev-requirements.txt commands= # Print out the python version and bitness diff --git a/urllib3/connection.py b/urllib3/connection.py index c0d83299..06bcbde1 100644 --- a/urllib3/connection.py +++ b/urllib3/connection.py @@ -124,6 +124,35 @@ class HTTPConnection(_HTTPConnection, object): # Superclass also sets self.source_address in Python 2.7+. _HTTPConnection.__init__(self, *args, **kw) + @property + def host(self): + """ + Getter method to remove any trailing dots that indicate the hostname is an FQDN. + + In general, SSL certificates don't include the trailing dot indicating a + fully-qualified domain name, and thus, they don't validate properly when + checked against a domain name that includes the dot. In addition, some + servers may not expect to receive the trailing dot when provided. + + However, the hostname with trailing dot is critical to DNS resolution; doing a + lookup with the trailing dot will properly only resolve the appropriate FQDN, + whereas a lookup without a trailing dot will search the system's search domain + list. Thus, it's important to keep the original host around for use only in + those cases where it's appropriate (i.e., when doing DNS lookup to establish the + actual TCP connection across which we're going to send HTTP requests). + """ + return self._dns_host.rstrip('.') + + @host.setter + def host(self, value): + """ + Setter for the `host` property. + + We assume that only urllib3 uses the _dns_host attribute; httplib itself + only uses `host`, and it seems reasonable that other libraries follow suit. + """ + self._dns_host = value + def _new_conn(self): """ Establish a socket connection and set nodelay settings on it. @@ -138,7 +167,7 @@ class HTTPConnection(_HTTPConnection, object): try: conn = connection.create_connection( - (self.host, self.port), self.timeout, **extra_kw) + (self._dns_host, self.port), self.timeout, **extra_kw) except SocketTimeout as e: raise ConnectTimeoutError( |