michael@0: /* Any copyright is dedicated to the Public Domain. michael@0: * http://creativecommons.org/publicdomain/zero/1.0/ michael@0: */ michael@0: michael@0: Components.utils.import("resource://gre/modules/Services.jsm"); michael@0: Components.utils.import("resource://gre/modules/NetUtil.jsm"); michael@0: Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: michael@0: const Cc = Components.classes; michael@0: const Ci = Components.interfaces; michael@0: const Cr = Components.results; michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(Services, "cookies", michael@0: "@mozilla.org/cookieService;1", michael@0: "nsICookieService"); michael@0: XPCOMUtils.defineLazyServiceGetter(Services, "cookiemgr", michael@0: "@mozilla.org/cookiemanager;1", michael@0: "nsICookieManager2"); michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(Services, "etld", michael@0: "@mozilla.org/network/effective-tld-service;1", michael@0: "nsIEffectiveTLDService"); michael@0: michael@0: function do_check_throws(f, result, stack) michael@0: { michael@0: if (!stack) michael@0: stack = Components.stack.caller; michael@0: michael@0: try { michael@0: f(); michael@0: } catch (exc) { michael@0: if (exc.result == result) michael@0: return; michael@0: do_throw("expected result " + result + ", caught " + exc, stack); michael@0: } michael@0: do_throw("expected result " + result + ", none thrown", stack); michael@0: } michael@0: michael@0: // Helper to step a generator function and catch a StopIteration exception. michael@0: function do_run_generator(generator) michael@0: { michael@0: try { michael@0: generator.next(); michael@0: } catch (e) { michael@0: if (e != StopIteration) michael@0: do_throw("caught exception " + e, Components.stack.caller); michael@0: } michael@0: } michael@0: michael@0: // Helper to finish a generator function test. michael@0: function do_finish_generator_test(generator) michael@0: { michael@0: do_execute_soon(function() { michael@0: generator.close(); michael@0: do_test_finished(); michael@0: }); michael@0: } michael@0: michael@0: function _observer(generator, topic) { michael@0: Services.obs.addObserver(this, topic, false); michael@0: michael@0: this.generator = generator; michael@0: this.topic = topic; michael@0: } michael@0: michael@0: _observer.prototype = { michael@0: observe: function (subject, topic, data) { michael@0: do_check_eq(this.topic, topic); michael@0: michael@0: Services.obs.removeObserver(this, this.topic); michael@0: michael@0: // Continue executing the generator function. michael@0: if (this.generator) michael@0: do_run_generator(this.generator); michael@0: michael@0: this.generator = null; michael@0: this.topic = null; michael@0: } michael@0: } michael@0: michael@0: // Close the cookie database. If a generator is supplied, it will be invoked michael@0: // once the close is complete. michael@0: function do_close_profile(generator, cleanse) { michael@0: // Register an observer for db close. michael@0: let obs = new _observer(generator, "cookie-db-closed"); michael@0: michael@0: // Close the db. michael@0: let service = Services.cookies.QueryInterface(Ci.nsIObserver); michael@0: service.observe(null, "profile-before-change", cleanse ? cleanse : ""); michael@0: } michael@0: michael@0: // Load the cookie database. If a generator is supplied, it will be invoked michael@0: // once the load is complete. michael@0: function do_load_profile(generator) { michael@0: // Register an observer for read completion. michael@0: let obs = new _observer(generator, "cookie-db-read"); michael@0: michael@0: // Load the profile. michael@0: let service = Services.cookies.QueryInterface(Ci.nsIObserver); michael@0: service.observe(null, "profile-do-change", ""); michael@0: } michael@0: michael@0: // Set a single session cookie using http and test the cookie count michael@0: // against 'expected' michael@0: function do_set_single_http_cookie(uri, channel, expected) { michael@0: Services.cookies.setCookieStringFromHttp(uri, null, null, "foo=bar", null, channel); michael@0: do_check_eq(Services.cookiemgr.countCookiesFromHost(uri.host), expected); michael@0: } michael@0: michael@0: // Set four cookies; with & without channel, http and non-http; and test michael@0: // the cookie count against 'expected' after each set. michael@0: function do_set_cookies(uri, channel, session, expected) { michael@0: let suffix = session ? "" : "; max-age=1000"; michael@0: michael@0: // without channel michael@0: Services.cookies.setCookieString(uri, null, "oh=hai" + suffix, null); michael@0: do_check_eq(Services.cookiemgr.countCookiesFromHost(uri.host), expected[0]); michael@0: // with channel michael@0: Services.cookies.setCookieString(uri, null, "can=has" + suffix, channel); michael@0: do_check_eq(Services.cookiemgr.countCookiesFromHost(uri.host), expected[1]); michael@0: // without channel, from http michael@0: Services.cookies.setCookieStringFromHttp(uri, null, null, "cheez=burger" + suffix, null, null); michael@0: do_check_eq(Services.cookiemgr.countCookiesFromHost(uri.host), expected[2]); michael@0: // with channel, from http michael@0: Services.cookies.setCookieStringFromHttp(uri, null, null, "hot=dog" + suffix, null, channel); michael@0: do_check_eq(Services.cookiemgr.countCookiesFromHost(uri.host), expected[3]); michael@0: } michael@0: michael@0: function do_count_enumerator(enumerator) { michael@0: let i = 0; michael@0: while (enumerator.hasMoreElements()) { michael@0: enumerator.getNext(); michael@0: ++i; michael@0: } michael@0: return i; michael@0: } michael@0: michael@0: function do_count_cookies() { michael@0: return do_count_enumerator(Services.cookiemgr.enumerator); michael@0: } michael@0: michael@0: // Helper object to store cookie data. michael@0: function Cookie(name, michael@0: value, michael@0: host, michael@0: path, michael@0: expiry, michael@0: lastAccessed, michael@0: creationTime, michael@0: isSession, michael@0: isSecure, michael@0: isHttpOnly) michael@0: { michael@0: this.name = name; michael@0: this.value = value; michael@0: this.host = host; michael@0: this.path = path; michael@0: this.expiry = expiry; michael@0: this.lastAccessed = lastAccessed; michael@0: this.creationTime = creationTime; michael@0: this.isSession = isSession; michael@0: this.isSecure = isSecure; michael@0: this.isHttpOnly = isHttpOnly; michael@0: michael@0: let strippedHost = host.charAt(0) == '.' ? host.slice(1) : host; michael@0: michael@0: try { michael@0: this.baseDomain = Services.etld.getBaseDomainFromHost(strippedHost); michael@0: } catch (e) { michael@0: if (e.result == Cr.NS_ERROR_HOST_IS_IP_ADDRESS || michael@0: e.result == Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) michael@0: this.baseDomain = strippedHost; michael@0: } michael@0: } michael@0: michael@0: // Object representing a database connection and associated statements. The michael@0: // implementation varies depending on schema version. michael@0: function CookieDatabaseConnection(file, schema) michael@0: { michael@0: // Manually generate a cookies.sqlite file with appropriate rows, columns, michael@0: // and schema version. If it already exists, just set up our statements. michael@0: let exists = file.exists(); michael@0: michael@0: this.db = Services.storage.openDatabase(file); michael@0: this.schema = schema; michael@0: if (!exists) michael@0: this.db.schemaVersion = schema; michael@0: michael@0: switch (schema) { michael@0: case 1: michael@0: { michael@0: if (!exists) { michael@0: this.db.executeSimpleSQL( michael@0: "CREATE TABLE moz_cookies ( \ michael@0: id INTEGER PRIMARY KEY, \ michael@0: name TEXT, \ michael@0: value TEXT, \ michael@0: host TEXT, \ michael@0: path TEXT, \ michael@0: expiry INTEGER, \ michael@0: isSecure INTEGER, \ michael@0: isHttpOnly INTEGER)"); michael@0: } michael@0: michael@0: this.stmtInsert = this.db.createStatement( michael@0: "INSERT INTO moz_cookies ( \ michael@0: id, \ michael@0: name, \ michael@0: value, \ michael@0: host, \ michael@0: path, \ michael@0: expiry, \ michael@0: isSecure, \ michael@0: isHttpOnly) \ michael@0: VALUES ( \ michael@0: :id, \ michael@0: :name, \ michael@0: :value, \ michael@0: :host, \ michael@0: :path, \ michael@0: :expiry, \ michael@0: :isSecure, \ michael@0: :isHttpOnly)"); michael@0: michael@0: this.stmtDelete = this.db.createStatement( michael@0: "DELETE FROM moz_cookies WHERE id = :id"); michael@0: michael@0: break; michael@0: } michael@0: michael@0: case 2: michael@0: { michael@0: if (!exists) { michael@0: this.db.executeSimpleSQL( michael@0: "CREATE TABLE moz_cookies ( \ michael@0: id INTEGER PRIMARY KEY, \ michael@0: name TEXT, \ michael@0: value TEXT, \ michael@0: host TEXT, \ michael@0: path TEXT, \ michael@0: expiry INTEGER, \ michael@0: lastAccessed INTEGER, \ michael@0: isSecure INTEGER, \ michael@0: isHttpOnly INTEGER)"); michael@0: } michael@0: michael@0: this.stmtInsert = this.db.createStatement( michael@0: "INSERT OR REPLACE INTO moz_cookies ( \ michael@0: id, \ michael@0: name, \ michael@0: value, \ michael@0: host, \ michael@0: path, \ michael@0: expiry, \ michael@0: lastAccessed, \ michael@0: isSecure, \ michael@0: isHttpOnly) \ michael@0: VALUES ( \ michael@0: :id, \ michael@0: :name, \ michael@0: :value, \ michael@0: :host, \ michael@0: :path, \ michael@0: :expiry, \ michael@0: :lastAccessed, \ michael@0: :isSecure, \ michael@0: :isHttpOnly)"); michael@0: michael@0: this.stmtDelete = this.db.createStatement( michael@0: "DELETE FROM moz_cookies WHERE id = :id"); michael@0: michael@0: this.stmtUpdate = this.db.createStatement( michael@0: "UPDATE moz_cookies SET lastAccessed = :lastAccessed WHERE id = :id"); michael@0: michael@0: break; michael@0: } michael@0: michael@0: case 3: michael@0: { michael@0: if (!exists) { michael@0: this.db.executeSimpleSQL( michael@0: "CREATE TABLE moz_cookies ( \ michael@0: id INTEGER PRIMARY KEY, \ michael@0: baseDomain TEXT, \ michael@0: name TEXT, \ michael@0: value TEXT, \ michael@0: host TEXT, \ michael@0: path TEXT, \ michael@0: expiry INTEGER, \ michael@0: lastAccessed INTEGER, \ michael@0: isSecure INTEGER, \ michael@0: isHttpOnly INTEGER)"); michael@0: michael@0: this.db.executeSimpleSQL( michael@0: "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain)"); michael@0: } michael@0: michael@0: this.stmtInsert = this.db.createStatement( michael@0: "INSERT INTO moz_cookies ( \ michael@0: id, \ michael@0: baseDomain, \ michael@0: name, \ michael@0: value, \ michael@0: host, \ michael@0: path, \ michael@0: expiry, \ michael@0: lastAccessed, \ michael@0: isSecure, \ michael@0: isHttpOnly) \ michael@0: VALUES ( \ michael@0: :id, \ michael@0: :baseDomain, \ michael@0: :name, \ michael@0: :value, \ michael@0: :host, \ michael@0: :path, \ michael@0: :expiry, \ michael@0: :lastAccessed, \ michael@0: :isSecure, \ michael@0: :isHttpOnly)"); michael@0: michael@0: this.stmtDelete = this.db.createStatement( michael@0: "DELETE FROM moz_cookies WHERE id = :id"); michael@0: michael@0: this.stmtUpdate = this.db.createStatement( michael@0: "UPDATE moz_cookies SET lastAccessed = :lastAccessed WHERE id = :id"); michael@0: michael@0: break; michael@0: } michael@0: michael@0: case 4: michael@0: { michael@0: if (!exists) { michael@0: this.db.executeSimpleSQL( michael@0: "CREATE TABLE moz_cookies ( \ michael@0: id INTEGER PRIMARY KEY, \ michael@0: baseDomain TEXT, \ michael@0: name TEXT, \ michael@0: value TEXT, \ michael@0: host TEXT, \ michael@0: path TEXT, \ michael@0: expiry INTEGER, \ michael@0: lastAccessed INTEGER, \ michael@0: creationTime INTEGER, \ michael@0: isSecure INTEGER, \ michael@0: isHttpOnly INTEGER \ michael@0: CONSTRAINT moz_uniqueid UNIQUE (name, host, path))"); michael@0: michael@0: this.db.executeSimpleSQL( michael@0: "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain)"); michael@0: michael@0: this.db.executeSimpleSQL( michael@0: "PRAGMA journal_mode = WAL"); michael@0: } michael@0: michael@0: this.stmtInsert = this.db.createStatement( michael@0: "INSERT INTO moz_cookies ( \ michael@0: baseDomain, \ michael@0: name, \ michael@0: value, \ michael@0: host, \ michael@0: path, \ michael@0: expiry, \ michael@0: lastAccessed, \ michael@0: creationTime, \ michael@0: isSecure, \ michael@0: isHttpOnly) \ michael@0: VALUES ( \ michael@0: :baseDomain, \ michael@0: :name, \ michael@0: :value, \ michael@0: :host, \ michael@0: :path, \ michael@0: :expiry, \ michael@0: :lastAccessed, \ michael@0: :creationTime, \ michael@0: :isSecure, \ michael@0: :isHttpOnly)"); michael@0: michael@0: this.stmtDelete = this.db.createStatement( michael@0: "DELETE FROM moz_cookies \ michael@0: WHERE name = :name AND host = :host AND path = :path"); michael@0: michael@0: this.stmtUpdate = this.db.createStatement( michael@0: "UPDATE moz_cookies SET lastAccessed = :lastAccessed \ michael@0: WHERE name = :name AND host = :host AND path = :path"); michael@0: michael@0: break; michael@0: } michael@0: michael@0: default: michael@0: do_throw("unrecognized schemaVersion!"); michael@0: } michael@0: } michael@0: michael@0: CookieDatabaseConnection.prototype = michael@0: { michael@0: insertCookie: function(cookie) michael@0: { michael@0: if (!(cookie instanceof Cookie)) michael@0: do_throw("not a cookie"); michael@0: michael@0: switch (this.schema) michael@0: { michael@0: case 1: michael@0: this.stmtInsert.bindByName("id", cookie.creationTime); michael@0: this.stmtInsert.bindByName("name", cookie.name); michael@0: this.stmtInsert.bindByName("value", cookie.value); michael@0: this.stmtInsert.bindByName("host", cookie.host); michael@0: this.stmtInsert.bindByName("path", cookie.path); michael@0: this.stmtInsert.bindByName("expiry", cookie.expiry); michael@0: this.stmtInsert.bindByName("isSecure", cookie.isSecure); michael@0: this.stmtInsert.bindByName("isHttpOnly", cookie.isHttpOnly); michael@0: break; michael@0: michael@0: case 2: michael@0: this.stmtInsert.bindByName("id", cookie.creationTime); michael@0: this.stmtInsert.bindByName("name", cookie.name); michael@0: this.stmtInsert.bindByName("value", cookie.value); michael@0: this.stmtInsert.bindByName("host", cookie.host); michael@0: this.stmtInsert.bindByName("path", cookie.path); michael@0: this.stmtInsert.bindByName("expiry", cookie.expiry); michael@0: this.stmtInsert.bindByName("lastAccessed", cookie.lastAccessed); michael@0: this.stmtInsert.bindByName("isSecure", cookie.isSecure); michael@0: this.stmtInsert.bindByName("isHttpOnly", cookie.isHttpOnly); michael@0: break; michael@0: michael@0: case 3: michael@0: this.stmtInsert.bindByName("id", cookie.creationTime); michael@0: this.stmtInsert.bindByName("baseDomain", cookie.baseDomain); michael@0: this.stmtInsert.bindByName("name", cookie.name); michael@0: this.stmtInsert.bindByName("value", cookie.value); michael@0: this.stmtInsert.bindByName("host", cookie.host); michael@0: this.stmtInsert.bindByName("path", cookie.path); michael@0: this.stmtInsert.bindByName("expiry", cookie.expiry); michael@0: this.stmtInsert.bindByName("lastAccessed", cookie.lastAccessed); michael@0: this.stmtInsert.bindByName("isSecure", cookie.isSecure); michael@0: this.stmtInsert.bindByName("isHttpOnly", cookie.isHttpOnly); michael@0: break; michael@0: michael@0: case 4: michael@0: this.stmtInsert.bindByName("baseDomain", cookie.baseDomain); michael@0: this.stmtInsert.bindByName("name", cookie.name); michael@0: this.stmtInsert.bindByName("value", cookie.value); michael@0: this.stmtInsert.bindByName("host", cookie.host); michael@0: this.stmtInsert.bindByName("path", cookie.path); michael@0: this.stmtInsert.bindByName("expiry", cookie.expiry); michael@0: this.stmtInsert.bindByName("lastAccessed", cookie.lastAccessed); michael@0: this.stmtInsert.bindByName("creationTime", cookie.creationTime); michael@0: this.stmtInsert.bindByName("isSecure", cookie.isSecure); michael@0: this.stmtInsert.bindByName("isHttpOnly", cookie.isHttpOnly); michael@0: break; michael@0: michael@0: default: michael@0: do_throw("unrecognized schemaVersion!"); michael@0: } michael@0: michael@0: do_execute_stmt(this.stmtInsert); michael@0: }, michael@0: michael@0: deleteCookie: function(cookie) michael@0: { michael@0: if (!(cookie instanceof Cookie)) michael@0: do_throw("not a cookie"); michael@0: michael@0: switch (this.db.schemaVersion) michael@0: { michael@0: case 1: michael@0: case 2: michael@0: case 3: michael@0: this.stmtDelete.bindByName("id", cookie.creationTime); michael@0: break; michael@0: michael@0: case 4: michael@0: this.stmtDelete.bindByName("name", cookie.name); michael@0: this.stmtDelete.bindByName("host", cookie.host); michael@0: this.stmtDelete.bindByName("path", cookie.path); michael@0: break; michael@0: michael@0: default: michael@0: do_throw("unrecognized schemaVersion!"); michael@0: } michael@0: michael@0: do_execute_stmt(this.stmtDelete); michael@0: }, michael@0: michael@0: updateCookie: function(cookie) michael@0: { michael@0: if (!(cookie instanceof Cookie)) michael@0: do_throw("not a cookie"); michael@0: michael@0: switch (this.db.schemaVersion) michael@0: { michael@0: case 1: michael@0: do_throw("can't update a schema 1 cookie!"); michael@0: michael@0: case 2: michael@0: case 3: michael@0: this.stmtUpdate.bindByName("id", cookie.creationTime); michael@0: this.stmtUpdate.bindByName("lastAccessed", cookie.lastAccessed); michael@0: break; michael@0: michael@0: case 4: michael@0: this.stmtDelete.bindByName("name", cookie.name); michael@0: this.stmtDelete.bindByName("host", cookie.host); michael@0: this.stmtDelete.bindByName("path", cookie.path); michael@0: this.stmtUpdate.bindByName("lastAccessed", cookie.lastAccessed); michael@0: break; michael@0: michael@0: default: michael@0: do_throw("unrecognized schemaVersion!"); michael@0: } michael@0: michael@0: do_execute_stmt(this.stmtUpdate); michael@0: }, michael@0: michael@0: close: function() michael@0: { michael@0: this.stmtInsert.finalize(); michael@0: this.stmtDelete.finalize(); michael@0: if (this.stmtUpdate) michael@0: this.stmtUpdate.finalize(); michael@0: this.db.close(); michael@0: michael@0: this.stmtInsert = null; michael@0: this.stmtDelete = null; michael@0: this.stmtUpdate = null; michael@0: this.db = null; michael@0: } michael@0: } michael@0: michael@0: function do_get_cookie_file(profile) michael@0: { michael@0: let file = profile.clone(); michael@0: file.append("cookies.sqlite"); michael@0: return file; michael@0: } michael@0: michael@0: // Count the cookies from 'host' in a database. If 'host' is null, count all michael@0: // cookies. michael@0: function do_count_cookies_in_db(connection, host) michael@0: { michael@0: let select = null; michael@0: if (host) { michael@0: select = connection.createStatement( michael@0: "SELECT COUNT(1) FROM moz_cookies WHERE host = :host"); michael@0: select.bindByName("host", host); michael@0: } else { michael@0: select = connection.createStatement( michael@0: "SELECT COUNT(1) FROM moz_cookies"); michael@0: } michael@0: michael@0: select.executeStep(); michael@0: let result = select.getInt32(0); michael@0: select.reset(); michael@0: select.finalize(); michael@0: return result; michael@0: } michael@0: michael@0: // Execute 'stmt', ensuring that we reset it if it throws. michael@0: function do_execute_stmt(stmt) michael@0: { michael@0: try { michael@0: stmt.executeStep(); michael@0: stmt.reset(); michael@0: } catch (e) { michael@0: stmt.reset(); michael@0: throw e; michael@0: } michael@0: }