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