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