2015-04-11 22:26:09 +00:00
|
|
|
"""
|
|
|
|
A flexible module for cookie parsing and manipulation.
|
|
|
|
|
|
|
|
We try to be as permissive as possible. Parsing accepts formats from RFC6265 an
|
|
|
|
RFC2109. Serialization follows RFC6265 strictly.
|
|
|
|
|
|
|
|
http://tools.ietf.org/html/rfc6265
|
|
|
|
http://tools.ietf.org/html/rfc2109
|
|
|
|
"""
|
|
|
|
|
|
|
|
import re
|
|
|
|
|
|
|
|
import odict
|
|
|
|
|
|
|
|
|
|
|
|
def _read_until(s, start, term):
|
|
|
|
"""
|
|
|
|
Read until one of the characters in term is reached.
|
|
|
|
"""
|
|
|
|
if start == len(s):
|
|
|
|
return "", start+1
|
|
|
|
for i in range(start, len(s)):
|
|
|
|
if s[i] in term:
|
|
|
|
return s[start:i], i
|
|
|
|
return s[start:i+1], i+1
|
|
|
|
|
|
|
|
|
|
|
|
def _read_token(s, start):
|
|
|
|
"""
|
|
|
|
Read a token - the LHS of a token/value pair in a cookie.
|
|
|
|
"""
|
|
|
|
return _read_until(s, start, ";=")
|
|
|
|
|
|
|
|
|
|
|
|
def _read_quoted_string(s, start):
|
|
|
|
"""
|
|
|
|
start: offset to the first quote of the string to be read
|
|
|
|
|
|
|
|
A sort of loose super-set of the various quoted string specifications.
|
|
|
|
|
|
|
|
RFC6265 disallows backslashes or double quotes within quoted strings.
|
|
|
|
Prior RFCs use backslashes to escape. This leaves us free to apply
|
|
|
|
backslash escaping by default and be compatible with everything.
|
|
|
|
"""
|
|
|
|
escaping = False
|
|
|
|
ret = []
|
|
|
|
# Skip the first quote
|
|
|
|
for i in range(start+1, len(s)):
|
|
|
|
if escaping:
|
|
|
|
ret.append(s[i])
|
|
|
|
escaping = False
|
|
|
|
elif s[i] == '"':
|
|
|
|
break
|
|
|
|
elif s[i] == "\\":
|
|
|
|
escaping = True
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
ret.append(s[i])
|
|
|
|
return "".join(ret), i+1
|
|
|
|
|
|
|
|
|
2015-04-11 23:26:02 +00:00
|
|
|
def _read_value(s, start, special):
|
2015-04-11 22:26:09 +00:00
|
|
|
"""
|
|
|
|
Reads a value - the RHS of a token/value pair in a cookie.
|
2015-04-11 23:26:02 +00:00
|
|
|
|
|
|
|
special: If the value is special, commas are premitted. Else comma
|
|
|
|
terminates. This helps us support old and new style values.
|
2015-04-11 22:26:09 +00:00
|
|
|
"""
|
2015-04-11 23:26:02 +00:00
|
|
|
if start >= len(s):
|
|
|
|
return "", start
|
|
|
|
elif s[start] == '"':
|
2015-04-11 22:26:09 +00:00
|
|
|
return _read_quoted_string(s, start)
|
2015-04-11 23:26:02 +00:00
|
|
|
elif special:
|
|
|
|
return _read_until(s, start, ";")
|
2015-04-11 22:26:09 +00:00
|
|
|
else:
|
|
|
|
return _read_until(s, start, ";,")
|
|
|
|
|
|
|
|
|
2015-04-11 23:26:02 +00:00
|
|
|
def _read_pairs(s, specials=()):
|
2015-04-11 22:26:09 +00:00
|
|
|
"""
|
|
|
|
Read pairs of lhs=rhs values.
|
2015-04-11 23:26:02 +00:00
|
|
|
|
|
|
|
specials: A lower-cased list of keys that may contain commas.
|
2015-04-11 22:26:09 +00:00
|
|
|
"""
|
|
|
|
off = 0
|
|
|
|
vals = []
|
|
|
|
while 1:
|
|
|
|
lhs, off = _read_token(s, off)
|
2015-04-11 23:26:02 +00:00
|
|
|
lhs = lhs.lstrip()
|
2015-04-11 22:26:09 +00:00
|
|
|
rhs = None
|
|
|
|
if off < len(s):
|
|
|
|
if s[off] == "=":
|
2015-04-11 23:26:02 +00:00
|
|
|
rhs, off = _read_value(s, off+1, lhs.lower() in specials)
|
|
|
|
vals.append([lhs, rhs])
|
2015-04-11 22:26:09 +00:00
|
|
|
off += 1
|
|
|
|
if not off < len(s):
|
|
|
|
break
|
|
|
|
return vals, off
|
|
|
|
|
|
|
|
|
|
|
|
ESCAPE = re.compile(r"([\"\\])")
|
|
|
|
|
|
|
|
|
2015-04-11 23:26:02 +00:00
|
|
|
def _has_special(s):
|
|
|
|
for i in s:
|
|
|
|
if i in '",;\\':
|
|
|
|
return True
|
|
|
|
o = ord(i)
|
|
|
|
if o < 0x21 or o > 0x7e:
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
def _format_pairs(lst, specials=()):
|
|
|
|
"""
|
|
|
|
specials: A lower-cased list of keys that will not be quoted.
|
|
|
|
"""
|
2015-04-11 22:26:09 +00:00
|
|
|
vals = []
|
|
|
|
for k, v in lst:
|
|
|
|
if v is None:
|
|
|
|
vals.append(k)
|
|
|
|
else:
|
2015-04-11 23:26:02 +00:00
|
|
|
if k.lower() not in specials and _has_special(v):
|
|
|
|
v = ESCAPE.sub(r"\\\1", v)
|
|
|
|
v = '"%s"'%v
|
2015-04-11 22:26:09 +00:00
|
|
|
vals.append("%s=%s"%(k, v))
|
|
|
|
return "; ".join(vals)
|
|
|
|
|
|
|
|
|
|
|
|
def parse_cookies(s):
|
|
|
|
"""
|
|
|
|
Parses a Cookie header value.
|
|
|
|
Returns an ODict object.
|
|
|
|
"""
|
|
|
|
pairs, off = _read_pairs(s)
|
|
|
|
return odict.ODict(pairs)
|
|
|
|
|
|
|
|
|
|
|
|
def unparse_cookies(od):
|
|
|
|
"""
|
|
|
|
Formats a Cookie header value.
|
|
|
|
"""
|
2015-04-11 23:26:02 +00:00
|
|
|
return _format_pairs(od.lst)
|
2015-04-11 22:26:09 +00:00
|
|
|
|
|
|
|
|
|
|
|
def parse_set_cookies(s):
|
|
|
|
start = 0
|
|
|
|
|
|
|
|
|
|
|
|
def unparse_set_cookies(s):
|
|
|
|
pass
|