summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJesse Shapiro <jesse@jesseshapiro.net>2017-12-15 09:15:48 -0500
committerGitHub <noreply@github.com>2017-12-15 09:15:48 -0500
commit4d7f4edd6e1b4b7e51df57787dec20dc07c69c51 (patch)
treef658f3909aa542837caa1e160f647b82de365a40
parent9bce238ddc0bde520c5b15abba63902c7e7bedc5 (diff)
parentda05fe2497e18b367cab159195a1ace9a74119e3 (diff)
downloadurllib3-4d7f4edd6e1b4b7e51df57787dec20dc07c69c51.tar.gz
Merge branch 'master' into employer-credit
-rw-r--r--dummyserver/testcase.py17
-rw-r--r--test/test_connectionpool.py2
-rw-r--r--test/test_poolmanager.py8
-rw-r--r--test/with_dummyserver/test_https.py5
-rw-r--r--test/with_dummyserver/test_socketlevel.py31
-rw-r--r--tox.ini7
-rw-r--r--urllib3/connection.py31
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
diff --git a/tox.ini b/tox.ini
index a8b9deb3..496bde33 100644
--- a/tox.ini
+++ b/tox.ini
@@ -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(