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