|
1 """Mozilla / Netscape cookie loading / saving.""" |
|
2 |
|
3 import re, time |
|
4 |
|
5 from cookielib import (_warn_unhandled_exception, FileCookieJar, LoadError, |
|
6 Cookie, MISSING_FILENAME_TEXT) |
|
7 |
|
8 class MozillaCookieJar(FileCookieJar): |
|
9 """ |
|
10 |
|
11 WARNING: you may want to backup your browser's cookies file if you use |
|
12 this class to save cookies. I *think* it works, but there have been |
|
13 bugs in the past! |
|
14 |
|
15 This class differs from CookieJar only in the format it uses to save and |
|
16 load cookies to and from a file. This class uses the Mozilla/Netscape |
|
17 `cookies.txt' format. lynx uses this file format, too. |
|
18 |
|
19 Don't expect cookies saved while the browser is running to be noticed by |
|
20 the browser (in fact, Mozilla on unix will overwrite your saved cookies if |
|
21 you change them on disk while it's running; on Windows, you probably can't |
|
22 save at all while the browser is running). |
|
23 |
|
24 Note that the Mozilla/Netscape format will downgrade RFC2965 cookies to |
|
25 Netscape cookies on saving. |
|
26 |
|
27 In particular, the cookie version and port number information is lost, |
|
28 together with information about whether or not Path, Port and Discard were |
|
29 specified by the Set-Cookie2 (or Set-Cookie) header, and whether or not the |
|
30 domain as set in the HTTP header started with a dot (yes, I'm aware some |
|
31 domains in Netscape files start with a dot and some don't -- trust me, you |
|
32 really don't want to know any more about this). |
|
33 |
|
34 Note that though Mozilla and Netscape use the same format, they use |
|
35 slightly different headers. The class saves cookies using the Netscape |
|
36 header by default (Mozilla can cope with that). |
|
37 |
|
38 """ |
|
39 magic_re = "#( Netscape)? HTTP Cookie File" |
|
40 header = """\ |
|
41 # Netscape HTTP Cookie File |
|
42 # http://www.netscape.com/newsref/std/cookie_spec.html |
|
43 # This is a generated file! Do not edit. |
|
44 |
|
45 """ |
|
46 |
|
47 def _really_load(self, f, filename, ignore_discard, ignore_expires): |
|
48 now = time.time() |
|
49 |
|
50 magic = f.readline() |
|
51 if not re.search(self.magic_re, magic): |
|
52 f.close() |
|
53 raise LoadError( |
|
54 "%r does not look like a Netscape format cookies file" % |
|
55 filename) |
|
56 |
|
57 try: |
|
58 while 1: |
|
59 line = f.readline() |
|
60 if line == "": break |
|
61 |
|
62 # last field may be absent, so keep any trailing tab |
|
63 if line.endswith("\n"): line = line[:-1] |
|
64 |
|
65 # skip comments and blank lines XXX what is $ for? |
|
66 if (line.strip().startswith(("#", "$")) or |
|
67 line.strip() == ""): |
|
68 continue |
|
69 |
|
70 domain, domain_specified, path, secure, expires, name, value = \ |
|
71 line.split("\t") |
|
72 secure = (secure == "TRUE") |
|
73 domain_specified = (domain_specified == "TRUE") |
|
74 if name == "": |
|
75 # cookies.txt regards 'Set-Cookie: foo' as a cookie |
|
76 # with no name, whereas cookielib regards it as a |
|
77 # cookie with no value. |
|
78 name = value |
|
79 value = None |
|
80 |
|
81 initial_dot = domain.startswith(".") |
|
82 assert domain_specified == initial_dot |
|
83 |
|
84 discard = False |
|
85 if expires == "": |
|
86 expires = None |
|
87 discard = True |
|
88 |
|
89 # assume path_specified is false |
|
90 c = Cookie(0, name, value, |
|
91 None, False, |
|
92 domain, domain_specified, initial_dot, |
|
93 path, False, |
|
94 secure, |
|
95 expires, |
|
96 discard, |
|
97 None, |
|
98 None, |
|
99 {}) |
|
100 if not ignore_discard and c.discard: |
|
101 continue |
|
102 if not ignore_expires and c.is_expired(now): |
|
103 continue |
|
104 self.set_cookie(c) |
|
105 |
|
106 except IOError: |
|
107 raise |
|
108 except Exception: |
|
109 _warn_unhandled_exception() |
|
110 raise LoadError("invalid Netscape format cookies file %r: %r" % |
|
111 (filename, line)) |
|
112 |
|
113 def save(self, filename=None, ignore_discard=False, ignore_expires=False): |
|
114 if filename is None: |
|
115 if self.filename is not None: filename = self.filename |
|
116 else: raise ValueError(MISSING_FILENAME_TEXT) |
|
117 |
|
118 f = open(filename, "w") |
|
119 try: |
|
120 f.write(self.header) |
|
121 now = time.time() |
|
122 for cookie in self: |
|
123 if not ignore_discard and cookie.discard: |
|
124 continue |
|
125 if not ignore_expires and cookie.is_expired(now): |
|
126 continue |
|
127 if cookie.secure: secure = "TRUE" |
|
128 else: secure = "FALSE" |
|
129 if cookie.domain.startswith("."): initial_dot = "TRUE" |
|
130 else: initial_dot = "FALSE" |
|
131 if cookie.expires is not None: |
|
132 expires = str(cookie.expires) |
|
133 else: |
|
134 expires = "" |
|
135 if cookie.value is None: |
|
136 # cookies.txt regards 'Set-Cookie: foo' as a cookie |
|
137 # with no name, whereas cookielib regards it as a |
|
138 # cookie with no value. |
|
139 name = "" |
|
140 value = cookie.name |
|
141 else: |
|
142 name = cookie.name |
|
143 value = cookie.value |
|
144 f.write( |
|
145 "\t".join([cookie.domain, initial_dot, cookie.path, |
|
146 secure, expires, name, value])+ |
|
147 "\n") |
|
148 finally: |
|
149 f.close() |