diff options
Diffstat (limited to 'wsgitools/digest.py')
-rw-r--r-- | wsgitools/digest.py | 80 |
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) |