|
1 """Load / save to libwww-perl (LWP) format files. |
|
2 |
|
3 Actually, the format is slightly extended from that used by LWP's |
|
4 (libwww-perl's) HTTP::Cookies, to avoid losing some RFC 2965 information |
|
5 not recorded by LWP. |
|
6 |
|
7 It uses the version string "2.0", though really there isn't an LWP Cookies |
|
8 2.0 format. This indicates that there is extra information in here |
|
9 (domain_dot and # port_spec) while still being compatible with |
|
10 libwww-perl, I hope. |
|
11 |
|
12 """ |
|
13 |
|
14 import time, re |
|
15 from cookielib import (_warn_unhandled_exception, FileCookieJar, LoadError, |
|
16 Cookie, MISSING_FILENAME_TEXT, |
|
17 join_header_words, split_header_words, |
|
18 iso2time, time2isoz) |
|
19 |
|
20 def lwp_cookie_str(cookie): |
|
21 """Return string representation of Cookie in an the LWP cookie file format. |
|
22 |
|
23 Actually, the format is extended a bit -- see module docstring. |
|
24 |
|
25 """ |
|
26 h = [(cookie.name, cookie.value), |
|
27 ("path", cookie.path), |
|
28 ("domain", cookie.domain)] |
|
29 if cookie.port is not None: h.append(("port", cookie.port)) |
|
30 if cookie.path_specified: h.append(("path_spec", None)) |
|
31 if cookie.port_specified: h.append(("port_spec", None)) |
|
32 if cookie.domain_initial_dot: h.append(("domain_dot", None)) |
|
33 if cookie.secure: h.append(("secure", None)) |
|
34 if cookie.expires: h.append(("expires", |
|
35 time2isoz(float(cookie.expires)))) |
|
36 if cookie.discard: h.append(("discard", None)) |
|
37 if cookie.comment: h.append(("comment", cookie.comment)) |
|
38 if cookie.comment_url: h.append(("commenturl", cookie.comment_url)) |
|
39 |
|
40 keys = cookie._rest.keys() |
|
41 keys.sort() |
|
42 for k in keys: |
|
43 h.append((k, str(cookie._rest[k]))) |
|
44 |
|
45 h.append(("version", str(cookie.version))) |
|
46 |
|
47 return join_header_words([h]) |
|
48 |
|
49 class LWPCookieJar(FileCookieJar): |
|
50 """ |
|
51 The LWPCookieJar saves a sequence of"Set-Cookie3" lines. |
|
52 "Set-Cookie3" is the format used by the libwww-perl libary, not known |
|
53 to be compatible with any browser, but which is easy to read and |
|
54 doesn't lose information about RFC 2965 cookies. |
|
55 |
|
56 Additional methods |
|
57 |
|
58 as_lwp_str(ignore_discard=True, ignore_expired=True) |
|
59 |
|
60 """ |
|
61 |
|
62 def as_lwp_str(self, ignore_discard=True, ignore_expires=True): |
|
63 """Return cookies as a string of "\n"-separated "Set-Cookie3" headers. |
|
64 |
|
65 ignore_discard and ignore_expires: see docstring for FileCookieJar.save |
|
66 |
|
67 """ |
|
68 now = time.time() |
|
69 r = [] |
|
70 for cookie in self: |
|
71 if not ignore_discard and cookie.discard: |
|
72 continue |
|
73 if not ignore_expires and cookie.is_expired(now): |
|
74 continue |
|
75 r.append("Set-Cookie3: %s" % lwp_cookie_str(cookie)) |
|
76 return "\n".join(r+[""]) |
|
77 |
|
78 def save(self, filename=None, ignore_discard=False, ignore_expires=False): |
|
79 if filename is None: |
|
80 if self.filename is not None: filename = self.filename |
|
81 else: raise ValueError(MISSING_FILENAME_TEXT) |
|
82 |
|
83 f = open(filename, "w") |
|
84 try: |
|
85 # There really isn't an LWP Cookies 2.0 format, but this indicates |
|
86 # that there is extra information in here (domain_dot and |
|
87 # port_spec) while still being compatible with libwww-perl, I hope. |
|
88 f.write("#LWP-Cookies-2.0\n") |
|
89 f.write(self.as_lwp_str(ignore_discard, ignore_expires)) |
|
90 finally: |
|
91 f.close() |
|
92 |
|
93 def _really_load(self, f, filename, ignore_discard, ignore_expires): |
|
94 magic = f.readline() |
|
95 if not re.search(self.magic_re, magic): |
|
96 msg = ("%r does not look like a Set-Cookie3 (LWP) format " |
|
97 "file" % filename) |
|
98 raise LoadError(msg) |
|
99 |
|
100 now = time.time() |
|
101 |
|
102 header = "Set-Cookie3:" |
|
103 boolean_attrs = ("port_spec", "path_spec", "domain_dot", |
|
104 "secure", "discard") |
|
105 value_attrs = ("version", |
|
106 "port", "path", "domain", |
|
107 "expires", |
|
108 "comment", "commenturl") |
|
109 |
|
110 try: |
|
111 while 1: |
|
112 line = f.readline() |
|
113 if line == "": break |
|
114 if not line.startswith(header): |
|
115 continue |
|
116 line = line[len(header):].strip() |
|
117 |
|
118 for data in split_header_words([line]): |
|
119 name, value = data[0] |
|
120 standard = {} |
|
121 rest = {} |
|
122 for k in boolean_attrs: |
|
123 standard[k] = False |
|
124 for k, v in data[1:]: |
|
125 if k is not None: |
|
126 lc = k.lower() |
|
127 else: |
|
128 lc = None |
|
129 # don't lose case distinction for unknown fields |
|
130 if (lc in value_attrs) or (lc in boolean_attrs): |
|
131 k = lc |
|
132 if k in boolean_attrs: |
|
133 if v is None: v = True |
|
134 standard[k] = v |
|
135 elif k in value_attrs: |
|
136 standard[k] = v |
|
137 else: |
|
138 rest[k] = v |
|
139 |
|
140 h = standard.get |
|
141 expires = h("expires") |
|
142 discard = h("discard") |
|
143 if expires is not None: |
|
144 expires = iso2time(expires) |
|
145 if expires is None: |
|
146 discard = True |
|
147 domain = h("domain") |
|
148 domain_specified = domain.startswith(".") |
|
149 c = Cookie(h("version"), name, value, |
|
150 h("port"), h("port_spec"), |
|
151 domain, domain_specified, h("domain_dot"), |
|
152 h("path"), h("path_spec"), |
|
153 h("secure"), |
|
154 expires, |
|
155 discard, |
|
156 h("comment"), |
|
157 h("commenturl"), |
|
158 rest) |
|
159 if not ignore_discard and c.discard: |
|
160 continue |
|
161 if not ignore_expires and c.is_expired(now): |
|
162 continue |
|
163 self.set_cookie(c) |
|
164 |
|
165 except IOError: |
|
166 raise |
|
167 except Exception: |
|
168 _warn_unhandled_exception() |
|
169 raise LoadError("invalid Set-Cookie3 format file %r: %r" % |
|
170 (filename, line)) |