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