blob: 6d5ce185e55b29a3f1341fe03a585fb02a2e8842 [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
Neal Norwitz3e7de592005-12-23 21:24:35 +000015from cookielib import (reraise_unmasked_exceptions, FileCookieJar, LoadError,
16 Cookie, MISSING_FILENAME_TEXT, join_header_words, split_header_words,
Andrew M. Kuchling33ad28b2004-08-31 11:38:12 +000017 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
Neal Norwitz3e7de592005-12-23 21:24:35 +000096 raise LoadError(msg)
Martin v. Löwis2a6ba902004-05-31 18:22:40 +000097
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]
Martin v. Löwis2a6ba902004-05-31 18:22:40 +0000118 standard = {}
119 rest = {}
120 for k in boolean_attrs:
121 standard[k] = False
122 for k, v in data[1:]:
123 if k is not None:
124 lc = k.lower()
125 else:
126 lc = None
127 # don't lose case distinction for unknown fields
128 if (lc in value_attrs) or (lc in boolean_attrs):
129 k = lc
130 if k in boolean_attrs:
131 if v is None: v = True
132 standard[k] = v
133 elif k in value_attrs:
134 standard[k] = v
135 else:
136 rest[k] = v
137
138 h = standard.get
139 expires = h("expires")
140 discard = h("discard")
141 if expires is not None:
142 expires = iso2time(expires)
143 if expires is None:
144 discard = True
145 domain = h("domain")
146 domain_specified = domain.startswith(".")
147 c = Cookie(h("version"), name, value,
148 h("port"), h("port_spec"),
149 domain, domain_specified, h("domain_dot"),
150 h("path"), h("path_spec"),
151 h("secure"),
152 expires,
153 discard,
154 h("comment"),
155 h("commenturl"),
156 rest)
157 if not ignore_discard and c.discard:
158 continue
159 if not ignore_expires and c.is_expired(now):
160 continue
161 self.set_cookie(c)
162 except:
163 reraise_unmasked_exceptions((IOError,))
Neal Norwitz3e7de592005-12-23 21:24:35 +0000164 raise LoadError("invalid Set-Cookie3 format file %s" % filename)