aboutsummaryrefslogtreecommitdiffstats
path: root/netlib/http_cookies.py
blob: 826754180fb50a55eb59e0f7c8c9c9635c3aa2af (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
"""
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


def _read_value(s, start, special):
    """
        Reads a value - the RHS of a token/value pair in a cookie.

        special: If the value is special, commas are premitted. Else comma
        terminates. This helps us support old and new style values.
    """
    if start >= len(s):
        return "", start
    elif s[start] == '"':
        return _read_quoted_string(s, start)
    elif special:
        return _read_until(s, start, ";")
    else:
        return _read_until(s, start, ";,")


def _read_pairs(s, specials=()):
    """
        Read pairs of lhs=rhs values.

        specials: A lower-cased list of keys that may contain commas.
    """
    off = 0
    vals = []
    while 1:
        lhs, off = _read_token(s, off)
        lhs = lhs.lstrip()
        rhs = None
        if off < len(s):
            if s[off] == "=":
                rhs, off = _read_value(s, off+1, lhs.lower() in specials)
        vals.append([lhs, rhs])
        off += 1
        if not off < len(s):
            break
    return vals, off


ESCAPE = re.compile(r"([\"\\])")


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.
    """
    vals = []
    for k, v in lst:
        if v is None:
            vals.append(k)
        else:
            if k.lower() not in specials and _has_special(v):
                v = ESCAPE.sub(r"\\\1", v)
                v = '"%s"'%v
            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.
    """
    return _format_pairs(od.lst)


def parse_set_cookies(s):
    start = 0


def unparse_set_cookies(s):
    pass