summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--wsgitools/digest.py80
1 files changed, 57 insertions, 23 deletions
diff --git a/wsgitools/digest.py b/wsgitools/digest.py
index fc8e310..ded5e8d 100644
--- a/wsgitools/digest.py
+++ b/wsgitools/digest.py
@@ -63,28 +63,63 @@ def parse_digest_response(data, ret=None):
... except ValueError:
... print("ValueError")
ValueError
+ >>> # backslashes: doc string, eval => two backslashes
+ >>> parse_digest_response('backslash="\\\\\\\\"')
+ {'backslash': '\\\\'}
+ >>> parse_digest_response('foo="quo\\\\"te"')
+ {'foo': 'quo"te'}
"""
assert isinstance(data, str)
- if ret is None:
- ret = {}
- data = data.strip()
- key, rest = data.split('=', 1) # raises ValueError
- if rest.startswith('"'):
- rest = rest[1:]
- value, rest = rest.split('"', 1) # raises ValueError
- if not rest:
- ret[key] = value
- return ret
- if rest[0] != ',':
- raise ValueError("invalid digest response")
- rest = rest[1:]
- else:
- if ',' not in rest:
- ret[key] = rest
- return ret
- value, rest = rest.split(',' , 1)
- ret[key] = value
- return parse_digest_response(rest, ret)
+ result = dict()
+ while True:
+ data = data.strip()
+ key, data = data.split('=', 1) # raises ValueError
+ if data.startswith('"'):
+ data = data[1:]
+ value = ""
+ while True:
+ part, data = data.split('"', 1) # raises ValueError
+ # how many consecutive backslashes did we see?
+ escape = -1 # the first iteration does not indicate a backslash
+ for part in part.split('\\'):
+ escape += 1 # backslash before part
+ if escape > 2:
+ value += "\\"
+ escape -= 2
+ if part:
+ escape = 0
+ value += part
+ if escape == 2:
+ value += "\\"
+ elif escape == 1:
+ value += '"'
+ continue
+ break
+ result[key] = value
+ if not data:
+ return result
+ if data[0] != ",":
+ raise ValueError("invalid digest response")
+ data = data[1:]
+ else:
+ if ',' not in data:
+ result[key] = data
+ return result
+ value, data = data.split(',', 1)
+ result[key] = value
+
+def format_digest(mapping):
+ """internal
+
+ @type mapping: {str: str}
+ @rtype: str
+ """
+ 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)
+ return ", ".join(result)
class StaleNonce(AuthenticationRequired):
pass
@@ -705,9 +740,8 @@ class AuthDigestMiddleware(AuthenticationMiddleware):
digest["qop"] = "auth"
digest["cnonce"] = credentials["cnonce"] # no KeyError
digest["rspauth"] = self.auth_response(credentials, "")
- challenge = ", ".join(map('%s="%s"'.__mod__, digest.items()))
return dict(user=credentials["username"],
- outheaders=[("Authentication-Info", challenge)])
+ outheaders=[("Authentication-Info", format_digest(digest))])
def auth_response(self, credentials, reqmethod):
"""internal method generating authentication tokens
@@ -751,5 +785,5 @@ class AuthDigestMiddleware(AuthenticationMiddleware):
qop="auth")
if isinstance(exception, StaleNonce):
digest["stale"] = "TRUE"
- challenge = ", ".join(map('%s="%s"'.__mod__, digest.items()))
+ challenge = format_digest(digest)
return ("WWW-Authenticate", "Digest %s" % challenge)