1import base64
2import re
3
4import pyparsing as pp
5
6from .error import *
7
8UNQUOTE_PAIRS = re.compile(r"\\(.)")
9unquote = lambda s, l, t: UNQUOTE_PAIRS.sub(r"\1", t[0][1:-1])
10
11# https://tools.ietf.org/html/rfc7235#section-1.2
12# https://tools.ietf.org/html/rfc7235#appendix-B
13tchar = "!#$%&'*+-.^_`|~" + pp.nums + pp.alphas
14token = pp.Word(tchar).setName("token")
15token68 = pp.Combine(pp.Word("-._~+/" + pp.nums + pp.alphas) + pp.Optional(pp.Word("=").leaveWhitespace())).setName(
16    "token68"
17)
18
19quoted_string = pp.dblQuotedString.copy().setName("quoted-string").setParseAction(unquote)
20auth_param_name = token.copy().setName("auth-param-name").addParseAction(pp.downcaseTokens)
21auth_param = auth_param_name + pp.Suppress("=") + (quoted_string | token)
22params = pp.Dict(pp.delimitedList(pp.Group(auth_param)))
23
24scheme = token("scheme")
25challenge = scheme + (params("params") | token68("token"))
26
27authentication_info = params.copy()
28www_authenticate = pp.delimitedList(pp.Group(challenge))
29
30
31def _parse_authentication_info(headers, headername="authentication-info"):
32    """https://tools.ietf.org/html/rfc7615
33    """
34    header = headers.get(headername, "").strip()
35    if not header:
36        return {}
37    try:
38        parsed = authentication_info.parseString(header)
39    except pp.ParseException as ex:
40        # print(ex.explain(ex))
41        raise MalformedHeader(headername)
42
43    return parsed.asDict()
44
45
46def _parse_www_authenticate(headers, headername="www-authenticate"):
47    """Returns a dictionary of dictionaries, one dict per auth_scheme."""
48    header = headers.get(headername, "").strip()
49    if not header:
50        return {}
51    try:
52        parsed = www_authenticate.parseString(header)
53    except pp.ParseException as ex:
54        # print(ex.explain(ex))
55        raise MalformedHeader(headername)
56
57    retval = {
58        challenge["scheme"].lower(): challenge["params"].asDict()
59        if "params" in challenge
60        else {"token": challenge.get("token")}
61        for challenge in parsed
62    }
63    return retval
64