[Python-checkins] r46531 - in python/trunk: Doc/lib/liburllib2.tex Lib/test/test_urllib2.py Lib/urllib2.py

georg.brandl python-checkins at python.org
Mon May 29 22:52:54 CEST 2006


Author: georg.brandl
Date: Mon May 29 22:52:54 2006
New Revision: 46531

Modified:
   python/trunk/Doc/lib/liburllib2.tex
   python/trunk/Lib/test/test_urllib2.py
   python/trunk/Lib/urllib2.py
Log:
Patches #1497027 and #972322: try HTTP digest auth first,
and watch out for handler name collisions.



Modified: python/trunk/Doc/lib/liburllib2.tex
==============================================================================
--- python/trunk/Doc/lib/liburllib2.tex	(original)
+++ python/trunk/Doc/lib/liburllib2.tex	Mon May 29 22:52:54 2006
@@ -65,9 +65,7 @@
 
 Beginning in Python 2.3, a \class{BaseHandler} subclass may also
 change its \member{handler_order} member variable to modify its
-position in the handlers list. Besides \class{ProxyHandler}, which has
-\member{handler_order} of \code{100}, all handlers currently have it
-set to \code{500}.
+position in the handlers list.
 \end{funcdesc}
 
 

Modified: python/trunk/Lib/test/test_urllib2.py
==============================================================================
--- python/trunk/Lib/test/test_urllib2.py	(original)
+++ python/trunk/Lib/test/test_urllib2.py	Mon May 29 22:52:54 2006
@@ -318,6 +318,27 @@
 
 class OpenerDirectorTests(unittest.TestCase):
 
+    def test_badly_named_methods(self):
+        # test work-around for three methods that accidentally follow the
+        # naming conventions for handler methods
+        # (*_open() / *_request() / *_response())
+
+        # These used to call the accidentally-named methods, causing a
+        # TypeError in real code; here, returning self from these mock
+        # methods would either cause no exception, or AttributeError.
+
+        from urllib2 import URLError
+
+        o = OpenerDirector()
+        meth_spec = [
+            [("do_open", "return self"), ("proxy_open", "return self")],
+            [("redirect_request", "return self")],
+            ]
+        handlers = add_ordered_mock_handlers(o, meth_spec)
+        o.add_handler(urllib2.UnknownHandler())
+        for scheme in "do", "proxy", "redirect":
+            self.assertRaises(URLError, o.open, scheme+"://example.com/")
+
     def test_handled(self):
         # handler returning non-None means no more handlers will be called
         o = OpenerDirector()
@@ -807,6 +828,8 @@
         realm = "ACME Widget Store"
         http_handler = MockHTTPHandler(
             401, 'WWW-Authenticate: Basic realm="%s"\r\n\r\n' % realm)
