blob: 0650aa2744311d0a24bf81c04fc572722f6c68a5 [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
Guido van Rossumcd16bf62007-06-13 18:07:49 +00007import unittest
8import hashlib
Senthil Kumarand943fde2014-04-15 16:36:43 -04009
Benjamin Petersonee8712c2008-05-20 21:35:26 +000010from test import support
Senthil Kumarand943fde2014-04-15 16:36:43 -040011
Victor Stinner45df8202010-04-28 22:31:17 +000012threading = support.import_module('threading')
Senthil Kumarand943fde2014-04-15 16:36:43 -040013
Antoine Pitrouda232592013-02-05 21:20:51 +010014try:
15 import ssl
16except ImportError:
17 ssl = None
Antoine Pitrou803e6d62010-10-13 10:36:15 +000018
19here = os.path.dirname(__file__)
20# Self-signed cert file for 'localhost'
21CERT_localhost = os.path.join(here, 'keycert.pem')
22# Self-signed cert file for 'fakehostname'
23CERT_fakehostname = os.path.join(here, 'keycert2.pem')
24
Antoine Pitrouda232592013-02-05 21:20:51 +010025
Guido van Rossumcd16bf62007-06-13 18:07:49 +000026# Loopback http server infrastructure
27
Georg Brandl24420152008-05-26 16:32:26 +000028class LoopbackHttpServer(http.server.HTTPServer):
Guido van Rossumcd16bf62007-06-13 18:07:49 +000029 """HTTP server w/ a few modifications that make it useful for
30 loopback testing purposes.
31 """
32
33 def __init__(self, server_address, RequestHandlerClass):
Georg Brandl24420152008-05-26 16:32:26 +000034 http.server.HTTPServer.__init__(self,
35 server_address,
36 RequestHandlerClass)
Guido van Rossumcd16bf62007-06-13 18:07:49 +000037
38 # Set the timeout of our listening socket really low so
39 # that we can stop the server easily.
Antoine Pitrou803e6d62010-10-13 10:36:15 +000040 self.socket.settimeout(0.1)
Guido van Rossumcd16bf62007-06-13 18:07:49 +000041
42 def get_request(self):
Georg Brandl24420152008-05-26 16:32:26 +000043 """HTTPServer method, overridden."""
Guido van Rossumcd16bf62007-06-13 18:07:49 +000044
45 request, client_address = self.socket.accept()
46
47 # It's a loopback connection, so setting the timeout
48 # really low shouldn't affect anything, but should make
49 # deadlocks less likely to occur.
50 request.settimeout(10.0)
51
52 return (request, client_address)
53
54class LoopbackHttpServerThread(threading.Thread):
55 """Stoppable thread that runs a loopback http server."""
56
Guido van Rossum806c2462007-08-06 23:33:07 +000057 def __init__(self, request_handler):
Guido van Rossumcd16bf62007-06-13 18:07:49 +000058 threading.Thread.__init__(self)
Guido van Rossum4566c712007-08-21 03:36:47 +000059 self._stop_server = False
Guido van Rossumcd16bf62007-06-13 18:07:49 +000060 self.ready = threading.Event()
Guido van Rossum806c2462007-08-06 23:33:07 +000061 request_handler.protocol_version = "HTTP/1.0"
Jeremy Hylton1afc1692008-06-18 20:49:58 +000062 self.httpd = LoopbackHttpServer(("127.0.0.1", 0),
Guido van Rossum806c2462007-08-06 23:33:07 +000063 request_handler)
Guido van Rossum806c2462007-08-06 23:33:07 +000064 self.port = self.httpd.server_port
Guido van Rossumcd16bf62007-06-13 18:07:49 +000065
66 def stop(self):
67 """Stops the webserver if it's currently running."""
68
Guido van Rossum4566c712007-08-21 03:36:47 +000069 self._stop_server = True
Guido van Rossumcd16bf62007-06-13 18:07:49 +000070
71 self.join()
Antoine Pitroub6751dc2010-10-30 17:33:22 +000072 self.httpd.server_close()
Guido van Rossumcd16bf62007-06-13 18:07:49 +000073
74 def run(self):
Guido van Rossumcd16bf62007-06-13 18:07:49 +000075 self.ready.set()
Guido van Rossum4566c712007-08-21 03:36:47 +000076 while not self._stop_server:
Guido van Rossum806c2462007-08-06 23:33:07 +000077 self.httpd.handle_request()
Guido van Rossumcd16bf62007-06-13 18:07:49 +000078
79# Authentication infrastructure
80
81class DigestAuthHandler:
82 """Handler for performing digest authentication."""
83
84 def __init__(self):
85 self._request_num = 0
86 self._nonces = []
87 self._users = {}
88 self._realm_name = "Test Realm"
89 self._qop = "auth"
90
91 def set_qop(self, qop):
92 self._qop = qop
93
94 def set_users(self, users):
95 assert isinstance(users, dict)
96 self._users = users
97
98 def set_realm(self, realm):
99 self._realm_name = realm
100
101 def _generate_nonce(self):
102 self._request_num += 1
Guido van Rossum81360142007-08-29 14:26:52 +0000103 nonce = hashlib.md5(str(self._request_num).encode("ascii")).hexdigest()
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000104 self._nonces.append(nonce)
105 return nonce
106
107 def _create_auth_dict(self, auth_str):
108 first_space_index = auth_str.find(" ")
109 auth_str = auth_str[first_space_index+1:]
110
111 parts = auth_str.split(",")
112
113 auth_dict = {}
114 for part in parts:
115 name, value = part.split("=")
116 name = name.strip()
117 if value[0] == '"' and value[-1] == '"':
118 value = value[1:-1]
119 else:
120 value = value.strip()
121 auth_dict[name] = value
122 return auth_dict
123
124 def _validate_auth(self, auth_dict, password, method, uri):
125 final_dict = {}
126 final_dict.update(auth_dict)
127 final_dict["password"] = password
128 final_dict["method"] = method
129 final_dict["uri"] = uri
130 HA1_str = "%(username)s:%(realm)s:%(password)s" % final_dict
Guido van Rossum81360142007-08-29 14:26:52 +0000131 HA1 = hashlib.md5(HA1_str.encode("ascii")).hexdigest()
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000132 HA2_str = "%(method)s:%(uri)s" % final_dict
Guido van Rossum81360142007-08-29 14:26:52 +0000133 HA2 = hashlib.md5(HA2_str.encode("ascii")).hexdigest()
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000134 final_dict["HA1"] = HA1
135 final_dict["HA2"] = HA2
136 response_str = "%(HA1)s:%(nonce)s:%(nc)s:" \
137 "%(cnonce)s:%(qop)s:%(HA2)s" % final_dict
Guido van Rossum81360142007-08-29 14:26:52 +0000138 response = hashlib.md5(response_str.encode("ascii")).hexdigest()
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000139
140 return response == auth_dict["response"]
141
142 def _return_auth_challenge(self, request_handler):
143 request_handler.send_response(407, "Proxy Authentication Required")
144 request_handler.send_header("Content-Type", "text/html")
145 request_handler.send_header(
146 'Proxy-Authenticate', 'Digest realm="%s", '
147 'qop="%s",'
148 'nonce="%s", ' % \
149 (self._realm_name, self._qop, self._generate_nonce()))
150 # XXX: Not sure if we're supposed to add this next header or
151 # not.
152 #request_handler.send_header('Connection', 'close')
153 request_handler.end_headers()
Guido van Rossum8a392d72007-11-21 22:09:45 +0000154 request_handler.wfile.write(b"Proxy Authentication Required.")
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000155 return False
156
157 def handle_request(self, request_handler):
158 """Performs digest authentication on the given HTTP request
159 handler. Returns True if authentication was successful, False
160 otherwise.
161
162 If no users have been set, then digest auth is effectively
163 disabled and this method will always return True.
164 """
165
166 if len(self._users) == 0:
167 return True
168
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000169 if "Proxy-Authorization" not in request_handler.headers:
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000170 return self._return_auth_challenge(request_handler)
171 else:
172 auth_dict = self._create_auth_dict(
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000173 request_handler.headers["Proxy-Authorization"]
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000174 )
175 if auth_dict["username"] in self._users:
176 password = self._users[ auth_dict["username"] ]
177 else:
178 return self._return_auth_challenge(request_handler)
179 if not auth_dict.get("nonce") in self._nonces:
180 return self._return_auth_challenge(request_handler)
181 else:
182 self._nonces.remove(auth_dict["nonce"])
183
184 auth_validated = False
185
186 # MSIE uses short_path in its validation, but Python's
Florent Xicluna419e3842010-08-08 16:16:07 +0000187 # urllib.request uses the full path, so we're going to see if
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000188 # either of them works here.
189
190 for path in [request_handler.path, request_handler.short_path]:
191 if self._validate_auth(auth_dict,
192 password,
193 request_handler.command,
194 path):
195 auth_validated = True
196
197 if not auth_validated:
198 return self._return_auth_challenge(request_handler)
199 return True
200
Senthil Kumaran78373762014-08-20 07:53:58 +0530201
202class BasicAuthHandler(http.server.BaseHTTPRequestHandler):
203 """Handler for performing basic authentication."""
204 # Server side values
205 USER = 'testUser'
206 PASSWD = 'testPass'
207 REALM = 'Test'
208 USER_PASSWD = "%s:%s" % (USER, PASSWD)
209 ENCODED_AUTH = base64.b64encode(USER_PASSWD.encode('ascii')).decode('ascii')
210
211 def __init__(self, *args, **kwargs):
212 http.server.BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
213
214 def log_message(self, format, *args):
215 # Suppress console log message
216 pass
217
218 def do_HEAD(self):
219 self.send_response(200)
220 self.send_header("Content-type", "text/html")
221 self.end_headers()
222
223 def do_AUTHHEAD(self):
224 self.send_response(401)
225 self.send_header("WWW-Authenticate", "Basic realm=\"%s\"" % self.REALM)
226 self.send_header("Content-type", "text/html")
227 self.end_headers()
228
229 def do_GET(self):
230 if not self.headers.get("Authorization", ""):
231 self.do_AUTHHEAD()
232 self.wfile.write(b"No Auth header received")
233 elif self.headers.get(
234 "Authorization", "") == "Basic " + self.ENCODED_AUTH:
235 self.send_response(200)
236 self.end_headers()
237 self.wfile.write(b"It works")
238 else:
239 # Request Unauthorized
240 self.do_AUTHHEAD()
Senthil Kumaran78373762014-08-20 07:53:58 +0530241
242
243
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000244# Proxy test infrastructure
245
Georg Brandl24420152008-05-26 16:32:26 +0000246class FakeProxyHandler(http.server.BaseHTTPRequestHandler):
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000247 """This is a 'fake proxy' that makes it look like the entire
248 internet has gone down due to a sudden zombie invasion. It main
249 utility is in providing us with authentication support for
250 testing.
251 """
252
Collin Winter9a4414d2009-05-18 22:32:26 +0000253 def __init__(self, digest_auth_handler, *args, **kwargs):
254 # This has to be set before calling our parent's __init__(), which will
255 # try to call do_GET().
256 self.digest_auth_handler = digest_auth_handler
257 http.server.BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000258
259 def log_message(self, format, *args):
260 # Uncomment the next line for debugging.
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000261 # sys.stderr.write(format % args)
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000262 pass
263
264 def do_GET(self):
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000265 (scm, netloc, path, params, query, fragment) = urllib.parse.urlparse(
266 self.path, "http")
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000267 self.short_path = path
268 if self.digest_auth_handler.handle_request(self):
269 self.send_response(200, "OK")
270 self.send_header("Content-Type", "text/html")
271 self.end_headers()
Guido van Rossum8a392d72007-11-21 22:09:45 +0000272 self.wfile.write(bytes("You've reached %s!<BR>" % self.path,
273 "ascii"))
274 self.wfile.write(b"Our apologies, but our server is down due to "
275 b"a sudden zombie invasion.")
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000276
277# Test cases
278
Senthil Kumarand943fde2014-04-15 16:36:43 -0400279@unittest.skipUnless(threading, "Threading required for this test.")
Senthil Kumaran78373762014-08-20 07:53:58 +0530280class BasicAuthTests(unittest.TestCase):
281 USER = "testUser"
282 PASSWD = "testPass"
283 INCORRECT_PASSWD = "Incorrect"
284 REALM = "Test"
285
286 def setUp(self):
287 super(BasicAuthTests, self).setUp()
288 # With Basic Authentication
289 def http_server_with_basic_auth_handler(*args, **kwargs):
290 return BasicAuthHandler(*args, **kwargs)
291 self.server = LoopbackHttpServerThread(http_server_with_basic_auth_handler)
292 self.server_url = 'http://127.0.0.1:%s' % self.server.port
293 self.server.start()
294 self.server.ready.wait()
295
296 def tearDown(self):
297 self.server.stop()
298 super(BasicAuthTests, self).tearDown()
299
300 def test_basic_auth_success(self):
301 ah = urllib.request.HTTPBasicAuthHandler()
302 ah.add_password(self.REALM, self.server_url, self.USER, self.PASSWD)
303 urllib.request.install_opener(urllib.request.build_opener(ah))
304 try:
305 self.assertTrue(urllib.request.urlopen(self.server_url))
306 except urllib.error.HTTPError:
307 self.fail("Basic auth failed for the url: %s", self.server_url)
308
309 def test_basic_auth_httperror(self):
310 ah = urllib.request.HTTPBasicAuthHandler()
311 ah.add_password(self.REALM, self.server_url, self.USER, self.INCORRECT_PASSWD)
312 urllib.request.install_opener(urllib.request.build_opener(ah))
313 self.assertRaises(urllib.error.HTTPError, urllib.request.urlopen, self.server_url)
314
315
316@unittest.skipUnless(threading, "Threading required for this test.")
Antoine Pitrou803e6d62010-10-13 10:36:15 +0000317class ProxyAuthTests(unittest.TestCase):
Christian Heimesbbe741d2008-03-28 10:53:29 +0000318 URL = "http://localhost"
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000319
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000320 USER = "tester"
321 PASSWD = "test123"
322 REALM = "TestRealm"
323
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000324 def setUp(self):
Florent Xicluna9b86b9a2010-03-19 19:00:44 +0000325 super(ProxyAuthTests, self).setUp()
Collin Winter9a4414d2009-05-18 22:32:26 +0000326 self.digest_auth_handler = DigestAuthHandler()
327 self.digest_auth_handler.set_users({self.USER: self.PASSWD})
328 self.digest_auth_handler.set_realm(self.REALM)
Senthil Kumaran78373762014-08-20 07:53:58 +0530329 # With Digest Authentication.
Collin Winter9a4414d2009-05-18 22:32:26 +0000330 def create_fake_proxy_handler(*args, **kwargs):
331 return FakeProxyHandler(self.digest_auth_handler, *args, **kwargs)
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000332
Collin Winter9a4414d2009-05-18 22:32:26 +0000333 self.server = LoopbackHttpServerThread(create_fake_proxy_handler)
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000334 self.server.start()
335 self.server.ready.wait()
Guido van Rossum806c2462007-08-06 23:33:07 +0000336 proxy_url = "http://127.0.0.1:%d" % self.server.port
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000337 handler = urllib.request.ProxyHandler({"http" : proxy_url})
Collin Winter9a4414d2009-05-18 22:32:26 +0000338 self.proxy_digest_handler = urllib.request.ProxyDigestAuthHandler()
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000339 self.opener = urllib.request.build_opener(
Collin Winter9a4414d2009-05-18 22:32:26 +0000340 handler, self.proxy_digest_handler)
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000341
342 def tearDown(self):
343 self.server.stop()
Florent Xicluna9b86b9a2010-03-19 19:00:44 +0000344 super(ProxyAuthTests, self).tearDown()
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000345
346 def test_proxy_with_bad_password_raises_httperror(self):
Collin Winter9a4414d2009-05-18 22:32:26 +0000347 self.proxy_digest_handler.add_password(self.REALM, self.URL,
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000348 self.USER, self.PASSWD+"bad")
Collin Winter9a4414d2009-05-18 22:32:26 +0000349 self.digest_auth_handler.set_qop("auth")
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000350 self.assertRaises(urllib.error.HTTPError,
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000351 self.opener.open,
352 self.URL)
353
354 def test_proxy_with_no_password_raises_httperror(self):
Collin Winter9a4414d2009-05-18 22:32:26 +0000355 self.digest_auth_handler.set_qop("auth")
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000356 self.assertRaises(urllib.error.HTTPError,
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000357 self.opener.open,
358 self.URL)
359
360 def test_proxy_qop_auth_works(self):
Collin Winter9a4414d2009-05-18 22:32:26 +0000361 self.proxy_digest_handler.add_password(self.REALM, self.URL,
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000362 self.USER, self.PASSWD)
Collin Winter9a4414d2009-05-18 22:32:26 +0000363 self.digest_auth_handler.set_qop("auth")
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000364 result = self.opener.open(self.URL)
365 while result.read():
366 pass
367 result.close()
368
369 def test_proxy_qop_auth_int_works_or_throws_urlerror(self):
Collin Winter9a4414d2009-05-18 22:32:26 +0000370 self.proxy_digest_handler.add_password(self.REALM, self.URL,
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000371 self.USER, self.PASSWD)
Collin Winter9a4414d2009-05-18 22:32:26 +0000372 self.digest_auth_handler.set_qop("auth-int")
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000373 try:
374 result = self.opener.open(self.URL)
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000375 except urllib.error.URLError:
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000376 # It's okay if we don't support auth-int, but we certainly
377 # shouldn't receive any kind of exception here other than
378 # a URLError.
379 result = None
380 if result:
381 while result.read():
382 pass
383 result.close()
384
Christian Heimesbbe741d2008-03-28 10:53:29 +0000385
386def GetRequestHandler(responses):
387
Georg Brandl24420152008-05-26 16:32:26 +0000388 class FakeHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
Christian Heimesbbe741d2008-03-28 10:53:29 +0000389
390 server_version = "TestHTTP/"
391 requests = []
392 headers_received = []
393 port = 80
394
395 def do_GET(self):
396 body = self.send_head()
Florent Xicluna37d3d9a2010-08-08 16:25:27 +0000397 while body:
398 done = self.wfile.write(body)
399 body = body[done:]
Christian Heimesbbe741d2008-03-28 10:53:29 +0000400
401 def do_POST(self):
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000402 content_length = self.headers["Content-Length"]
Christian Heimesbbe741d2008-03-28 10:53:29 +0000403 post_data = self.rfile.read(int(content_length))
404 self.do_GET()
405 self.requests.append(post_data)
406
407 def send_head(self):
408 FakeHTTPRequestHandler.headers_received = self.headers
409 self.requests.append(self.path)
410 response_code, headers, body = responses.pop(0)
411
412 self.send_response(response_code)
413
414 for (header, value) in headers:
Antoine Pitroub353c122009-02-11 00:39:14 +0000415 self.send_header(header, value % {'port':self.port})
Christian Heimesbbe741d2008-03-28 10:53:29 +0000416 if body:
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000417 self.send_header("Content-type", "text/plain")
Christian Heimesbbe741d2008-03-28 10:53:29 +0000418 self.end_headers()
419 return body
420 self.end_headers()
421
422 def log_message(self, *args):
423 pass
424
425
426 return FakeHTTPRequestHandler
427
428
Senthil Kumarand943fde2014-04-15 16:36:43 -0400429@unittest.skipUnless(threading, "Threading required for this test.")
Antoine Pitrou803e6d62010-10-13 10:36:15 +0000430class TestUrlopen(unittest.TestCase):
Florent Xicluna419e3842010-08-08 16:16:07 +0000431 """Tests urllib.request.urlopen using the network.
Christian Heimesbbe741d2008-03-28 10:53:29 +0000432
433 These tests are not exhaustive. Assuming that testing using files does a
434 good job overall of some of the basic interface features. There are no
435 tests exercising the optional 'data' and 'proxies' arguments. No tests
436 for transparent redirection have been written.
437 """
438
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000439 def setUp(self):
Florent Xicluna9b86b9a2010-03-19 19:00:44 +0000440 super(TestUrlopen, self).setUp()
Senthil Kumaran303eb472012-12-26 01:45:58 -0800441 # Ignore proxies for localhost tests.
Antoine Pitrouda232592013-02-05 21:20:51 +0100442 self.old_environ = os.environ.copy()
Senthil Kumaran303eb472012-12-26 01:45:58 -0800443 os.environ['NO_PROXY'] = '*'
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000444 self.server = None
445
446 def tearDown(self):
447 if self.server is not None:
448 self.server.stop()
Antoine Pitrouda232592013-02-05 21:20:51 +0100449 os.environ.clear()
450 os.environ.update(self.old_environ)
Florent Xicluna9b86b9a2010-03-19 19:00:44 +0000451 super(TestUrlopen, self).tearDown()
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000452
Antoine Pitrou803e6d62010-10-13 10:36:15 +0000453 def urlopen(self, url, data=None, **kwargs):
Antoine Pitroub353c122009-02-11 00:39:14 +0000454 l = []
Antoine Pitrou803e6d62010-10-13 10:36:15 +0000455 f = urllib.request.urlopen(url, data, **kwargs)
Antoine Pitroub353c122009-02-11 00:39:14 +0000456 try:
457 # Exercise various methods
458 l.extend(f.readlines(200))
459 l.append(f.readline())
460 l.append(f.read(1024))
461 l.append(f.read())
462 finally:
463 f.close()
464 return b"".join(l)
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000465
466 def start_server(self, responses=None):
467 if responses is None:
468 responses = [(200, [], b"we don't care")]
Christian Heimesbbe741d2008-03-28 10:53:29 +0000469 handler = GetRequestHandler(responses)
470
471 self.server = LoopbackHttpServerThread(handler)
472 self.server.start()
473 self.server.ready.wait()
474 port = self.server.port
475 handler.port = port
476 return handler
477
Antoine Pitrouda232592013-02-05 21:20:51 +0100478 def start_https_server(self, responses=None, **kwargs):
Antoine Pitrou803e6d62010-10-13 10:36:15 +0000479 if not hasattr(urllib.request, 'HTTPSHandler'):
480 self.skipTest('ssl support required')
481 from test.ssl_servers import make_https_server
482 if responses is None:
483 responses = [(200, [], b"we care a bit")]
484 handler = GetRequestHandler(responses)
Antoine Pitrouda232592013-02-05 21:20:51 +0100485 server = make_https_server(self, handler_class=handler, **kwargs)
Antoine Pitrou803e6d62010-10-13 10:36:15 +0000486 handler.port = server.port
487 return handler
488
Christian Heimesbbe741d2008-03-28 10:53:29 +0000489 def test_redirection(self):
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000490 expected_response = b"We got here..."
Christian Heimesbbe741d2008-03-28 10:53:29 +0000491 responses = [
Antoine Pitroub353c122009-02-11 00:39:14 +0000492 (302, [("Location", "http://localhost:%(port)s/somewhere_else")],
493 ""),
Christian Heimesbbe741d2008-03-28 10:53:29 +0000494 (200, [], expected_response)
495 ]
496
497 handler = self.start_server(responses)
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000498 data = self.urlopen("http://localhost:%s/" % handler.port)
Florent Xicluna419e3842010-08-08 16:16:07 +0000499 self.assertEqual(data, expected_response)
500 self.assertEqual(handler.requests, ["/", "/somewhere_else"])
Christian Heimesbbe741d2008-03-28 10:53:29 +0000501
Antoine Pitroub353c122009-02-11 00:39:14 +0000502 def test_chunked(self):
503 expected_response = b"hello world"
504 chunked_start = (
505 b'a\r\n'
506 b'hello worl\r\n'
507 b'1\r\n'
508 b'd\r\n'
509 b'0\r\n'
510 )
511 response = [(200, [("Transfer-Encoding", "chunked")], chunked_start)]
512 handler = self.start_server(response)
513 data = self.urlopen("http://localhost:%s/" % handler.port)
Florent Xicluna419e3842010-08-08 16:16:07 +0000514 self.assertEqual(data, expected_response)
Antoine Pitroub353c122009-02-11 00:39:14 +0000515
Christian Heimesbbe741d2008-03-28 10:53:29 +0000516 def test_404(self):
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000517 expected_response = b"Bad bad bad..."
Christian Heimesbbe741d2008-03-28 10:53:29 +0000518 handler = self.start_server([(404, [], expected_response)])
519
520 try:
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000521 self.urlopen("http://localhost:%s/weeble" % handler.port)
522 except urllib.error.URLError as f:
523 data = f.read()
524 f.close()
525 else:
526 self.fail("404 should raise URLError")
Christian Heimesbbe741d2008-03-28 10:53:29 +0000527
Florent Xicluna419e3842010-08-08 16:16:07 +0000528 self.assertEqual(data, expected_response)
529 self.assertEqual(handler.requests, ["/weeble"])
Christian Heimesbbe741d2008-03-28 10:53:29 +0000530
531 def test_200(self):
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000532 expected_response = b"pycon 2008..."
Christian Heimesbbe741d2008-03-28 10:53:29 +0000533 handler = self.start_server([(200, [], expected_response)])
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000534 data = self.urlopen("http://localhost:%s/bizarre" % handler.port)
Florent Xicluna419e3842010-08-08 16:16:07 +0000535 self.assertEqual(data, expected_response)
536 self.assertEqual(handler.requests, ["/bizarre"])
Christian Heimesbbe741d2008-03-28 10:53:29 +0000537
538 def test_200_with_parameters(self):
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000539 expected_response = b"pycon 2008..."
Christian Heimesbbe741d2008-03-28 10:53:29 +0000540 handler = self.start_server([(200, [], expected_response)])
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000541 data = self.urlopen("http://localhost:%s/bizarre" % handler.port,
542 b"get=with_feeling")
Florent Xicluna419e3842010-08-08 16:16:07 +0000543 self.assertEqual(data, expected_response)
544 self.assertEqual(handler.requests, ["/bizarre", b"get=with_feeling"])
Christian Heimesbbe741d2008-03-28 10:53:29 +0000545
Antoine Pitrou803e6d62010-10-13 10:36:15 +0000546 def test_https(self):
547 handler = self.start_https_server()
Benjamin Peterson4ffb0752014-11-03 14:29:33 -0500548 context = ssl.create_default_context(cafile=CERT_localhost)
549 data = self.urlopen("https://localhost:%s/bizarre" % handler.port, context=context)
Antoine Pitrou803e6d62010-10-13 10:36:15 +0000550 self.assertEqual(data, b"we care a bit")
551
552 def test_https_with_cafile(self):
553 handler = self.start_https_server(certfile=CERT_localhost)
Antoine Pitrou803e6d62010-10-13 10:36:15 +0000554 # Good cert
555 data = self.urlopen("https://localhost:%s/bizarre" % handler.port,
556 cafile=CERT_localhost)
557 self.assertEqual(data, b"we care a bit")
558 # Bad cert
559 with self.assertRaises(urllib.error.URLError) as cm:
560 self.urlopen("https://localhost:%s/bizarre" % handler.port,
561 cafile=CERT_fakehostname)
562 # Good cert, but mismatching hostname
563 handler = self.start_https_server(certfile=CERT_fakehostname)
564 with self.assertRaises(ssl.CertificateError) as cm:
565 self.urlopen("https://localhost:%s/bizarre" % handler.port,
566 cafile=CERT_fakehostname)
567
Antoine Pitroude9ac6c2012-05-16 21:40:01 +0200568 def test_https_with_cadefault(self):
569 handler = self.start_https_server(certfile=CERT_localhost)
570 # Self-signed cert should fail verification with system certificate store
571 with self.assertRaises(urllib.error.URLError) as cm:
572 self.urlopen("https://localhost:%s/bizarre" % handler.port,
573 cadefault=True)
574
Antoine Pitrouda232592013-02-05 21:20:51 +0100575 def test_https_sni(self):
576 if ssl is None:
577 self.skipTest("ssl module required")
578 if not ssl.HAS_SNI:
579 self.skipTest("SNI support required in OpenSSL")
580 sni_name = None
581 def cb_sni(ssl_sock, server_name, initial_context):
582 nonlocal sni_name
583 sni_name = server_name
584 context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
585 context.set_servername_callback(cb_sni)
586 handler = self.start_https_server(context=context, certfile=CERT_localhost)
Benjamin Peterson4ffb0752014-11-03 14:29:33 -0500587 context = ssl.create_default_context(cafile=CERT_localhost)
588 self.urlopen("https://localhost:%s" % handler.port, context=context)
Antoine Pitrouda232592013-02-05 21:20:51 +0100589 self.assertEqual(sni_name, "localhost")
590
Christian Heimesbbe741d2008-03-28 10:53:29 +0000591 def test_sending_headers(self):
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000592 handler = self.start_server()
593 req = urllib.request.Request("http://localhost:%s/" % handler.port,
594 headers={"Range": "bytes=20-39"})
595 urllib.request.urlopen(req)
596 self.assertEqual(handler.headers_received["Range"], "bytes=20-39")
Christian Heimesbbe741d2008-03-28 10:53:29 +0000597
598 def test_basic(self):
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000599 handler = self.start_server()
600 open_url = urllib.request.urlopen("http://localhost:%s" % handler.port)
601 for attr in ("read", "close", "info", "geturl"):
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000602 self.assertTrue(hasattr(open_url, attr), "object returned from "
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000603 "urlopen lacks the %s attribute" % attr)
Christian Heimesbbe741d2008-03-28 10:53:29 +0000604 try:
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000605 self.assertTrue(open_url.read(), "calling 'read' failed")
Christian Heimesbbe741d2008-03-28 10:53:29 +0000606 finally:
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000607 open_url.close()
Christian Heimesbbe741d2008-03-28 10:53:29 +0000608
609 def test_info(self):
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000610 handler = self.start_server()
Christian Heimesbbe741d2008-03-28 10:53:29 +0000611 try:
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000612 open_url = urllib.request.urlopen(
613 "http://localhost:%s" % handler.port)
Christian Heimesbbe741d2008-03-28 10:53:29 +0000614 info_obj = open_url.info()
Ezio Melottie9615932010-01-24 19:26:24 +0000615 self.assertIsInstance(info_obj, email.message.Message,
616 "object returned by 'info' is not an "
617 "instance of email.message.Message")
Barry Warsaw820c1202008-06-12 04:06:45 +0000618 self.assertEqual(info_obj.get_content_subtype(), "plain")
Christian Heimesbbe741d2008-03-28 10:53:29 +0000619 finally:
620 self.server.stop()
621
622 def test_geturl(self):
623 # Make sure same URL as opened is returned by geturl.
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000624 handler = self.start_server()
625 open_url = urllib.request.urlopen("http://localhost:%s" % handler.port)
626 url = open_url.geturl()
627 self.assertEqual(url, "http://localhost:%s" % handler.port)
Christian Heimesbbe741d2008-03-28 10:53:29 +0000628
629 def test_bad_address(self):
630 # Make sure proper exception is raised when connecting to a bogus
631 # address.
Ezio Melotti90984722013-03-30 01:28:40 +0200632
633 # as indicated by the comment below, this might fail with some ISP,
634 # so we run the test only when -unetwork/-uall is specified to
635 # mitigate the problem a bit (see #17564)
636 support.requires('network')
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200637 self.assertRaises(OSError,
R. David Murray8da3cac2009-09-29 14:01:08 +0000638 # Given that both VeriSign and various ISPs have in
639 # the past or are presently hijacking various invalid
640 # domain name requests in an attempt to boost traffic
641 # to their own sites, finding a domain name to use
642 # for this test is difficult. RFC2606 leads one to
643 # believe that '.invalid' should work, but experience
644 # seemed to indicate otherwise. Single character
645 # TLDs are likely to remain invalid, so this seems to
646 # be the best choice. The trailing '.' prevents a
647 # related problem: The normal DNS resolver appends
648 # the domain names from the search path if there is
649 # no '.' the end and, and if one of those domains
650 # implements a '*' rule a result is returned.
651 # However, none of this will prevent the test from
652 # failing if the ISP hijacks all invalid domain
653 # requests. The real solution would be to be able to
654 # parameterize the framework with a mock resolver.
Jeremy Hylton1afc1692008-06-18 20:49:58 +0000655 urllib.request.urlopen,
R. David Murray8da3cac2009-09-29 14:01:08 +0000656 "http://sadflkjsasf.i.nvali.d./")
Christian Heimesbbe741d2008-03-28 10:53:29 +0000657
Florent Xicluna37d3d9a2010-08-08 16:25:27 +0000658 def test_iteration(self):
659 expected_response = b"pycon 2008..."
660 handler = self.start_server([(200, [], expected_response)])
661 data = urllib.request.urlopen("http://localhost:%s" % handler.port)
662 for line in data:
663 self.assertEqual(line, expected_response)
664
665 def test_line_iteration(self):
666 lines = [b"We\n", b"got\n", b"here\n", b"verylong " * 8192 + b"\n"]
667 expected_response = b"".join(lines)
668 handler = self.start_server([(200, [], expected_response)])
669 data = urllib.request.urlopen("http://localhost:%s" % handler.port)
670 for index, line in enumerate(data):
671 self.assertEqual(line, lines[index],
672 "Fetched line number %s doesn't match expected:\n"
673 " Expected length was %s, got %s" %
674 (index, len(lines[index]), len(line)))
675 self.assertEqual(index + 1, len(lines))
676
Antoine Pitrou803e6d62010-10-13 10:36:15 +0000677
Senthil Kumarand943fde2014-04-15 16:36:43 -0400678threads_key = None
679
680def setUpModule():
681 # Store the threading_setup in a key and ensure that it is cleaned up
682 # in the tearDown
683 global threads_key
684 threads_key = support.threading_setup()
685
686def tearDownModule():
687 if threads_key:
688 support.threading_cleanup(threads_key)
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000689
690if __name__ == "__main__":
Senthil Kumarand943fde2014-04-15 16:36:43 -0400691 unittest.main()