source: py-scraping/mechanize/_firefox3cookiejar.py@ 173

Last change on this file since 173 was 106, checked in by Rick van der Zwet, 15 years ago

Initial commit...

File size: 8.1 KB
Line 
1"""Firefox 3 "cookies.sqlite" cookie persistence.
2
3Copyright 2008 John J Lee <jjl@pobox.com>
4
5This code is free software; you can redistribute it and/or modify it
6under the terms of the BSD or ZPL 2.1 licenses (see the file
7COPYING.txt included with the distribution).
8
9"""
10
11import logging
12import time
13import sqlite3
14
15from _clientcookie import CookieJar, Cookie, MappingIterator
16from _util import isstringlike, experimental
17debug = logging.getLogger("mechanize.cookies").debug
18
19
20class Firefox3CookieJar(CookieJar):
21
22 """Firefox 3 cookie jar.
23
24 The cookies are stored in Firefox 3's "cookies.sqlite" format.
25
26 Constructor arguments:
27
28 filename: filename of cookies.sqlite (typically found at the top level
29 of a firefox profile directory)
30 autoconnect: as a convenience, connect to the SQLite cookies database at
31 Firefox3CookieJar construction time (default True)
32 policy: an object satisfying the mechanize.CookiePolicy interface
33
34 Note that this is NOT a FileCookieJar, and there are no .load(),
35 .save() or .restore() methods. The database is in sync with the
36 cookiejar object's state after each public method call.
37
38 Following Firefox's own behaviour, session cookies are never saved to
39 the database.
40
41 The file is created, and an sqlite database written to it, if it does
42 not already exist. The moz_cookies database table is created if it does
43 not already exist.
44 """
45
46 # XXX
47 # handle DatabaseError exceptions
48 # add a FileCookieJar (explicit .save() / .revert() / .load() methods)
49
50 def __init__(self, filename, autoconnect=True, policy=None):
51 experimental("Firefox3CookieJar is experimental code")
52 CookieJar.__init__(self, policy)
53 if filename is not None and not isstringlike(filename):
54 raise ValueError("filename must be string-like")
55 self.filename = filename
56 self._conn = None
57 if autoconnect:
58 self.connect()
59
60 def connect(self):
61 self._conn = sqlite3.connect(self.filename)
62 self._conn.isolation_level = "DEFERRED"
63 self._create_table_if_necessary()
64
65 def close(self):
66 self._conn.close()
67
68 def _transaction(self, func):
69 try:
70 cur = self._conn.cursor()
71 try:
72 result = func(cur)
73 finally:
74 cur.close()
75 except:
76 self._conn.rollback()
77 raise
78 else:
79 self._conn.commit()
80 return result
81
82 def _execute(self, query, params=()):
83 return self._transaction(lambda cur: cur.execute(query, params))
84
85 def _query(self, query, params=()):
86 # XXX should we bother with a transaction?
87 cur = self._conn.cursor()
88 try:
89 cur.execute(query, params)
90 for row in cur.fetchall():
91 yield row
92 finally:
93 cur.close()
94
95 def _create_table_if_necessary(self):
96 self._execute("""\
97CREATE TABLE IF NOT EXISTS moz_cookies (id INTEGER PRIMARY KEY, name TEXT,
98 value TEXT, host TEXT, path TEXT,expiry INTEGER,
99 lastAccessed INTEGER, isSecure INTEGER, isHttpOnly INTEGER)""")
100
101 def _cookie_from_row(self, row):
102 (pk, name, value, domain, path, expires,
103 last_accessed, secure, http_only) = row
104
105 version = 0
106 domain = domain.encode("ascii", "ignore")
107 path = path.encode("ascii", "ignore")
108 name = name.encode("ascii", "ignore")
109 value = value.encode("ascii", "ignore")
110 secure = bool(secure)
111
112 # last_accessed isn't a cookie attribute, so isn't added to rest
113 rest = {}
114 if http_only:
115 rest["HttpOnly"] = None
116
117 if name == "":
118 name = value
119 value = None
120
121 initial_dot = domain.startswith(".")
122 domain_specified = initial_dot
123
124 discard = False
125 if expires == "":
126 expires = None
127 discard = True
128
129 return Cookie(version, name, value,
130 None, False,
131 domain, domain_specified, initial_dot,
132 path, False,
133 secure,
134 expires,
135 discard,
136 None,
137 None,
138 rest)
139
140 def clear(self, domain=None, path=None, name=None):
141 CookieJar.clear(self, domain, path, name)
142 where_parts = []
143 sql_params = []
144 if domain is not None:
145 where_parts.append("host = ?")
146 sql_params.append(domain)
147 if path is not None:
148 where_parts.append("path = ?")
149 sql_params.append(path)
150 if name is not None:
151 where_parts.append("name = ?")
152 sql_params.append(name)
153 where = " AND ".join(where_parts)
154 if where:
155 where = " WHERE " + where
156 def clear(cur):
157 cur.execute("DELETE FROM moz_cookies%s" % where,
158 tuple(sql_params))
159 self._transaction(clear)
160
161 def _row_from_cookie(self, cookie, cur):
162 expires = cookie.expires
163 if cookie.discard:
164 expires = ""
165
166 domain = unicode(cookie.domain)
167 path = unicode(cookie.path)
168 name = unicode(cookie.name)
169 value = unicode(cookie.value)
170 secure = bool(int(cookie.secure))
171
172 if value is None:
173 value = name
174 name = ""
175
176 last_accessed = int(time.time())
177 http_only = cookie.has_nonstandard_attr("HttpOnly")
178
179 query = cur.execute("""SELECT MAX(id) + 1 from moz_cookies""")
180 pk = query.fetchone()[0]
181 if pk is None:
182 pk = 1
183
184 return (pk, name, value, domain, path, expires,
185 last_accessed, secure, http_only)
186
187 def set_cookie(self, cookie):
188 if cookie.discard:
189 CookieJar.set_cookie(self, cookie)
190 return
191
192 def set_cookie(cur):
193 # XXX
194 # is this RFC 2965-correct?
195 # could this do an UPDATE instead?
196 row = self._row_from_cookie(cookie, cur)
197 name, unused, domain, path = row[1:5]
198 cur.execute("""\
199DELETE FROM moz_cookies WHERE host = ? AND path = ? AND name = ?""",
200 (domain, path, name))
201 cur.execute("""\
202INSERT INTO moz_cookies VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
203""", row)
204 self._transaction(set_cookie)
205
206 def __iter__(self):
207 # session (non-persistent) cookies
208 for cookie in MappingIterator(self._cookies):
209 yield cookie
210 # persistent cookies
211 for row in self._query("""\
212SELECT * FROM moz_cookies ORDER BY name, path, host"""):
213 yield self._cookie_from_row(row)
214
215 def _cookies_for_request(self, request):
216 session_cookies = CookieJar._cookies_for_request(self, request)
217 def get_cookies(cur):
218 query = cur.execute("SELECT host from moz_cookies")
219 domains = [row[0] for row in query.fetchmany()]
220 cookies = []
221 for domain in domains:
222 cookies += self._persistent_cookies_for_domain(domain,
223 request, cur)
224 return cookies
225 persistent_coookies = self._transaction(get_cookies)
226 return session_cookies + persistent_coookies
227
228 def _persistent_cookies_for_domain(self, domain, request, cur):
229 cookies = []
230 if not self._policy.domain_return_ok(domain, request):
231 return []
232 debug("Checking %s for cookies to return", domain)
233 query = cur.execute("""\
234SELECT * from moz_cookies WHERE host = ? ORDER BY path""",
235 (domain,))
236 cookies = [self._cookie_from_row(row) for row in query.fetchmany()]
237 last_path = None
238 r = []
239 for cookie in cookies:
240 if (cookie.path != last_path and
241 not self._policy.path_return_ok(cookie.path, request)):
242 last_path = cookie.path
243 continue
244 if not self._policy.return_ok(cookie, request):
245 debug(" not returning cookie")
246 continue
247 debug(" it's a match")
248 r.append(cookie)
249 return r
Note: See TracBrowser for help on using the repository browser.