blob: 591b48d6d4d76b0bb9e87ce16162e824c955ffc3 [file] [log] [blame]
Senthil Kumaran78373762014-08-20 07:53:58 +05301import base64
Antoine Pitrou803e6d62010-10-13 10:36:15 +00002import os
Barry Warsaw820c1202008-06-12 04:06:45 +00003import email
Jeremy Hylton1afc1692008-06-18 20:49:58 +00004import urllib.parse
5import urllib.request
Georg Brandl24420152008-05-26 16:32:26 +00006import http.server
Antoine Pitroua6a4dc82017-09-07 18:56:24 +02007import threading
Guido van Rossumcd16bf62007-06-13 18:07:49 +00008import unittest
9import hashlib
Senthil Kumarand943fde2014-04-15 16:36:43 -040010
Benjamin Petersonee8712c2008-05-20 21:35:26 +000011from test import support
Senthil Kumarand943fde2014-04-15 16:36:43 -040012
Antoine Pitrouda232592013-02-05 21:20:51 +010013try:
14 import ssl
15except ImportError:
16 ssl = None
Antoine Pitrou803e6d62010-10-13 10:36:15 +000017
18here = os.path.dirname(__file__)
19# Self-signed cert file for 'localhost'
20CERT_localhost = os.path.join(here, 'keycert.pem')
21# Self-signed cert file for 'fakehostname'
22CERT_fakehostname = os.path.join(here, 'keycert2.pem')
23
Antoine Pitrouda232592013-02-05 21:20:51 +010024
Guido van Rossumcd16bf62007-06-13 18:07:49 +000025# Loopback http server infrastructure
26
Georg Brandl24420152008-05-26 16:32:26 +000027class LoopbackHttpServer(http.server.HTTPServer):
Guido van Rossumcd16bf62007-06-13 18:07:49 +000028 """HTTP server w/ a few modifications that make it useful for
29 loopback testing purposes.
30 """
31
32 def __init__(self, server_address, RequestHandlerClass):
Georg Brandl24420152008-05-26 16:32:26 +000033 http.server.HTTPServer.__init__(self,
34 server_address,
35 RequestHandlerClass)
Guido van Rossumcd16bf62007-06-13 18:07:49 +000036
37 # Set the timeout of our listening socket really low so
38 # that we can stop the server easily.
Antoine Pitrou803e6d62010-10-13 10:36:15 +000039 self.socket.settimeout(0.1)
Guido van Rossumcd16bf62007-06-13 18:07:49 +000040
41 def get_request(self):
Georg Brandl24420152008-05-26 16:32:26 +000042 """HTTPServer method, overridden."""
Guido van Rossumcd16bf62007-06-13 18:07:49 +000043
44 request, client_address = self.socket.accept()
45
46 # It's a loopback connection, so setting the timeout
47 # really low shouldn't affect anything, but should make
48 # deadlocks less likely to occur.
49 request.settimeout(10.0)
50
51 return (request, client_address)
52
53class LoopbackHttpServerThread(threading.Thread):
54 """Stoppable thread that runs a loopback http server."""
55
Guido van Rossum806c2462007-08-06 23:33:07 +000056 def __init__(self, request_handler):
Guido van Rossumcd16bf62007-06-13 18:07:49 +000057 threading.Thread.__init__(self)
Guido van Rossum4566c712007-08-21 03:36:47 +000058 self._stop_server = False
Guido van Rossumcd16bf62007-06-13 18:07:49 +000059 self.ready = threading.Event()
Guido van Rossum806c2462007-08-06 23:33:07 +000060 request_handler.protocol_version = "HTTP/1.0"
Jeremy Hylton1afc1692008-06-18 20:49:58 +000061 self.httpd = LoopbackHttpServer(("127.0.0.1", 0),
Guido van Rossum806c2462007-08-06 23:33:07 +000062 request_handler)
Guido van Rossum806c2462007-08-06 23:33:07 +000063 self.port = self.httpd.server_port
Guido van Rossumcd16bf62007-06-13 18:07:49 +000064
65 def stop(self):
66 """Stops the webserver if it's currently running."""
67
Guido van Rossum4566c712007-08-21 03:36:47 +000068 self._stop_server = True
Guido van Rossumcd16bf62007-06-13 18:07:49 +000069
70 self.join()
Antoine Pitroub6751dc2010-10-30 17:33:22 +000071 self.httpd.server_close()
Guido van Rossumcd16bf62007-06-13 18:07:49 +000072
73 def run(self):
Guido van Rossumcd16bf62007-06-13 18:07:49 +000074 self.ready.set()
Guido van Rossum4566c712007-08-21 03:36:47 +000075 while not self._stop_server:
Guido van Rossum806c2462007-08-06 23:33:07 +000076 self.httpd.handle_request()
Guido van Rossumcd16bf62007-06-13 18:07:49 +000077
78# Authentication infrastructure
79
80class DigestAuthHandler:
81 """Handler for performing digest authentication."""
82
83 def __init__(self):
84 self._request_num = 0
85 self._nonces = []
86 self._users = {}
87 self._realm_name = "Test Realm"
88 self._qop = "auth"
89
90 def set_qop(self, qop):
91 self._qop = qop
92
93 def set_users(self, users):
94 assert isinstance(users, dict)
95 self._users = users
96
97 def set_realm(self, realm):
98 self._realm_name = realm
99
100 def _generate_nonce(self):
101 self._request_num += 1
Guido van Rossum81360142007-08-29 14:26:52 +0000102 nonce = hashlib.md5(str(self._request_num).encode("ascii")).hexdigest()
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000103 self._nonces.append(nonce)
104 return nonce
105
106 def _create_auth_dict(self, auth_str):
107 first_space_index = auth_str.find(" ")
108 auth_str = auth_str[first_space_index+1:]
109
110 parts = auth_str.split(",")
111
112 auth_dict = {}
113 for part in parts:
114 name, value = part.split("=")
115 name = name.strip()
116 if value[0] == '"' and value[-1] == '"':
117 value = value[1:-1]
118 else:
119 value = value.strip()
120 auth_dict[name] = value
121 return auth_dict
122
123 def _validate_auth(self, auth_dict, password, method, uri):
124 final_dict = {}
125 final_dict.update(auth_dict)
126 final_dict["password"] = password
127 final_dict["method"] = method
128 final_dict["uri"] = uri
129 HA1_str = "%(username)s:%(realm)s:%(password)s" % final_dict
Guido van Rossum81360142007-08-29 14:26:52 +0000130 HA1 = hashlib.md5(HA1_str.encode("ascii")).hexdigest()
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000131 HA2_str = "%(method)s:%(uri)s" % final_dict
Guido van Rossum81360142007-08-29 14:26:52 +0000132 HA2 = hashlib.md5(HA2_str.encode("ascii")).hexdigest()
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000133 final_dict["HA1"] = HA1
134 final_dict["HA2"] = HA2
135 response_str = "%(HA1)s:%(nonce)s:%(nc)s:" \
136 "%(cnonce)s:%(qop)s:%(HA2)s" % final_dict
Guido van Rossum81360142007-08-29 14:26:52 +0000137 response = hashlib.md5(response_str.encode("ascii")).hexdigest()
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000138
139 return response == auth_dict["response"]
140
141 def _return_auth_challenge(self, request_handler):
142 request_handler.send_response(407, "Proxy Authentication Required")
143 request_handler.send_header("Content-Type", "text/html")
144 request_handler.send_header(
145 'Proxy-Authenticate', 'Digest realm="%s", '
146 'qop="%s",'
147 'nonce="%s", ' % \
148 (self._realm_name, self._qop, self._generate_nonce()))
149 # XXX: Not sure if we're supposed to add this next header or
150 # not.
151 #request_handler.send_header('Connection', 'close')
152 request_handler.end_headers()
Guido van Rossum8a392d72007-11-21 22:09:45 +0000153 request_handler.wfile.write(b"Proxy Authentication Required.")
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000154 return False
155
156 def handle_request(self, request_handler):
157 """Performs digest authentication on the given HTTP request
158 handler. Returns True if authentication was successful, False
159 otherwise.
160
161 If no users have been set, then digest auth is effectively
162 disabled and this method will always return True.
163 """
164
165 if len(self._users) == 0:
166 return True
167
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000168 if "Proxy-Authorization" not in request_handler.headers:
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000169 return self._return_auth_challenge(request_handler)
170 else:
171 auth_dict = self._create_auth_dict(
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000172 request_handler.headers["Proxy-Authorization"]
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000173 )
174 if auth_dict["username"] in self._users:
175 password = self._users[ auth_dict["username"] ]
176 else:
177 return self._return_auth_challenge(request_handler)
178 if not auth_dict.get("nonce") in self._nonces:
179 return self._return_auth_challenge(request_handler)
180 else:
181 self._nonces.remove(auth_dict["nonce"])
182
183 auth_validated = False
184
185 # MSIE uses short_path in its validation, but Python's
Florent Xicluna419e3842010-08-08 16:16:07 +0000186 # urllib.request uses the full path, so we're going to see if
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000187 # either of them works here.
188
189 for path in [request_handler.path, request_handler.short_path]:
190 if self._validate_auth(auth_dict,
191 password,
192 request_handler.command,
193 path):
194 auth_validated = True
195
196 if not auth_validated:
197 return self._return_auth_challenge(request_handler)
198 return True
199
Senthil Kumaran78373762014-08-20 07:53:58 +0530200
201class BasicAuthHandler(http.server.BaseHTTPRequestHandler):
202 """Handler for performing basic authentication."""
203 # Server side values
204 USER = 'testUser'
205 PASSWD = 'testPass'
206 REALM = 'Test'
207 USER_PASSWD = "%s:%s" % (USER, PASSWD)
208 ENCODED_AUTH = base64.b64encode(USER_PASSWD.encode('ascii')).decode('ascii')
209
210 def __init__(self, *args, **kwargs):
211 http.server.BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
212
213 def log_message(self, format, *args):
214 # Suppress console log message
215 pass
216
217 def do_HEAD(self):
218 self.send_response(200)
219 self.send_header("Content-type", "text/html")
220 self.end_headers()
221
222 def do_AUTHHEAD(self):
223 self.send_response(401)
224 self.send_header("WWW-Authenticate", "Basic realm=\"%s\"" % self.REALM)
225 self.send_header("Content-type", "text/html")
226 self.end_headers()
227
228 def do_GET(self):
229 if not self.headers.get("Authorization", ""):
230 self.do_AUTHHEAD()
231 self.wfile.write(b"No Auth header received")
232 elif self.headers.get(
233 "Authorization", "") == "Basic " + self.ENCODED_AUTH:
234 self.send_response(200)
235 self.end_headers()
236 self.wfile.write(b"It works")
237 else:
238 # Request Unauthorized
239 self.do_AUTHHEAD()
Senthil Kumaran78373762014-08-20 07:53:58 +0530240
241
242
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000243# Proxy test infrastructure
244
Georg Brandl24420152008-05-26 16:32:26 +0000245class FakeProxyHandler(http.server.BaseHTTPRequestHandler):
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000246 """This is a 'fake proxy' that makes it look like the entire
247 internet has gone down due to a sudden zombie invasion. It main
248 utility is in providing us with authentication support for
249 testing.
250 """
251
Collin Winter9a4414d2009-05-18 22:32:26 +0000252 def __init__(self, digest_auth_handler, *args, **kwargs):
253 # This has to be set before calling our parent's __init__(), which will
254 # try to call do_GET().
255 self.digest_auth_handler = digest_auth_handler
256 http.server.BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000257
258 def log_message(self, format, *args):
259 # Uncomment the next line for debugging.
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000260 # sys.stderr.write(format % args)
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000261 pass
262
263 def do_GET(self):
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000264 (scm, netloc, path, params, query, fragment) = urllib.parse.urlparse(
265 self.path, "http")
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000266 self.short_path = path
267 if self.digest_auth_handler.handle_request(self):
268 self.send_response(200, "OK")
269 self.send_header("Content-Type", "text/html")
270 self.end_headers()
Guido van Rossum8a392d72007-11-21 22:09:45 +0000271 self.wfile.write(bytes("You've reached %s!<BR>" % self.path,
272 "ascii"))
273 self.wfile.write(b"Our apologies, but our server is down due to "
274 b"a sudden zombie invasion.")
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000275
276# Test cases
277
Senthil Kumaran78373762014-08-20 07:53:58 +0530278class BasicAuthTests(unittest.TestCase):
279 USER = "testUser"
280 PASSWD = "testPass"
281 INCORRECT_PASSWD = "Incorrect"
282 REALM = "Test"
283
284 def setUp(self):
285 super(BasicAuthTests, self).setUp()
286 # With Basic Authentication
287 def http_server_with_basic_auth_handler(*args, **kwargs):
288 return BasicAuthHandler(*args, **kwargs)
289 self.server = LoopbackHttpServerThread(http_server_with_basic_auth_handler)
Victor Stinner45dba3a2017-06-30 17:04:48 +0200290 self.addCleanup(self.stop_server)
Senthil Kumaran78373762014-08-20 07:53:58 +0530291 self.server_url = 'http://127.0.0.1:%s' % self.server.port
292 self.server.start()
293 self.server.ready.wait()
294
Victor Stinner45dba3a2017-06-30 17:04:48 +0200295 def stop_server(self):
296 self.server.stop()
297 self.server = None
298
Senthil Kumaran78373762014-08-20 07:53:58 +0530299 def tearDown(self):
Senthil Kumaran78373762014-08-20 07:53:58 +0530300 super(BasicAuthTests, self).tearDown()
301
302 def test_basic_auth_success(self):
303 ah = urllib.request.HTTPBasicAuthHandler()
304 ah.add_password(self.REALM, self.server_url, self.USER, self.PASSWD)
305 urllib.request.install_opener(urllib.request.build_opener(ah))
306 try:
307 self.assertTrue(urllib.request.urlopen(self.server_url))
308 except urllib.error.HTTPError:
Bradley Laney6b490b52018-07-10 05:46:44 -0400309 self.fail("Basic auth failed for the url: %s" % self.server_url)
Senthil Kumaran78373762014-08-20 07:53:58 +0530310
311 def test_basic_auth_httperror(self):
312 ah = urllib.request.HTTPBasicAuthHandler()
313 ah.add_password(self.REALM, self.server_url, self.USER, self.INCORRECT_PASSWD)
314 urllib.request.install_opener(urllib.request.build_opener(ah))
315 self.assertRaises(urllib.error.HTTPError, urllib.request.urlopen, self.server_url)
316
317
Antoine Pitrou803e6d62010-10-13 10:36:15 +0000318class ProxyAuthTests(unittest.TestCase):
Christian Heimesbbe741d2008-03-28 10:53:29 +0000319 URL = "http://localhost"
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000320
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000321 USER = "tester"
322 PASSWD = "test123"
323 REALM = "TestRealm"
324
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000325 def setUp(self):
Florent Xicluna9b86b9a2010-03-19 19:00:44 +0000326 super(ProxyAuthTests, self).setUp()
Martin Panter1002a622016-10-22 01:42:06 +0000327 # Ignore proxy bypass settings in the environment.
328 def restore_environ(old_environ):
329 os.environ.clear()
330 os.environ.update(old_environ)
331 self.addCleanup(restore_environ, os.environ.copy())
332 os.environ['NO_PROXY'] = ''
333 os.environ['no_proxy'] = ''
334
Collin Winter9a4414d2009-05-18 22:32:26 +0000335 self.digest_auth_handler = DigestAuthHandler()
336 self.digest_auth_handler.set_users({self.USER: self.PASSWD})
337 self.digest_auth_handler.set_realm(self.REALM)
Senthil Kumaran78373762014-08-20 07:53:58 +0530338 # With Digest Authentication.
Collin Winter9a4414d2009-05-18 22:32:26 +0000339 def create_fake_proxy_handler(*args, **kwargs):
340 return FakeProxyHandler(self.digest_auth_handler, *args, **kwargs)
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000341
Collin Winter9a4414d2009-05-18 22:32:26 +0000342 self.server = LoopbackHttpServerThread(create_fake_proxy_handler)
Victor Stinner45dba3a2017-06-30 17:04:48 +0200343 self.addCleanup(self.stop_server)
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000344 self.server.start()
345 self.server.ready.wait()
Guido van Rossum806c2462007-08-06 23:33:07 +0000346 proxy_url = "http://127.0.0.1:%d" % self.server.port
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000347 handler = urllib.request.ProxyHandler({"http" : proxy_url})
Collin Winter9a4414d2009-05-18 22:32:26 +0000348 self.proxy_digest_handler = urllib.request.ProxyDigestAuthHandler()
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000349 self.opener = urllib.request.build_opener(
Collin Winter9a4414d2009-05-18 22:32:26 +0000350 handler, self.proxy_digest_handler)
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000351
Victor Stinner45dba3a2017-06-30 17:04:48 +0200352 def stop_server(self):
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000353 self.server.stop()
Victor Stinner45dba3a2017-06-30 17:04:48 +0200354 self.server = None
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000355
356 def test_proxy_with_bad_password_raises_httperror(self):
Collin Winter9a4414d2009-05-18 22:32:26 +0000357 self.proxy_digest_handler.add_password(self.REALM, self.URL,
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000358 self.USER, self.PASSWD+"bad")
Collin Winter9a4414d2009-05-18 22:32:26 +0000359 self.digest_auth_handler.set_qop("auth")
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000360 self.assertRaises(urllib.error.HTTPError,
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000361 self.opener.open,
362 self.URL)
363
364 def test_proxy_with_no_password_raises_httperror(self):
Collin Winter9a4414d2009-05-18 22:32:26 +0000365 self.digest_auth_handler.set_qop("auth")
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000366 self.assertRaises(urllib.error.HTTPError,
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000367 self.opener.open,
368 self.URL)
369
370 def test_proxy_qop_auth_works(self):
Collin Winter9a4414d2009-05-18 22:32:26 +0000371 self.proxy_digest_handler.add_password(self.REALM, self.URL,
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000372 self.USER, self.PASSWD)
Collin Winter9a4414d2009-05-18 22:32:26 +0000373 self.digest_auth_handler.set_qop("auth")
Serhiy Storchaka5b10b982019-03-05 10:06:26 +0200374 with self.opener.open(self.URL) as result:
375 while result.read():
376 pass
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000377
378 def test_proxy_qop_auth_int_works_or_throws_urlerror(self):
Collin Winter9a4414d2009-05-18 22:32:26 +0000379 self.proxy_digest_handler.add_password(self.REALM, self.URL,
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000380 self.USER, self.PASSWD)
Collin Winter9a4414d2009-05-18 22:32:26 +0000381 self.digest_auth_handler.set_qop("auth-int")
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000382 try:
383 result = self.opener.open(self.URL)
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000384 except urllib.error.URLError:
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000385 # It's okay if we don't support auth-int, but we certainly
386 # shouldn't receive any kind of exception here other than
387 # a URLError.
Serhiy Storchaka5b10b982019-03-05 10:06:26 +0200388 pass
389 else:
390 with result:
391 while result.read():
392 pass
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000393
Christian Heimesbbe741d2008-03-28 10:53:29 +0000394
395def GetRequestHandler(responses):
396
Georg Brandl24420152008-05-26 16:32:26 +0000397 class FakeHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
Christian Heimesbbe741d2008-03-28 10:53:29 +0000398
399 server_version = "TestHTTP/"
400 requests = []
401 headers_received = []
402 port = 80
403
404 def do_GET(self):
405 body = self.send_head()
Florent Xicluna37d3d9a2010-08-08 16:25:27 +0000406 while body:
407 done = self.wfile.write(body)
408 body = body[done:]
Christian Heimesbbe741d2008-03-28 10:53:29 +0000409
410 def do_POST(self):
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000411 content_length = self.headers["Content-Length"]
Christian Heimesbbe741d2008-03-28 10:53:29 +0000412 post_data = self.rfile.read(int(content_length))
413 self.do_GET()
414 self.requests.append(post_data)
415
416 def send_head(self):
417 FakeHTTPRequestHandler.headers_received = self.headers
418 self.requests.append(self.path)
419 response_code, headers, body = responses.pop(0)
420
421 self.send_response(response_code)
422
423 for (header, value) in headers:
Antoine Pitroub353c122009-02-11 00:39:14 +0000424 self.send_header(header, value % {'port':self.port})
Christian Heimesbbe741d2008-03-28 10:53:29 +0000425 if body:
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000426 self.send_header("Content-type", "text/plain")
Christian Heimesbbe741d2008-03-28 10:53:29 +0000427 self.end_headers()
428 return body
429 self.end_headers()
430
431 def log_message(self, *args):
432 pass
433
434
435 return FakeHTTPRequestHandler
436
437
Antoine Pitrou803e6d62010-10-13 10:36:15 +0000438class TestUrlopen(unittest.TestCase):
Florent Xicluna419e3842010-08-08 16:16:07 +0000439 """Tests urllib.request.urlopen using the network.
Christian Heimesbbe741d2008-03-28 10:53:29 +0000440
441 These tests are not exhaustive. Assuming that testing using files does a
442 good job overall of some of the basic interface features. There are no
443 tests exercising the optional 'data' and 'proxies' arguments. No tests
444 for transparent redirection have been written.
445 """
446
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000447 def setUp(self):
Florent Xicluna9b86b9a2010-03-19 19:00:44 +0000448 super(TestUrlopen, self).setUp()
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000449
Victor Stinnerbc6b72e2016-03-24 13:55:58 +0100450 # Ignore proxies for localhost tests.
451 def restore_environ(old_environ):
452 os.environ.clear()
453 os.environ.update(old_environ)
454 self.addCleanup(restore_environ, os.environ.copy())
455 os.environ['NO_PROXY'] = '*'
Martin Panter1002a622016-10-22 01:42:06 +0000456 os.environ['no_proxy'] = '*'
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000457
Antoine Pitrou803e6d62010-10-13 10:36:15 +0000458 def urlopen(self, url, data=None, **kwargs):
Antoine Pitroub353c122009-02-11 00:39:14 +0000459 l = []
Antoine Pitrou803e6d62010-10-13 10:36:15 +0000460 f = urllib.request.urlopen(url, data, **kwargs)
Antoine Pitroub353c122009-02-11 00:39:14 +0000461 try:
462 # Exercise various methods
463 l.extend(f.readlines(200))
464 l.append(f.readline())
465 l.append(f.read(1024))
466 l.append(f.read())
467 finally:
468 f.close()
469 return b"".join(l)
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000470
Victor Stinner45dba3a2017-06-30 17:04:48 +0200471 def stop_server(self):
472 self.server.stop()
473 self.server = None
474
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000475 def start_server(self, responses=None):
476 if responses is None:
477 responses = [(200, [], b"we don't care")]
Christian Heimesbbe741d2008-03-28 10:53:29 +0000478 handler = GetRequestHandler(responses)
479
480 self.server = LoopbackHttpServerThread(handler)
Victor Stinner45dba3a2017-06-30 17:04:48 +0200481 self.addCleanup(self.stop_server)
Christian Heimesbbe741d2008-03-28 10:53:29 +0000482 self.server.start()
483 self.server.ready.wait()
484 port = self.server.port
485 handler.port = port
486 return handler
487
Antoine Pitrouda232592013-02-05 21:20:51 +0100488 def start_https_server(self, responses=None, **kwargs):
Antoine Pitrou803e6d62010-10-13 10:36:15 +0000489 if not hasattr(urllib.request, 'HTTPSHandler'):
490 self.skipTest('ssl support required')
491 from test.ssl_servers import make_https_server
492 if responses is None:
493 responses = [(200, [], b"we care a bit")]
494 handler = GetRequestHandler(responses)
Antoine Pitrouda232592013-02-05 21:20:51 +0100495 server = make_https_server(self, handler_class=handler, **kwargs)
Antoine Pitrou803e6d62010-10-13 10:36:15 +0000496 handler.port = server.port
497 return handler
498
Christian Heimesbbe741d2008-03-28 10:53:29 +0000499 def test_redirection(self):
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000500 expected_response = b"We got here..."
Christian Heimesbbe741d2008-03-28 10:53:29 +0000501 responses = [
Antoine Pitroub353c122009-02-11 00:39:14 +0000502 (302, [("Location", "http://localhost:%(port)s/somewhere_else")],
503 ""),
Christian Heimesbbe741d2008-03-28 10:53:29 +0000504 (200, [], expected_response)
505 ]
506
507 handler = self.start_server(responses)
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000508 data = self.urlopen("http://localhost:%s/" % handler.port)
Florent Xicluna419e3842010-08-08 16:16:07 +0000509 self.assertEqual(data, expected_response)
510 self.assertEqual(handler.requests, ["/", "/somewhere_else"])
Christian Heimesbbe741d2008-03-28 10:53:29 +0000511
Antoine Pitroub353c122009-02-11 00:39:14 +0000512 def test_chunked(self):
513 expected_response = b"hello world"
514 chunked_start = (
515 b'a\r\n'
516 b'hello worl\r\n'
517 b'1\r\n'
518 b'd\r\n'
519 b'0\r\n'
520 )
521 response = [(200, [("Transfer-Encoding", "chunked")], chunked_start)]
522 handler = self.start_server(response)
523 data = self.urlopen("http://localhost:%s/" % handler.port)
Florent Xicluna419e3842010-08-08 16:16:07 +0000524 self.assertEqual(data, expected_response)
Antoine Pitroub353c122009-02-11 00:39:14 +0000525
Christian Heimesbbe741d2008-03-28 10:53:29 +0000526 def test_404(self):
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000527 expected_response = b"Bad bad bad..."
Christian Heimesbbe741d2008-03-28 10:53:29 +0000528 handler = self.start_server([(404, [], expected_response)])
529
530 try:
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000531 self.urlopen("http://localhost:%s/weeble" % handler.port)
532 except urllib.error.URLError as f:
533 data = f.read()
534 f.close()
535 else:
536 self.fail("404 should raise URLError")
Christian Heimesbbe741d2008-03-28 10:53:29 +0000537
Florent Xicluna419e3842010-08-08 16:16:07 +0000538 self.assertEqual(data, expected_response)
539 self.assertEqual(handler.requests, ["/weeble"])
Christian Heimesbbe741d2008-03-28 10:53:29 +0000540
541 def test_200(self):
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000542 expected_response = b"pycon 2008..."
Christian Heimesbbe741d2008-03-28 10:53:29 +0000543 handler = self.start_server([(200, [], expected_response)])
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000544 data = self.urlopen("http://localhost:%s/bizarre" % handler.port)
Florent Xicluna419e3842010-08-08 16:16:07 +0000545 self.assertEqual(data, expected_response)
546 self.assertEqual(handler.requests, ["/bizarre"])
Christian Heimesbbe741d2008-03-28 10:53:29 +0000547
548 def test_200_with_parameters(self):
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000549 expected_response = b"pycon 2008..."
Christian Heimesbbe741d2008-03-28 10:53:29 +0000550 handler = self.start_server([(200, [], expected_response)])
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000551 data = self.urlopen("http://localhost:%s/bizarre" % handler.port,
552 b"get=with_feeling")
Florent Xicluna419e3842010-08-08 16:16:07 +0000553 self.assertEqual(data, expected_response)
554 self.assertEqual(handler.requests, ["/bizarre", b"get=with_feeling"])
Christian Heimesbbe741d2008-03-28 10:53:29 +0000555
Antoine Pitrou803e6d62010-10-13 10:36:15 +0000556 def test_https(self):
557 handler = self.start_https_server()
Benjamin Peterson4ffb0752014-11-03 14:29:33 -0500558 context = ssl.create_default_context(cafile=CERT_localhost)
559 data = self.urlopen("https://localhost:%s/bizarre" % handler.port, context=context)
Antoine Pitrou803e6d62010-10-13 10:36:15 +0000560 self.assertEqual(data, b"we care a bit")
561
562 def test_https_with_cafile(self):
563 handler = self.start_https_server(certfile=CERT_localhost)
Christian Heimesd0486372016-09-10 23:23:33 +0200564 with support.check_warnings(('', DeprecationWarning)):
565 # Good cert
566 data = self.urlopen("https://localhost:%s/bizarre" % handler.port,
567 cafile=CERT_localhost)
568 self.assertEqual(data, b"we care a bit")
569 # Bad cert
570 with self.assertRaises(urllib.error.URLError) as cm:
571 self.urlopen("https://localhost:%s/bizarre" % handler.port,
572 cafile=CERT_fakehostname)
573 # Good cert, but mismatching hostname
574 handler = self.start_https_server(certfile=CERT_fakehostname)
Christian Heimes61d478c2018-01-27 15:51:38 +0100575 with self.assertRaises(urllib.error.URLError) as cm:
Christian Heimesd0486372016-09-10 23:23:33 +0200576 self.urlopen("https://localhost:%s/bizarre" % handler.port,
577 cafile=CERT_fakehostname)
Antoine Pitrou803e6d62010-10-13 10:36:15 +0000578
Antoine Pitroude9ac6c2012-05-16 21:40:01 +0200579 def test_https_with_cadefault(self):
580 handler = self.start_https_server(certfile=CERT_localhost)
581 # Self-signed cert should fail verification with system certificate store
Christian Heimesd0486372016-09-10 23:23:33 +0200582 with support.check_warnings(('', DeprecationWarning)):
583 with self.assertRaises(urllib.error.URLError) as cm:
584 self.urlopen("https://localhost:%s/bizarre" % handler.port,
585 cadefault=True)
Antoine Pitroude9ac6c2012-05-16 21:40:01 +0200586
Antoine Pitrouda232592013-02-05 21:20:51 +0100587 def test_https_sni(self):
588 if ssl is None:
589 self.skipTest("ssl module required")
590 if not ssl.HAS_SNI:
591 self.skipTest("SNI support required in OpenSSL")
592 sni_name = None
593 def cb_sni(ssl_sock, server_name, initial_context):
594 nonlocal sni_name
595 sni_name = server_name
Christian Heimesa170fa12017-09-15 20:27:30 +0200596 context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
Antoine Pitrouda232592013-02-05 21:20:51 +0100597 context.set_servername_callback(cb_sni)
598 handler = self.start_https_server(context=context, certfile=CERT_localhost)
Benjamin Peterson4ffb0752014-11-03 14:29:33 -0500599 context = ssl.create_default_context(cafile=CERT_localhost)
600 self.urlopen("https://localhost:%s" % handler.port, context=context)
Antoine Pitrouda232592013-02-05 21:20:51 +0100601 self.assertEqual(sni_name, "localhost")
602
Christian Heimesbbe741d2008-03-28 10:53:29 +0000603 def test_sending_headers(self):
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000604 handler = self.start_server()
605 req = urllib.request.Request("http://localhost:%s/" % handler.port,
606 headers={"Range": "bytes=20-39"})
Victor Stinnerbc6b72e2016-03-24 13:55:58 +0100607 with urllib.request.urlopen(req):
608 pass
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000609 self.assertEqual(handler.headers_received["Range"], "bytes=20-39")
Christian Heimesbbe741d2008-03-28 10:53:29 +0000610
611 def test_basic(self):
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000612 handler = self.start_server()
Serhiy Storchaka5b10b982019-03-05 10:06:26 +0200613 with urllib.request.urlopen("http://localhost:%s" % handler.port) as open_url:
614 for attr in ("read", "close", "info", "geturl"):
615 self.assertTrue(hasattr(open_url, attr), "object returned from "
616 "urlopen lacks the %s attribute" % attr)
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000617 self.assertTrue(open_url.read(), "calling 'read' failed")
Christian Heimesbbe741d2008-03-28 10:53:29 +0000618
619 def test_info(self):
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000620 handler = self.start_server()
Victor Stinnerbc6b72e2016-03-24 13:55:58 +0100621 open_url = urllib.request.urlopen(
622 "http://localhost:%s" % handler.port)
623 with open_url:
Christian Heimesbbe741d2008-03-28 10:53:29 +0000624 info_obj = open_url.info()
Victor Stinnerbc6b72e2016-03-24 13:55:58 +0100625 self.assertIsInstance(info_obj, email.message.Message,
626 "object returned by 'info' is not an "
627 "instance of email.message.Message")
628 self.assertEqual(info_obj.get_content_subtype(), "plain")
Christian Heimesbbe741d2008-03-28 10:53:29 +0000629
630 def test_geturl(self):
631 # Make sure same URL as opened is returned by geturl.
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000632 handler = self.start_server()
633 open_url = urllib.request.urlopen("http://localhost:%s" % handler.port)
Victor Stinnerbc6b72e2016-03-24 13:55:58 +0100634 with open_url:
635 url = open_url.geturl()
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000636 self.assertEqual(url, "http://localhost:%s" % handler.port)
Christian Heimesbbe741d2008-03-28 10:53:29 +0000637
Florent Xicluna37d3d9a2010-08-08 16:25:27 +0000638 def test_iteration(self):
639 expected_response = b"pycon 2008..."
640 handler = self.start_server([(200, [], expected_response)])
641 data = urllib.request.urlopen("http://localhost:%s" % handler.port)
642 for line in data:
643 self.assertEqual(line, expected_response)
644
645 def test_line_iteration(self):
646 lines = [b"We\n", b"got\n", b"here\n", b"verylong " * 8192 + b"\n"]
647 expected_response = b"".join(lines)
648 handler = self.start_server([(200, [], expected_response)])
649 data = urllib.request.urlopen("http://localhost:%s" % handler.port)
650 for index, line in enumerate(data):
651 self.assertEqual(line, lines[index],
652 "Fetched line number %s doesn't match expected:\n"
653 " Expected length was %s, got %s" %
654 (index, len(lines[index]), len(line)))
655 self.assertEqual(index + 1, len(lines))
656
Antoine Pitrou803e6d62010-10-13 10:36:15 +0000657
Senthil Kumarand943fde2014-04-15 16:36:43 -0400658threads_key = None
659
660def setUpModule():
661 # Store the threading_setup in a key and ensure that it is cleaned up
662 # in the tearDown
663 global threads_key
664 threads_key = support.threading_setup()
665
666def tearDownModule():
667 if threads_key:
Victor Stinnerd20324a2017-04-20 13:40:08 +0200668 support.threading_cleanup(*threads_key)
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000669
670if __name__ == "__main__":
Senthil Kumarand943fde2014-04-15 16:36:43 -0400671 unittest.main()