From 1696718c0b89b7562f13135a91356358933c27c4 Mon Sep 17 00:00:00 2001
From: Helmut Grohne <helmut@subdivi.de>
Date: Thu, 1 Dec 2011 15:07:19 +0100
Subject: respect RFC2617 in terms of what is quoted

Said RFC is quite precise on which values of a challenge are to be
quoted. I didn't honour those parts and many applications do not enforce
these requirements, so I didn't notice. Now I explain which values are
to be quoted in the hopes that it works with "Wget/1.10.2 (Red Hat
modified)".
---
 wsgitools/digest.py | 36 ++++++++++++++++++++++--------------
 1 file changed, 22 insertions(+), 14 deletions(-)

diff --git a/wsgitools/digest.py b/wsgitools/digest.py
index 6781e94..0acfa34 100644
--- a/wsgitools/digest.py
+++ b/wsgitools/digest.py
@@ -111,14 +111,22 @@ def parse_digest_response(data, ret=None):
 def format_digest(mapping):
     """internal
 
-    @type mapping: {str: str}
+    @type mapping: {str: (str, bool)}
+    @param mapping: a mapping of keys to values and a boolean that
+        determines whether the value needs quoting.
     @rtype: str
+    @note: the RFC specifies which values must be quoted and which must not be
+        quoted.
     """
     assert isinstance(mapping, dict)
-    result = ((key, value if value.isalnum() else
-                    '"%s"' % value.replace('\\', '\\\\').replace('"', '\\"'))
-              for key, value in mapping.items())
-    result = map("%s=%s".__mod__, result)
+    result = []
+    for key, (value, needsquoting) in mapping.items():
+        if needsquoting:
+            value = '"%s"' % value.replace('\\', '\\\\').replace('"', '\\"')
+        else:
+            assert '"' not in value
+            assert ',' not in value
+        result.append("%s=%s" % (key, value))
     return ", ".join(result)
 
 class StaleNonce(AuthenticationRequired):
@@ -741,11 +749,11 @@ class AuthDigestMiddleware(AuthenticationMiddleware):
         if response is None or response != credresponse:
             raise AuthenticationRequired("wrong response")
 
-        digest = dict(nextnonce=self.noncestore.newnonce())
+        digest = dict(nextnonce=(self.noncestore.newnonce(), True))
         if "qop" in credentials:
-            digest["qop"] = "auth"
-            digest["cnonce"] = credentials["cnonce"] # no KeyError
-            digest["rspauth"] = self.auth_response(credentials, "")
+            digest["qop"] = ("auth", False)
+            digest["cnonce"] = (credentials["cnonce"], True) # no KeyError
+            digest["rspauth"] = (self.auth_response(credentials, ""), True)
         return dict(user=credentials["username"],
                     outheaders=[("Authentication-Info", format_digest(digest))])
 
@@ -785,11 +793,11 @@ class AuthDigestMiddleware(AuthenticationMiddleware):
         return self.algorithms[algo](":".join(dig))
 
     def www_authenticate(self, exception):
-        digest = dict(nonce=self.noncestore.newnonce(),
-                      realm=self.gentoken.realm,
-                      algorithm="md5",
-                      qop="auth")
+        digest = dict(nonce=(self.noncestore.newnonce(), True),
+                      realm=(self.gentoken.realm, True),
+                      algorithm=("MD5", False),
+                      qop=("auth", False))
         if isinstance(exception, StaleNonce):
-            digest["stale"] = "TRUE"
+            digest["stale"] = ("TRUE", False)
         challenge = format_digest(digest)
         return ("WWW-Authenticate", "Digest %s" % challenge)
-- 
cgit v1.2.3