+        opener.add_handler(auth_handler)
+        opener.add_handler(http_handler)
         self._test_basic_auth(opener, auth_handler, "Authorization",
                               realm, http_handler, password_manager,
                               "http://acme.example.com/protected",
@@ -822,6 +845,8 @@
         realm = "ACME Networks"
         http_handler = MockHTTPHandler(
             407, 'Proxy-Authenticate: Basic realm="%s"\r\n\r\n' % realm)
+        opener.add_handler(auth_handler)
+        opener.add_handler(http_handler)
         self._test_basic_auth(opener, auth_handler, "Proxy-authorization",
                               realm, http_handler, password_manager,
                               "http://acme.example.com:3128/protected",
@@ -833,29 +858,53 @@
         # response (http://python.org/sf/1479302), where it should instead
         # return None to allow another handler (especially
         # HTTPBasicAuthHandler) to handle the response.
+
+        # Also (http://python.org/sf/14797027, RFC 2617 section 1.2), we must
+        # try digest first (since it's the strongest auth scheme), so we record
+        # order of calls here to check digest comes first:
+        class RecordingOpenerDirector(OpenerDirector):
+            def __init__(self):
+                OpenerDirector.__init__(self)
+                self.recorded = []
+            def record(self, info):
+                self.recorded.append(info)
         class TestDigestAuthHandler(urllib2.HTTPDigestAuthHandler):
-            handler_order = 400  # strictly before HTTPBasicAuthHandler
-        opener = OpenerDirector()
+            def http_error_401(self, *args, **kwds):
+                self.parent.record("digest")
+                urllib2.HTTPDigestAuthHandler.http_error_401(self,
+                                                             *args, **kwds)
+        class TestBasicAuthHandler(urllib2.HTTPBasicAuthHandler):
+            def http_error_401(self, *args, **kwds):
+                self.parent.record("basic")
+                urllib2.HTTPBasicAuthHandler.http_error_401(self,
+                                                            *args, **kwds)
+
+        opener = RecordingOpenerDirector()
         password_manager = MockPasswordManager()
         digest_handler = TestDigestAuthHandler(password_manager)
-        basic_handler = urllib2.HTTPBasicAuthHandler(password_manager)
-        opener.add_handler(digest_handler)
+        basic_handler = TestBasicAuthHandler(password_manager)
         realm = "ACME Networks"
         http_handler = MockHTTPHandler(
             401, 'WWW-Authenticate: Basic realm="%s"\r\n\r\n' % realm)
+        opener.add_handler(basic_handler)
+        opener.add_handler(digest_handler)
+        opener.add_handler(http_handler)
+
+        # check basic auth isn't blocked by digest handler failing
         self._test_basic_auth(opener, basic_handler, "Authorization",
                               realm, http_handler, password_manager,
                               "http://acme.example.com/protected",
                               "http://acme.example.com/protected",
                               )
+        # check digest was tried before basic (twice, because
+        # _test_basic_auth called .open() twice)
+        self.assertEqual(opener.recorded, ["digest", "basic"]*2)
 
     def _test_basic_auth(self, opener, auth_handler, auth_header,
                          realm, http_handler, password_manager,
                          request_url, protected_url):
         import base64, httplib
         user, password = "wile", "coyote"
-        opener.add_handler(auth_handler)
-        opener.add_handler(http_handler)
 
         # .add_password() fed through to password manager
         auth_handler.add_password(realm, request_url, user, password)

Modified: python/trunk/Lib/urllib2.py
==============================================================================
--- python/trunk/Lib/urllib2.py	(original)
+++ python/trunk/Lib/urllib2.py	Mon May 29 22:52:54 2006
@@ -297,6 +297,10 @@
     def add_handler(self, handler):
         added = False
         for meth in dir(handler):
+            if meth in ["redirect_request", "do_open", "proxy_open"]:
+                # oops, coincidental match
+                continue
+
             i = meth.find("_")
             protocol = meth[:i]
             condition = meth[i+1:]
@@ -768,6 +772,10 @@
     # www-authenticate header.  should probably be a lot more careful
     # in parsing them to extract multiple alternatives
 
+    # XXX could pre-emptively send auth info already accepted (RFC 2617,
+    # end of section 2, and section 1.2 immediately after "credentials"
+    # production).
+
     def __init__(self, password_mgr=None):
         if password_mgr is None:
             password_mgr = HTTPPasswordMgr()
@@ -977,6 +985,7 @@
     """
 
     auth_header = 'Authorization'
+    handler_order = 490  # before Basic auth
 
     def http_error_401(self, req, fp, code, msg, headers):
         host = urlparse.urlparse(req.get_full_url())[1]
@@ -989,6 +998,7 @@
 class ProxyDigestAuthHandler(BaseHandler, AbstractDigestAuthHandler):
 
     auth_header = 'Proxy-Authorization'
+    handler_order = 490  # before Basic auth
 
     def http_error_407(self, req, fp, code, msg, headers):
         host = req.get_host()


More information about the Python-checkins mailing list