blob: c2d594b400316b7678363404b8fbbbdedd8dd968 [file] [log] [blame]
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +00001import unittest
Benjamin Petersonee8712c2008-05-20 21:35:26 +00002from test import support
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +00003
Christian Heimes05e8be12008-02-23 18:30:17 +00004import os
Guido van Rossum34d19282007-08-09 01:03:29 +00005import io
Georg Brandlf78e02b2008-06-10 17:40:04 +00006import socket
Jeremy Hyltone3e61042001-05-09 15:50:25 +00007
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +00008import urllib2
9from urllib2 import Request, OpenerDirector
Jeremy Hyltone3e61042001-05-09 15:50:25 +000010
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +000011# XXX
12# Request
13# CacheFTPHandler (hard to write)
Thomas Wouters477c8d52006-05-27 19:21:47 +000014# parse_keqv_list, parse_http_list, HTTPDigestAuthHandler
Jeremy Hyltone3e61042001-05-09 15:50:25 +000015
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +000016class TrivialTests(unittest.TestCase):
17 def test_trivial(self):
18 # A couple trivial tests
Guido van Rossume2ae77b2001-10-24 20:42:55 +000019
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +000020 self.assertRaises(ValueError, urllib2.urlopen, 'bogus url')
Tim Peters861adac2001-07-16 20:49:49 +000021
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +000022 # XXX Name hacking to get this to work on Windows.
23 fname = os.path.abspath(urllib2.__file__).replace('\\', '/')
24 if fname[1:2] == ":":
25 fname = fname[2:]
26 # And more hacking to get it to work on MacOS. This assumes
27 # urllib.pathname2url works, unfortunately...
28 if os.name == 'mac':
29 fname = '/' + fname.replace(':', '/')
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +000030
31 file_url = "file://%s" % fname
32 f = urllib2.urlopen(file_url)
33
34 buf = f.read()
35 f.close()
Tim Petersf5f32b42005-07-17 23:16:17 +000036
Georg Brandle1b13d22005-08-24 22:20:32 +000037 def test_parse_http_list(self):
38 tests = [('a,b,c', ['a', 'b', 'c']),
39 ('path"o,l"og"i"cal, example', ['path"o,l"og"i"cal', 'example']),
40 ('a, b, "c", "d", "e,f", g, h', ['a', 'b', '"c"', '"d"', '"e,f"', 'g', 'h']),
41 ('a="b\\"c", d="e\\,f", g="h\\\\i"', ['a="b"c"', 'd="e,f"', 'g="h\\i"'])]
42 for string, list in tests:
43 self.assertEquals(urllib2.parse_http_list(string), list)
44
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +000045
Thomas Wouters00ee7ba2006-08-21 19:07:27 +000046def test_request_headers_dict():
47 """
48 The Request.headers dictionary is not a documented interface. It should
49 stay that way, because the complete set of headers are only accessible
50 through the .get_header(), .has_header(), .header_items() interface.
51 However, .headers pre-dates those methods, and so real code will be using
52 the dictionary.
53
54 The introduction in 2.4 of those methods was a mistake for the same reason:
55 code that previously saw all (urllib2 user)-provided headers in .headers
56 now sees only a subset (and the function interface is ugly and incomplete).
57 A better change would have been to replace .headers dict with a dict
58 subclass (or UserDict.DictMixin instance?) that preserved the .headers
59 interface and also provided access to the "unredirected" headers. It's
60 probably too late to fix that, though.
61
62
63 Check .capitalize() case normalization:
64
65 >>> url = "http://example.com"
66 >>> Request(url, headers={"Spam-eggs": "blah"}).headers["Spam-eggs"]
67 'blah'
68 >>> Request(url, headers={"spam-EggS": "blah"}).headers["Spam-eggs"]
69 'blah'
70
71 Currently, Request(url, "Spam-eggs").headers["Spam-Eggs"] raises KeyError,
72 but that could be changed in future.
73
74 """
75
76def test_request_headers_methods():
77 """
78 Note the case normalization of header names here, to .capitalize()-case.
79 This should be preserved for backwards-compatibility. (In the HTTP case,
80 normalization to .title()-case is done by urllib2 before sending headers to
Georg Brandl24420152008-05-26 16:32:26 +000081 http.client).
Thomas Wouters00ee7ba2006-08-21 19:07:27 +000082
83 >>> url = "http://example.com"
84 >>> r = Request(url, headers={"Spam-eggs": "blah"})
85 >>> r.has_header("Spam-eggs")
86 True
87 >>> r.header_items()
88 [('Spam-eggs', 'blah')]
89 >>> r.add_header("Foo-Bar", "baz")
Guido van Rossumcc2b0162007-02-11 06:12:03 +000090 >>> items = sorted(r.header_items())
Thomas Wouters00ee7ba2006-08-21 19:07:27 +000091 >>> items
92 [('Foo-bar', 'baz'), ('Spam-eggs', 'blah')]
93
94 Note that e.g. r.has_header("spam-EggS") is currently False, and
95 r.get_header("spam-EggS") returns None, but that could be changed in
96 future.
97
98 >>> r.has_header("Not-there")
99 False
Guido van Rossum7131f842007-02-09 20:13:25 +0000100 >>> print(r.get_header("Not-there"))
Thomas Wouters00ee7ba2006-08-21 19:07:27 +0000101 None
102 >>> r.get_header("Not-there", "default")
103 'default'
104
105 """
106
107
Thomas Wouters477c8d52006-05-27 19:21:47 +0000108def test_password_manager(self):
109 """
110 >>> mgr = urllib2.HTTPPasswordMgr()
111 >>> add = mgr.add_password
112 >>> add("Some Realm", "http://example.com/", "joe", "password")
113 >>> add("Some Realm", "http://example.com/ni", "ni", "ni")
114 >>> add("c", "http://example.com/foo", "foo", "ni")
115 >>> add("c", "http://example.com/bar", "bar", "nini")
116 >>> add("b", "http://example.com/", "first", "blah")
117 >>> add("b", "http://example.com/", "second", "spam")
118 >>> add("a", "http://example.com", "1", "a")
119 >>> add("Some Realm", "http://c.example.com:3128", "3", "c")
120 >>> add("Some Realm", "d.example.com", "4", "d")
121 >>> add("Some Realm", "e.example.com:3128", "5", "e")
122
123 >>> mgr.find_user_password("Some Realm", "example.com")
124 ('joe', 'password')
125 >>> mgr.find_user_password("Some Realm", "http://example.com")
126 ('joe', 'password')
127 >>> mgr.find_user_password("Some Realm", "http://example.com/")
128 ('joe', 'password')
129 >>> mgr.find_user_password("Some Realm", "http://example.com/spam")
130 ('joe', 'password')
131 >>> mgr.find_user_password("Some Realm", "http://example.com/spam/spam")
132 ('joe', 'password')
133 >>> mgr.find_user_password("c", "http://example.com/foo")
134 ('foo', 'ni')
135 >>> mgr.find_user_password("c", "http://example.com/bar")
136 ('bar', 'nini')
137
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000138 Actually, this is really undefined ATM
139## Currently, we use the highest-level path where more than one match:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000140
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000141## >>> mgr.find_user_password("Some Realm", "http://example.com/ni")
142## ('joe', 'password')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000143
144 Use latest add_password() in case of conflict:
145
146 >>> mgr.find_user_password("b", "http://example.com/")
147 ('second', 'spam')
148
149 No special relationship between a.example.com and example.com:
150
151 >>> mgr.find_user_password("a", "http://example.com/")
152 ('1', 'a')
153 >>> mgr.find_user_password("a", "http://a.example.com/")
154 (None, None)
155
156 Ports:
157
158 >>> mgr.find_user_password("Some Realm", "c.example.com")
159 (None, None)
160 >>> mgr.find_user_password("Some Realm", "c.example.com:3128")
161 ('3', 'c')
162 >>> mgr.find_user_password("Some Realm", "http://c.example.com:3128")
163 ('3', 'c')
164 >>> mgr.find_user_password("Some Realm", "d.example.com")
165 ('4', 'd')
166 >>> mgr.find_user_password("Some Realm", "e.example.com:3128")
167 ('5', 'e')
168
169 """
170 pass
171
172
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000173def test_password_manager_default_port(self):
174 """
175 >>> mgr = urllib2.HTTPPasswordMgr()
176 >>> add = mgr.add_password
177
178 The point to note here is that we can't guess the default port if there's
179 no scheme. This applies to both add_password and find_user_password.
180
181 >>> add("f", "http://g.example.com:80", "10", "j")
182 >>> add("g", "http://h.example.com", "11", "k")
183 >>> add("h", "i.example.com:80", "12", "l")
184 >>> add("i", "j.example.com", "13", "m")
185 >>> mgr.find_user_password("f", "g.example.com:100")
186 (None, None)
187 >>> mgr.find_user_password("f", "g.example.com:80")
188 ('10', 'j')
189 >>> mgr.find_user_password("f", "g.example.com")
190 (None, None)
191 >>> mgr.find_user_password("f", "http://g.example.com:100")
192 (None, None)
193 >>> mgr.find_user_password("f", "http://g.example.com:80")
194 ('10', 'j')
195 >>> mgr.find_user_password("f", "http://g.example.com")
196 ('10', 'j')
197 >>> mgr.find_user_password("g", "h.example.com")
198 ('11', 'k')
199 >>> mgr.find_user_password("g", "h.example.com:80")
200 ('11', 'k')
201 >>> mgr.find_user_password("g", "http://h.example.com:80")
202 ('11', 'k')
203 >>> mgr.find_user_password("h", "i.example.com")
204 (None, None)
205 >>> mgr.find_user_password("h", "i.example.com:80")
206 ('12', 'l')
207 >>> mgr.find_user_password("h", "http://i.example.com:80")
208 ('12', 'l')
209 >>> mgr.find_user_password("i", "j.example.com")
210 ('13', 'm')
211 >>> mgr.find_user_password("i", "j.example.com:80")
212 (None, None)
213 >>> mgr.find_user_password("i", "http://j.example.com")
214 ('13', 'm')
215 >>> mgr.find_user_password("i", "http://j.example.com:80")
216 (None, None)
217
218 """
219
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000220class MockOpener:
221 addheaders = []
222 def open(self, req, data=None):
223 self.req, self.data = req, data
224 def error(self, proto, *args):
225 self.proto, self.args = proto, args
226
227class MockFile:
228 def read(self, count=None): pass
229 def readline(self, count=None): pass
230 def close(self): pass
231
Martin v. Löwis2a6ba902004-05-31 18:22:40 +0000232class MockHeaders(dict):
233 def getheaders(self, name):
Guido van Rossumcc2b0162007-02-11 06:12:03 +0000234 return list(self.values())
Martin v. Löwis2a6ba902004-05-31 18:22:40 +0000235
Guido van Rossum34d19282007-08-09 01:03:29 +0000236class MockResponse(io.StringIO):
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000237 def __init__(self, code, msg, headers, data, url=None):
Guido van Rossum34d19282007-08-09 01:03:29 +0000238 io.StringIO.__init__(self, data)
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000239 self.code, self.msg, self.headers, self.url = code, msg, headers, url
240 def info(self):
241 return self.headers
242 def geturl(self):
243 return self.url
244
Martin v. Löwis2a6ba902004-05-31 18:22:40 +0000245class MockCookieJar:
246 def add_cookie_header(self, request):
247 self.ach_req = request
248 def extract_cookies(self, response, request):
249 self.ec_req, self.ec_r = request, response
250
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000251class FakeMethod:
252 def __init__(self, meth_name, action, handle):
253 self.meth_name = meth_name
254 self.handle = handle
255 self.action = action
256 def __call__(self, *args):
257 return self.handle(self.meth_name, self.action, *args)
258
259class MockHandler:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000260 # useful for testing handler machinery
261 # see add_ordered_mock_handlers() docstring
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000262 handler_order = 500
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000263 def __init__(self, methods):
264 self._define_methods(methods)
265 def _define_methods(self, methods):
266 for spec in methods:
267 if len(spec) == 2: name, action = spec
268 else: name, action = spec, None
269 meth = FakeMethod(name, action, self.handle)
270 setattr(self.__class__, name, meth)
271 def handle(self, fn_name, action, *args, **kwds):
272 self.parent.calls.append((self, fn_name, args, kwds))
273 if action is None:
274 return None
275 elif action == "return self":
276 return self
277 elif action == "return response":
278 res = MockResponse(200, "OK", {}, "")
279 return res
280 elif action == "return request":
281 return Request("http://blah/")
282 elif action.startswith("error"):
283 code = action[action.rfind(" ")+1:]
284 try:
285 code = int(code)
286 except ValueError:
287 pass
288 res = MockResponse(200, "OK", {}, "")
289 return self.parent.error("http", args[0], res, code, "", {})
290 elif action == "raise":
291 raise urllib2.URLError("blah")
292 assert False
293 def close(self): pass
294 def add_parent(self, parent):
295 self.parent = parent
296 self.parent.calls = []
297 def __lt__(self, other):
298 if not hasattr(other, "handler_order"):
299 # No handler_order, leave in original order. Yuck.
300 return True
301 return self.handler_order < other.handler_order
302
303def add_ordered_mock_handlers(opener, meth_spec):
304 """Create MockHandlers and add them to an OpenerDirector.
305
306 meth_spec: list of lists of tuples and strings defining methods to define
307 on handlers. eg:
308
309 [["http_error", "ftp_open"], ["http_open"]]
310
311 defines methods .http_error() and .ftp_open() on one handler, and
312 .http_open() on another. These methods just record their arguments and
313 return None. Using a tuple instead of a string causes the method to
314 perform some action (see MockHandler.handle()), eg:
315
316 [["http_error"], [("http_open", "return request")]]
317
318 defines .http_error() on one handler (which simply returns None), and
319 .http_open() on another handler, which returns a Request object.
320
321 """
322 handlers = []
323 count = 0
324 for meths in meth_spec:
325 class MockHandlerSubclass(MockHandler): pass
326 h = MockHandlerSubclass(meths)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000327 h.handler_order += count
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000328 h.add_parent(opener)
329 count = count + 1
330 handlers.append(h)
331 opener.add_handler(h)
332 return handlers
333
Thomas Wouters477c8d52006-05-27 19:21:47 +0000334def build_test_opener(*handler_instances):
335 opener = OpenerDirector()
336 for h in handler_instances:
337 opener.add_handler(h)
338 return opener
339
340class MockHTTPHandler(urllib2.BaseHandler):
341 # useful for testing redirections and auth
342 # sends supplied headers and code as first response
343 # sends 200 OK as second response
344 def __init__(self, code, headers):
345 self.code = code
346 self.headers = headers
347 self.reset()
348 def reset(self):
349 self._count = 0
350 self.requests = []
351 def http_open(self, req):
Georg Brandl24420152008-05-26 16:32:26 +0000352 import mimetools, http.client, copy
Guido van Rossum34d19282007-08-09 01:03:29 +0000353 from io import StringIO
Thomas Wouters477c8d52006-05-27 19:21:47 +0000354 self.requests.append(copy.deepcopy(req))
355 if self._count == 0:
356 self._count = self._count + 1
Georg Brandl24420152008-05-26 16:32:26 +0000357 name = http.client.responses[self.code]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000358 msg = mimetools.Message(StringIO(self.headers))
359 return self.parent.error(
360 "http", req, MockFile(), self.code, name, msg)
361 else:
362 self.req = req
363 msg = mimetools.Message(StringIO("\r\n\r\n"))
364 return MockResponse(200, "OK", msg, "", req.get_full_url())
365
366class MockPasswordManager:
367 def add_password(self, realm, uri, user, password):
368 self.realm = realm
369 self.url = uri
370 self.user = user
371 self.password = password
372 def find_user_password(self, realm, authuri):
373 self.target_realm = realm
374 self.target_url = authuri
375 return self.user, self.password
376
377
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000378class OpenerDirectorTests(unittest.TestCase):
379
Guido van Rossumb5a755e2007-07-18 18:15:48 +0000380 def test_add_non_handler(self):
381 class NonHandler(object):
382 pass
383 self.assertRaises(TypeError,
384 OpenerDirector().add_handler, NonHandler())
385
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000386 def test_badly_named_methods(self):
387 # test work-around for three methods that accidentally follow the
388 # naming conventions for handler methods
389 # (*_open() / *_request() / *_response())
390
391 # These used to call the accidentally-named methods, causing a
392 # TypeError in real code; here, returning self from these mock
393 # methods would either cause no exception, or AttributeError.
394
395 from urllib2 import URLError
396
397 o = OpenerDirector()
398 meth_spec = [
399 [("do_open", "return self"), ("proxy_open", "return self")],
400 [("redirect_request", "return self")],
401 ]
402 handlers = add_ordered_mock_handlers(o, meth_spec)
403 o.add_handler(urllib2.UnknownHandler())
404 for scheme in "do", "proxy", "redirect":
405 self.assertRaises(URLError, o.open, scheme+"://example.com/")
406
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000407 def test_handled(self):
408 # handler returning non-None means no more handlers will be called
409 o = OpenerDirector()
410 meth_spec = [
411 ["http_open", "ftp_open", "http_error_302"],
412 ["ftp_open"],
413 [("http_open", "return self")],
414 [("http_open", "return self")],
415 ]
416 handlers = add_ordered_mock_handlers(o, meth_spec)
417
418 req = Request("http://example.com/")
419 r = o.open(req)
420 # Second .http_open() gets called, third doesn't, since second returned
421 # non-None. Handlers without .http_open() never get any methods called
422 # on them.
423 # In fact, second mock handler defining .http_open() returns self
424 # (instead of response), which becomes the OpenerDirector's return
425 # value.
Jeremy Hyltondf38ea92003-12-17 20:42:38 +0000426 self.assertEqual(r, handlers[2])
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000427 calls = [(handlers[0], "http_open"), (handlers[2], "http_open")]
428 for expected, got in zip(calls, o.calls):
429 handler, name, args, kwds = got
Jeremy Hyltondf38ea92003-12-17 20:42:38 +0000430 self.assertEqual((handler, name), expected)
431 self.assertEqual(args, (req,))
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000432
433 def test_handler_order(self):
434 o = OpenerDirector()
435 handlers = []
436 for meths, handler_order in [
437 ([("http_open", "return self")], 500),
438 (["http_open"], 0),
439 ]:
440 class MockHandlerSubclass(MockHandler): pass
441 h = MockHandlerSubclass(meths)
442 h.handler_order = handler_order
443 handlers.append(h)
444 o.add_handler(h)
445
446 r = o.open("http://example.com/")
447 # handlers called in reverse order, thanks to their sort order
Jeremy Hyltondf38ea92003-12-17 20:42:38 +0000448 self.assertEqual(o.calls[0][0], handlers[1])
449 self.assertEqual(o.calls[1][0], handlers[0])
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000450
451 def test_raise(self):
452 # raising URLError stops processing of request
453 o = OpenerDirector()
454 meth_spec = [
455 [("http_open", "raise")],
456 [("http_open", "return self")],
457 ]
458 handlers = add_ordered_mock_handlers(o, meth_spec)
459
460 req = Request("http://example.com/")
461 self.assertRaises(urllib2.URLError, o.open, req)
Jeremy Hyltondf38ea92003-12-17 20:42:38 +0000462 self.assertEqual(o.calls, [(handlers[0], "http_open", (req,), {})])
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000463
464## def test_error(self):
465## # XXX this doesn't actually seem to be used in standard library,
466## # but should really be tested anyway...
467
468 def test_http_error(self):
469 # XXX http_error_default
470 # http errors are a special case
471 o = OpenerDirector()
472 meth_spec = [
473 [("http_open", "error 302")],
474 [("http_error_400", "raise"), "http_open"],
475 [("http_error_302", "return response"), "http_error_303",
476 "http_error"],
477 [("http_error_302")],
478 ]
479 handlers = add_ordered_mock_handlers(o, meth_spec)
480
481 class Unknown:
482 def __eq__(self, other): return True
483
484 req = Request("http://example.com/")
485 r = o.open(req)
486 assert len(o.calls) == 2
487 calls = [(handlers[0], "http_open", (req,)),
Jeremy Hyltondf38ea92003-12-17 20:42:38 +0000488 (handlers[2], "http_error_302",
489 (req, Unknown(), 302, "", {}))]
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000490 for expected, got in zip(calls, o.calls):
491 handler, method_name, args = expected
Jeremy Hyltondf38ea92003-12-17 20:42:38 +0000492 self.assertEqual((handler, method_name), got[:2])
493 self.assertEqual(args, got[2])
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000494
495 def test_processors(self):
496 # *_request / *_response methods get called appropriately
497 o = OpenerDirector()
498 meth_spec = [
499 [("http_request", "return request"),
500 ("http_response", "return response")],
501 [("http_request", "return request"),
502 ("http_response", "return response")],
503 ]
504 handlers = add_ordered_mock_handlers(o, meth_spec)
505
506 req = Request("http://example.com/")
507 r = o.open(req)
508 # processor methods are called on *all* handlers that define them,
509 # not just the first handler that handles the request
Jeremy Hyltondf38ea92003-12-17 20:42:38 +0000510 calls = [
511 (handlers[0], "http_request"), (handlers[1], "http_request"),
512 (handlers[0], "http_response"), (handlers[1], "http_response")]
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000513
514 for i, (handler, name, args, kwds) in enumerate(o.calls):
515 if i < 2:
516 # *_request
Jeremy Hyltondf38ea92003-12-17 20:42:38 +0000517 self.assertEqual((handler, name), calls[i])
518 self.assertEqual(len(args), 1)
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000519 self.assert_(isinstance(args[0], Request))
520 else:
521 # *_response
Jeremy Hyltondf38ea92003-12-17 20:42:38 +0000522 self.assertEqual((handler, name), calls[i])
523 self.assertEqual(len(args), 2)
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000524 self.assert_(isinstance(args[0], Request))
525 # response from opener.open is None, because there's no
526 # handler that defines http_open to handle it
527 self.assert_(args[1] is None or
528 isinstance(args[1], MockResponse))
529
530
Tim Peters58eb11c2004-01-18 20:29:55 +0000531def sanepathname2url(path):
532 import urllib
533 urlpath = urllib.pathname2url(path)
534 if os.name == "nt" and urlpath.startswith("///"):
535 urlpath = urlpath[2:]
536 # XXX don't ask me about the mac...
537 return urlpath
538
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000539class HandlerTests(unittest.TestCase):
540
541 def test_ftp(self):
542 class MockFTPWrapper:
543 def __init__(self, data): self.data = data
544 def retrfile(self, filename, filetype):
545 self.filename, self.filetype = filename, filetype
Guido van Rossum34d19282007-08-09 01:03:29 +0000546 return io.StringIO(self.data), len(self.data)
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000547
548 class NullFTPHandler(urllib2.FTPHandler):
549 def __init__(self, data): self.data = data
Georg Brandlf78e02b2008-06-10 17:40:04 +0000550 def connect_ftp(self, user, passwd, host, port, dirs,
551 timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000552 self.user, self.passwd = user, passwd
553 self.host, self.port = host, port
554 self.dirs = dirs
555 self.ftpwrapper = MockFTPWrapper(self.data)
556 return self.ftpwrapper
557
Georg Brandlf78e02b2008-06-10 17:40:04 +0000558 import ftplib
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000559 data = "rheum rhaponicum"
560 h = NullFTPHandler(data)
561 o = h.parent = MockOpener()
562
563 for url, host, port, type_, dirs, filename, mimetype in [
564 ("ftp://localhost/foo/bar/baz.html",
565 "localhost", ftplib.FTP_PORT, "I",
566 ["foo", "bar"], "baz.html", "text/html"),
Kurt B. Kaiser3f7cb5d2004-07-11 17:14:13 +0000567 ("ftp://localhost:80/foo/bar/",
568 "localhost", 80, "D",
569 ["foo", "bar"], "", None),
570 ("ftp://localhost/baz.gif;type=a",
571 "localhost", ftplib.FTP_PORT, "A",
572 [], "baz.gif", None), # XXX really this should guess image/gif
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000573 ]:
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000574 req = Request(url)
575 req.timeout = None
576 r = h.ftp_open(req)
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000577 # ftp authentication not yet implemented by FTPHandler
578 self.assert_(h.user == h.passwd == "")
Jeremy Hyltondf38ea92003-12-17 20:42:38 +0000579 self.assertEqual(h.host, socket.gethostbyname(host))
580 self.assertEqual(h.port, port)
581 self.assertEqual(h.dirs, dirs)
582 self.assertEqual(h.ftpwrapper.filename, filename)
583 self.assertEqual(h.ftpwrapper.filetype, type_)
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000584 headers = r.info()
Kurt B. Kaiser3f7cb5d2004-07-11 17:14:13 +0000585 self.assertEqual(headers.get("Content-type"), mimetype)
Jeremy Hyltondf38ea92003-12-17 20:42:38 +0000586 self.assertEqual(int(headers["Content-length"]), len(data))
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000587
588 def test_file(self):
Christian Heimes05e8be12008-02-23 18:30:17 +0000589 import rfc822, socket
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000590 h = urllib2.FileHandler()
591 o = h.parent = MockOpener()
592
Benjamin Petersonee8712c2008-05-20 21:35:26 +0000593 TESTFN = support.TESTFN
Tim Peters58eb11c2004-01-18 20:29:55 +0000594 urlpath = sanepathname2url(os.path.abspath(TESTFN))
Guido van Rossum6a2ccd02007-07-16 20:51:57 +0000595 towrite = b"hello, world\n"
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000596 urls = [
Tim Peters58eb11c2004-01-18 20:29:55 +0000597 "file://localhost%s" % urlpath,
598 "file://%s" % urlpath,
599 "file://%s%s" % (socket.gethostbyname('localhost'), urlpath),
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000600 ]
601 try:
602 localaddr = socket.gethostbyname(socket.gethostname())
603 except socket.gaierror:
604 localaddr = ''
605 if localaddr:
606 urls.append("file://%s%s" % (localaddr, urlpath))
607
608 for url in urls:
Tim Peters58eb11c2004-01-18 20:29:55 +0000609 f = open(TESTFN, "wb")
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000610 try:
611 try:
612 f.write(towrite)
613 finally:
614 f.close()
615
616 r = h.file_open(Request(url))
617 try:
618 data = r.read()
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000619 headers = r.info()
620 newurl = r.geturl()
621 finally:
622 r.close()
Tim Peters58eb11c2004-01-18 20:29:55 +0000623 stats = os.stat(TESTFN)
624 modified = rfc822.formatdate(stats.st_mtime)
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000625 finally:
626 os.remove(TESTFN)
Jeremy Hyltondf38ea92003-12-17 20:42:38 +0000627 self.assertEqual(data, towrite)
628 self.assertEqual(headers["Content-type"], "text/plain")
629 self.assertEqual(headers["Content-length"], "13")
Tim Peters58eb11c2004-01-18 20:29:55 +0000630 self.assertEqual(headers["Last-modified"], modified)
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000631
632 for url in [
Tim Peters58eb11c2004-01-18 20:29:55 +0000633 "file://localhost:80%s" % urlpath,
Guido van Rossumd8faa362007-04-27 19:54:29 +0000634 "file:///file_does_not_exist.txt",
635 "file://%s:80%s/%s" % (socket.gethostbyname('localhost'),
636 os.getcwd(), TESTFN),
637 "file://somerandomhost.ontheinternet.com%s/%s" %
638 (os.getcwd(), TESTFN),
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000639 ]:
640 try:
Tim Peters58eb11c2004-01-18 20:29:55 +0000641 f = open(TESTFN, "wb")
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000642 try:
643 f.write(towrite)
644 finally:
645 f.close()
646
647 self.assertRaises(urllib2.URLError,
648 h.file_open, Request(url))
649 finally:
650 os.remove(TESTFN)
651
652 h = urllib2.FileHandler()
653 o = h.parent = MockOpener()
654 # XXXX why does // mean ftp (and /// mean not ftp!), and where
655 # is file: scheme specified? I think this is really a bug, and
656 # what was intended was to distinguish between URLs like:
657 # file:/blah.txt (a file)
658 # file://localhost/blah.txt (a file)
659 # file:///blah.txt (a file)
660 # file://ftp.example.com/blah.txt (an ftp URL)
661 for url, ftp in [
662 ("file://ftp.example.com//foo.txt", True),
663 ("file://ftp.example.com///foo.txt", False),
664# XXXX bug: fails with OSError, should be URLError
665 ("file://ftp.example.com/foo.txt", False),
666 ]:
667 req = Request(url)
668 try:
669 h.file_open(req)
670 # XXXX remove OSError when bug fixed
671 except (urllib2.URLError, OSError):
672 self.assert_(not ftp)
673 else:
674 self.assert_(o.req is req)
Jeremy Hyltondf38ea92003-12-17 20:42:38 +0000675 self.assertEqual(req.type, "ftp")
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000676
677 def test_http(self):
Guido van Rossum700bd922007-08-27 18:10:06 +0000678 class MockHTTPResponse(io.IOBase):
Jeremy Hyltondf38ea92003-12-17 20:42:38 +0000679 def __init__(self, fp, msg, status, reason):
680 self.fp = fp
681 self.msg = msg
682 self.status = status
683 self.reason = reason
Jeremy Hylton5d9c3032004-08-07 17:40:50 +0000684 def read(self):
685 return ''
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000686 class MockHTTPClass:
687 def __init__(self):
688 self.req_headers = []
689 self.data = None
690 self.raise_on_endheaders = False
Georg Brandlf78e02b2008-06-10 17:40:04 +0000691 def __call__(self, host, timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000692 self.host = host
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000693 self.timeout = timeout
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000694 return self
Jeremy Hyltondf38ea92003-12-17 20:42:38 +0000695 def set_debuglevel(self, level):
696 self.level = level
697 def request(self, method, url, body=None, headers={}):
698 self.method = method
699 self.selector = url
700 self.req_headers += headers.items()
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000701 self.req_headers.sort()
Jeremy Hyltondf38ea92003-12-17 20:42:38 +0000702 if body:
703 self.data = body
704 if self.raise_on_endheaders:
705 import socket
706 raise socket.error()
Jeremy Hyltondf38ea92003-12-17 20:42:38 +0000707 def getresponse(self):
708 return MockHTTPResponse(MockFile(), {}, 200, "OK")
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000709
710 h = urllib2.AbstractHTTPHandler()
711 o = h.parent = MockOpener()
712
713 url = "http://example.com/"
714 for method, data in [("GET", None), ("POST", "blah")]:
715 req = Request(url, data, {"Foo": "bar"})
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000716 req.timeout = None
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000717 req.add_unredirected_header("Spam", "eggs")
718 http = MockHTTPClass()
719 r = h.do_open(http, req)
720
721 # result attributes
722 r.read; r.readline # wrapped MockFile methods
723 r.info; r.geturl # addinfourl methods
724 r.code, r.msg == 200, "OK" # added from MockHTTPClass.getreply()
725 hdrs = r.info()
Guido van Rossume2b70bc2006-08-18 22:13:04 +0000726 hdrs.get; hdrs.__contains__ # r.info() gives dict from .getreply()
Jeremy Hyltondf38ea92003-12-17 20:42:38 +0000727 self.assertEqual(r.geturl(), url)
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000728
Jeremy Hyltondf38ea92003-12-17 20:42:38 +0000729 self.assertEqual(http.host, "example.com")
730 self.assertEqual(http.level, 0)
731 self.assertEqual(http.method, method)
732 self.assertEqual(http.selector, "/")
733 self.assertEqual(http.req_headers,
Jeremy Hyltonb3ee6f92004-02-24 19:40:35 +0000734 [("Connection", "close"),
735 ("Foo", "bar"), ("Spam", "eggs")])
Jeremy Hyltondf38ea92003-12-17 20:42:38 +0000736 self.assertEqual(http.data, data)
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000737
738 # check socket.error converted to URLError
739 http.raise_on_endheaders = True
740 self.assertRaises(urllib2.URLError, h.do_open, http, req)
741
742 # check adding of standard headers
743 o.addheaders = [("Spam", "eggs")]
744 for data in "", None: # POST, GET
745 req = Request("http://example.com/", data)
746 r = MockResponse(200, "OK", {}, "")
Martin v. Löwis2a6ba902004-05-31 18:22:40 +0000747 newreq = h.do_request_(req)
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000748 if data is None: # GET
Thomas Wouters00ee7ba2006-08-21 19:07:27 +0000749 self.assert_("Content-length" not in req.unredirected_hdrs)
750 self.assert_("Content-type" not in req.unredirected_hdrs)
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000751 else: # POST
Thomas Wouters00ee7ba2006-08-21 19:07:27 +0000752 self.assertEqual(req.unredirected_hdrs["Content-length"], "0")
753 self.assertEqual(req.unredirected_hdrs["Content-type"],
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000754 "application/x-www-form-urlencoded")
755 # XXX the details of Host could be better tested
Jeremy Hyltondf38ea92003-12-17 20:42:38 +0000756 self.assertEqual(req.unredirected_hdrs["Host"], "example.com")
757 self.assertEqual(req.unredirected_hdrs["Spam"], "eggs")
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000758
759 # don't clobber existing headers
760 req.add_unredirected_header("Content-length", "foo")
761 req.add_unredirected_header("Content-type", "bar")
762 req.add_unredirected_header("Host", "baz")
763 req.add_unredirected_header("Spam", "foo")
Martin v. Löwis2a6ba902004-05-31 18:22:40 +0000764 newreq = h.do_request_(req)
Thomas Wouters00ee7ba2006-08-21 19:07:27 +0000765 self.assertEqual(req.unredirected_hdrs["Content-length"], "foo")
766 self.assertEqual(req.unredirected_hdrs["Content-type"], "bar")
Jeremy Hyltondf38ea92003-12-17 20:42:38 +0000767 self.assertEqual(req.unredirected_hdrs["Host"], "baz")
768 self.assertEqual(req.unredirected_hdrs["Spam"], "foo")
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000769
770 def test_errors(self):
771 h = urllib2.HTTPErrorProcessor()
772 o = h.parent = MockOpener()
773
774 url = "http://example.com/"
775 req = Request(url)
Guido van Rossumd8faa362007-04-27 19:54:29 +0000776 # all 2xx are passed through
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000777 r = MockResponse(200, "OK", {}, "", url)
778 newr = h.http_response(req, r)
779 self.assert_(r is newr)
780 self.assert_(not hasattr(o, "proto")) # o.error not called
Guido van Rossumd8faa362007-04-27 19:54:29 +0000781 r = MockResponse(202, "Accepted", {}, "", url)
782 newr = h.http_response(req, r)
783 self.assert_(r is newr)
784 self.assert_(not hasattr(o, "proto")) # o.error not called
785 r = MockResponse(206, "Partial content", {}, "", url)
786 newr = h.http_response(req, r)
787 self.assert_(r is newr)
788 self.assert_(not hasattr(o, "proto")) # o.error not called
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000789 # anything else calls o.error (and MockOpener returns None, here)
Guido van Rossumd8faa362007-04-27 19:54:29 +0000790 r = MockResponse(502, "Bad gateway", {}, "", url)
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000791 self.assert_(h.http_response(req, r) is None)
Jeremy Hyltondf38ea92003-12-17 20:42:38 +0000792 self.assertEqual(o.proto, "http") # o.error called
Guido van Rossumd8faa362007-04-27 19:54:29 +0000793 self.assertEqual(o.args, (req, r, 502, "Bad gateway", {}))
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000794
Martin v. Löwis2a6ba902004-05-31 18:22:40 +0000795 def test_cookies(self):
796 cj = MockCookieJar()
797 h = urllib2.HTTPCookieProcessor(cj)
798 o = h.parent = MockOpener()
799
800 req = Request("http://example.com/")
801 r = MockResponse(200, "OK", {}, "")
802 newreq = h.http_request(req)
803 self.assert_(cj.ach_req is req is newreq)
804 self.assertEquals(req.get_origin_req_host(), "example.com")
805 self.assert_(not req.is_unverifiable())
806 newr = h.http_response(req, r)
807 self.assert_(cj.ec_req is req)
808 self.assert_(cj.ec_r is r is newr)
809
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000810 def test_redirect(self):
811 from_url = "http://example.com/a.html"
812 to_url = "http://example.com/b.html"
813 h = urllib2.HTTPRedirectHandler()
814 o = h.parent = MockOpener()
815
816 # ordinary redirect behaviour
817 for code in 301, 302, 303, 307:
818 for data in None, "blah\nblah\n":
819 method = getattr(h, "http_error_%s" % code)
820 req = Request(from_url, data)
821 req.add_header("Nonsense", "viking=withhold")
Christian Heimes77c02eb2008-02-09 02:18:51 +0000822 if data is not None:
823 req.add_header("Content-Length", str(len(data)))
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000824 req.add_unredirected_header("Spam", "spam")
825 try:
Martin v. Löwis2a6ba902004-05-31 18:22:40 +0000826 method(req, MockFile(), code, "Blah",
827 MockHeaders({"location": to_url}))
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000828 except urllib2.HTTPError:
829 # 307 in response to POST requires user OK
830 self.assert_(code == 307 and data is not None)
Jeremy Hyltondf38ea92003-12-17 20:42:38 +0000831 self.assertEqual(o.req.get_full_url(), to_url)
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000832 try:
Jeremy Hyltondf38ea92003-12-17 20:42:38 +0000833 self.assertEqual(o.req.get_method(), "GET")
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000834 except AttributeError:
835 self.assert_(not o.req.has_data())
Christian Heimes77c02eb2008-02-09 02:18:51 +0000836
837 # now it's a GET, there should not be headers regarding content
838 # (possibly dragged from before being a POST)
839 headers = [x.lower() for x in o.req.headers]
840 self.assertTrue("content-length" not in headers)
841 self.assertTrue("content-type" not in headers)
842
Jeremy Hyltondf38ea92003-12-17 20:42:38 +0000843 self.assertEqual(o.req.headers["Nonsense"],
844 "viking=withhold")
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000845 self.assert_("Spam" not in o.req.headers)
846 self.assert_("Spam" not in o.req.unredirected_hdrs)
847
848 # loop detection
849 req = Request(from_url)
Martin v. Löwis2a6ba902004-05-31 18:22:40 +0000850 def redirect(h, req, url=to_url):
851 h.http_error_302(req, MockFile(), 302, "Blah",
852 MockHeaders({"location": url}))
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000853 # Note that the *original* request shares the same record of
854 # redirections with the sub-requests caused by the redirections.
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000855
Martin v. Löwis2a6ba902004-05-31 18:22:40 +0000856 # detect infinite loop redirect of a URL to itself
857 req = Request(from_url, origin_req_host="example.com")
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000858 count = 0
859 try:
860 while 1:
Martin v. Löwis2a6ba902004-05-31 18:22:40 +0000861 redirect(h, req, "http://example.com/")
862 count = count + 1
863 except urllib2.HTTPError:
864 # don't stop until max_repeats, because cookies may introduce state
865 self.assertEqual(count, urllib2.HTTPRedirectHandler.max_repeats)
866
867 # detect endless non-repeating chain of redirects
868 req = Request(from_url, origin_req_host="example.com")
869 count = 0
870 try:
871 while 1:
872 redirect(h, req, "http://example.com/%d" % count)
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000873 count = count + 1
874 except urllib2.HTTPError:
Jeremy Hyltondf38ea92003-12-17 20:42:38 +0000875 self.assertEqual(count,
876 urllib2.HTTPRedirectHandler.max_redirections)
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +0000877
Martin v. Löwis2a6ba902004-05-31 18:22:40 +0000878 def test_cookie_redirect(self):
Martin v. Löwis2a6ba902004-05-31 18:22:40 +0000879 # cookies shouldn't leak into redirected requests
Georg Brandl24420152008-05-26 16:32:26 +0000880 from http.cookiejar import CookieJar
881 from test.test_http_cookiejar import interact_netscape
Martin v. Löwis2a6ba902004-05-31 18:22:40 +0000882
883 cj = CookieJar()
884 interact_netscape(cj, "http://www.example.com/", "spam=eggs")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000885 hh = MockHTTPHandler(302, "Location: http://www.cracker.com/\r\n\r\n")
886 hdeh = urllib2.HTTPDefaultErrorHandler()
887 hrh = urllib2.HTTPRedirectHandler()
888 cp = urllib2.HTTPCookieProcessor(cj)
889 o = build_test_opener(hh, hdeh, hrh, cp)
Martin v. Löwis2a6ba902004-05-31 18:22:40 +0000890 o.open("http://www.example.com/")
891 self.assert_(not hh.req.has_header("Cookie"))
892
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000893 def test_proxy(self):
894 o = OpenerDirector()
895 ph = urllib2.ProxyHandler(dict(http="proxy.example.com:3128"))
896 o.add_handler(ph)
897 meth_spec = [
898 [("http_open", "return response")]
899 ]
900 handlers = add_ordered_mock_handlers(o, meth_spec)
901
902 req = Request("http://acme.example.com/")
903 self.assertEqual(req.get_host(), "acme.example.com")
904 r = o.open(req)
905 self.assertEqual(req.get_host(), "proxy.example.com:3128")
906
907 self.assertEqual([(handlers[0], "http_open")],
908 [tup[0:2] for tup in o.calls])
909
Christian Heimes4fbc72b2008-03-22 00:47:35 +0000910 def test_basic_auth(self, quote_char='"'):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000911 opener = OpenerDirector()
912 password_manager = MockPasswordManager()
913 auth_handler = urllib2.HTTPBasicAuthHandler(password_manager)
914 realm = "ACME Widget Store"
915 http_handler = MockHTTPHandler(
Christian Heimes4fbc72b2008-03-22 00:47:35 +0000916 401, 'WWW-Authenticate: Basic realm=%s%s%s\r\n\r\n' %
917 (quote_char, realm, quote_char) )
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000918 opener.add_handler(auth_handler)
919 opener.add_handler(http_handler)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000920 self._test_basic_auth(opener, auth_handler, "Authorization",
921 realm, http_handler, password_manager,
922 "http://acme.example.com/protected",
923 "http://acme.example.com/protected",
924 )
925
Christian Heimes4fbc72b2008-03-22 00:47:35 +0000926 def test_basic_auth_with_single_quoted_realm(self):
927 self.test_basic_auth(quote_char="'")
928
Thomas Wouters477c8d52006-05-27 19:21:47 +0000929 def test_proxy_basic_auth(self):
930 opener = OpenerDirector()
931 ph = urllib2.ProxyHandler(dict(http="proxy.example.com:3128"))
932 opener.add_handler(ph)
933 password_manager = MockPasswordManager()
934 auth_handler = urllib2.ProxyBasicAuthHandler(password_manager)
935 realm = "ACME Networks"
936 http_handler = MockHTTPHandler(
937 407, 'Proxy-Authenticate: Basic realm="%s"\r\n\r\n' % realm)
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000938 opener.add_handler(auth_handler)
939 opener.add_handler(http_handler)
Thomas Wouters00ee7ba2006-08-21 19:07:27 +0000940 self._test_basic_auth(opener, auth_handler, "Proxy-authorization",
Thomas Wouters477c8d52006-05-27 19:21:47 +0000941 realm, http_handler, password_manager,
942 "http://acme.example.com:3128/protected",
943 "proxy.example.com:3128",
944 )
945
946 def test_basic_and_digest_auth_handlers(self):
947 # HTTPDigestAuthHandler threw an exception if it couldn't handle a 40*
948 # response (http://python.org/sf/1479302), where it should instead
949 # return None to allow another handler (especially
950 # HTTPBasicAuthHandler) to handle the response.
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000951
952 # Also (http://python.org/sf/14797027, RFC 2617 section 1.2), we must
953 # try digest first (since it's the strongest auth scheme), so we record
954 # order of calls here to check digest comes first:
955 class RecordingOpenerDirector(OpenerDirector):
956 def __init__(self):
957 OpenerDirector.__init__(self)
958 self.recorded = []
959 def record(self, info):
960 self.recorded.append(info)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000961 class TestDigestAuthHandler(urllib2.HTTPDigestAuthHandler):
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000962 def http_error_401(self, *args, **kwds):
963 self.parent.record("digest")
964 urllib2.HTTPDigestAuthHandler.http_error_401(self,
965 *args, **kwds)
966 class TestBasicAuthHandler(urllib2.HTTPBasicAuthHandler):
967 def http_error_401(self, *args, **kwds):
968 self.parent.record("basic")
969 urllib2.HTTPBasicAuthHandler.http_error_401(self,
970 *args, **kwds)
971
972 opener = RecordingOpenerDirector()
Thomas Wouters477c8d52006-05-27 19:21:47 +0000973 password_manager = MockPasswordManager()
974 digest_handler = TestDigestAuthHandler(password_manager)
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000975 basic_handler = TestBasicAuthHandler(password_manager)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000976 realm = "ACME Networks"
977 http_handler = MockHTTPHandler(
978 401, 'WWW-Authenticate: Basic realm="%s"\r\n\r\n' % realm)
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000979 opener.add_handler(basic_handler)
980 opener.add_handler(digest_handler)
981 opener.add_handler(http_handler)
982
983 # check basic auth isn't blocked by digest handler failing
Thomas Wouters477c8d52006-05-27 19:21:47 +0000984 self._test_basic_auth(opener, basic_handler, "Authorization",
985 realm, http_handler, password_manager,
986 "http://acme.example.com/protected",
987 "http://acme.example.com/protected",
988 )
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000989 # check digest was tried before basic (twice, because
990 # _test_basic_auth called .open() twice)
991 self.assertEqual(opener.recorded, ["digest", "basic"]*2)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000992
993 def _test_basic_auth(self, opener, auth_handler, auth_header,
994 realm, http_handler, password_manager,
995 request_url, protected_url):
Christian Heimes05e8be12008-02-23 18:30:17 +0000996 import base64
Thomas Wouters477c8d52006-05-27 19:21:47 +0000997 user, password = "wile", "coyote"
Thomas Wouters477c8d52006-05-27 19:21:47 +0000998
999 # .add_password() fed through to password manager
1000 auth_handler.add_password(realm, request_url, user, password)
1001 self.assertEqual(realm, password_manager.realm)
1002 self.assertEqual(request_url, password_manager.url)
1003 self.assertEqual(user, password_manager.user)
1004 self.assertEqual(password, password_manager.password)
1005
1006 r = opener.open(request_url)
1007
1008 # should have asked the password manager for the username/password
1009 self.assertEqual(password_manager.target_realm, realm)
1010 self.assertEqual(password_manager.target_url, protected_url)
1011
1012 # expect one request without authorization, then one with
1013 self.assertEqual(len(http_handler.requests), 2)
1014 self.assertFalse(http_handler.requests[0].has_header(auth_header))
Guido van Rossum98b349f2007-08-27 21:47:52 +00001015 userpass = bytes('%s:%s' % (user, password), "ascii")
Guido van Rossum98297ee2007-11-06 21:34:58 +00001016 auth_hdr_value = ('Basic ' +
1017 base64.encodestring(userpass).strip().decode())
Thomas Wouters477c8d52006-05-27 19:21:47 +00001018 self.assertEqual(http_handler.requests[1].get_header(auth_header),
1019 auth_hdr_value)
1020
1021 # if the password manager can't find a password, the handler won't
1022 # handle the HTTP auth error
1023 password_manager.user = password_manager.password = None
1024 http_handler.reset()
1025 r = opener.open(request_url)
1026 self.assertEqual(len(http_handler.requests), 1)
1027 self.assertFalse(http_handler.requests[0].has_header(auth_header))
1028
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +00001029
1030class MiscTests(unittest.TestCase):
1031
1032 def test_build_opener(self):
1033 class MyHTTPHandler(urllib2.HTTPHandler): pass
1034 class FooHandler(urllib2.BaseHandler):
1035 def foo_open(self): pass
1036 class BarHandler(urllib2.BaseHandler):
1037 def bar_open(self): pass
1038
1039 build_opener = urllib2.build_opener
1040
1041 o = build_opener(FooHandler, BarHandler)
1042 self.opener_has_handler(o, FooHandler)
1043 self.opener_has_handler(o, BarHandler)
1044
1045 # can take a mix of classes and instances
1046 o = build_opener(FooHandler, BarHandler())
1047 self.opener_has_handler(o, FooHandler)
1048 self.opener_has_handler(o, BarHandler)
1049
1050 # subclasses of default handlers override default handlers
1051 o = build_opener(MyHTTPHandler)
1052 self.opener_has_handler(o, MyHTTPHandler)
1053
1054 # a particular case of overriding: default handlers can be passed
1055 # in explicitly
1056 o = build_opener()
1057 self.opener_has_handler(o, urllib2.HTTPHandler)
1058 o = build_opener(urllib2.HTTPHandler)
1059 self.opener_has_handler(o, urllib2.HTTPHandler)
1060 o = build_opener(urllib2.HTTPHandler())
1061 self.opener_has_handler(o, urllib2.HTTPHandler)
1062
Christian Heimes81ee3ef2008-05-04 22:42:01 +00001063 # Issue2670: multiple handlers sharing the same base class
1064 class MyOtherHTTPHandler(urllib2.HTTPHandler): pass
1065 o = build_opener(MyHTTPHandler, MyOtherHTTPHandler)
1066 self.opener_has_handler(o, MyHTTPHandler)
1067 self.opener_has_handler(o, MyOtherHTTPHandler)
1068
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +00001069 def opener_has_handler(self, opener, handler_class):
1070 for h in opener.handlers:
1071 if h.__class__ == handler_class:
1072 break
1073 else:
1074 self.assert_(False)
1075
1076
1077def test_main(verbose=None):
Thomas Wouters477c8d52006-05-27 19:21:47 +00001078 from test import test_urllib2
Benjamin Petersonee8712c2008-05-20 21:35:26 +00001079 support.run_doctest(test_urllib2, verbose)
1080 support.run_doctest(urllib2, verbose)
Andrew M. Kuchlingbd3200f2004-06-29 13:15:46 +00001081 tests = (TrivialTests,
1082 OpenerDirectorTests,
1083 HandlerTests,
1084 MiscTests)
Benjamin Petersonee8712c2008-05-20 21:35:26 +00001085 support.run_unittest(*tests)
Jeremy Hyltonc1be59f2003-12-14 05:27:34 +00001086
1087if __name__ == "__main__":
1088 test_main(verbose=True)