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