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