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