gecko/services/sync/modules/stores.js

946 lines
31 KiB
JavaScript

/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Bookmarks Sync.
*
* The Initial Developer of the Original Code is Mozilla.
* Portions created by the Initial Developer are Copyright (C) 2007
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Dan Mills <thunder@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
const EXPORTED_SYMBOLS = ['Store', 'SnapshotStore', 'BookmarksStore',
'HistoryStore', 'CookieStore'];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://weave/log4moz.js");
Cu.import("resource://weave/constants.js");
Cu.import("resource://weave/util.js");
Cu.import("resource://weave/async.js");
Function.prototype.async = Async.sugar;
/*
* Data Stores
* These can wrap, serialize items and apply commands
*/
function Store() {
this._init();
}
Store.prototype = {
_logName: "Store",
_yieldDuringApply: true,
__json: null,
get _json() {
if (!this.__json)
this.__json = Cc["@mozilla.org/dom/json;1"].
createInstance(Ci.nsIJSON);
return this.__json;
},
_init: function Store__init() {
this._log = Log4Moz.Service.getLogger("Service." + this._logName);
},
applyCommands: function Store_applyCommands(commandList) {
let self = yield;
let timer, listener;
if (this._yieldDuringApply) {
listener = new Utils.EventListener(self.cb);
timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
}
for (var i = 0; i < commandList.length; i++) {
if (this._yieldDuringApply) {
timer.initWithCallback(listener, 0, timer.TYPE_ONE_SHOT);
yield; // Yield to main loop
}
var command = commandList[i];
this._log.debug("Processing command: " + this._json.encode(command));
switch (command["action"]) {
case "create":
this._createCommand(command);
break;
case "remove":
this._removeCommand(command);
break;
case "edit":
this._editCommand(command);
break;
default:
this._log.error("unknown action in command: " + command["action"]);
break;
}
}
self.done();
},
// override these in derived objects
wrap: function Store_wrap() {},
wipe: function Store_wipe() {},
resetGUIDs: function Store_resetGUIDs() {}
};
function SnapshotStore(name) {
this._init(name);
}
SnapshotStore.prototype = {
_logName: "SnapStore",
_filename: null,
get filename() {
if (this._filename === null)
throw "filename is null";
return this._filename;
},
set filename(value) {
this._filename = value + ".json";
},
__dirSvc: null,
get _dirSvc() {
if (!this.__dirSvc)
this.__dirSvc = Cc["@mozilla.org/file/directory_service;1"].
getService(Ci.nsIProperties);
return this.__dirSvc;
},
// Last synced tree, version, and GUID (to detect if the store has
// been completely replaced and invalidate the snapshot)
_data: {},
get data() { return this._data; },
set data(value) { this._data = value; },
_version: 0,
get version() { return this._version; },
set version(value) { this._version = value; },
_GUID: null,
get GUID() {
if (!this._GUID) {
let uuidgen = Cc["@mozilla.org/uuid-generator;1"].
getService(Ci.nsIUUIDGenerator);
this._GUID = uuidgen.generateUUID().toString().replace(/[{}]/g, '');
}
return this._GUID;
},
set GUID(GUID) {
this._GUID = GUID;
},
_init: function SStore__init(name) {
this.filename = name;
this._log = Log4Moz.Service.getLogger("Service." + this._logName);
},
_createCommand: function SStore__createCommand(command) {
this._data[command.GUID] = Utils.deepCopy(command.data);
},
_removeCommand: function SStore__removeCommand(command) {
delete this._data[command.GUID];
},
_editCommand: function SStore__editCommand(command) {
if ("GUID" in command.data) {
// special-case guid changes
let newGUID = command.data.GUID,
oldGUID = command.GUID;
this._data[newGUID] = this._data[oldGUID];
delete this._data[oldGUID]
for (let GUID in this._data) {
if (this._data[GUID].parentGUID == oldGUID)
this._data[GUID].parentGUID = newGUID;
}
}
for (let prop in command.data) {
if (prop == "GUID")
continue;
this._data[command.GUID][prop] = command.data[prop];
}
},
save: function SStore_save() {
this._log.info("Saving snapshot to disk");
let file = this._dirSvc.get("ProfD", Ci.nsIFile);
file.QueryInterface(Ci.nsILocalFile);
file.append("weave");
file.append("snapshots");
file.append(this.filename);
if (!file.exists())
file.create(file.NORMAL_FILE_TYPE, PERMS_FILE);
let out = {version: this.version,
GUID: this.GUID,
snapshot: this.data};
out = this._json.encode(out);
let [fos] = Utils.open(file, ">");
fos.writeString(out);
fos.close();
},
load: function SStore_load() {
let file = this._dirSvc.get("ProfD", Ci.nsIFile);
file.append("weave");
file.append("snapshots");
file.append(this.filename);
if (!file.exists())
return;
try {
let [is] = Utils.open(file, "<");
let json = Utils.readStream(is);
is.close();
json = this._json.decode(json);
if (json && 'snapshot' in json && 'version' in json && 'GUID' in json) {
this._log.info("Read saved snapshot from disk");
this.data = json.snapshot;
this.version = json.version;
this.GUID = json.GUID;
}
} catch (e) {
this._log.warn("Could not parse saved snapshot; reverting to initial-sync state");
this._log.debug("Exception: " + e);
}
},
serialize: function SStore_serialize() {
let json = this._json.encode(this.data);
json = json.replace(/:{type/g, ":\n\t{type");
json = json.replace(/}, /g, "},\n ");
json = json.replace(/, parentGUID/g, ",\n\t parentGUID");
json = json.replace(/, index/g, ",\n\t index");
json = json.replace(/, title/g, ",\n\t title");
json = json.replace(/, URI/g, ",\n\t URI");
json = json.replace(/, tags/g, ",\n\t tags");
json = json.replace(/, keyword/g, ",\n\t keyword");
return json;
},
wrap: function SStore_wrap() {
return this.data;
},
wipe: function SStore_wipe() {
this.data = {};
this.version = -1;
this.GUID = null;
this.save();
}
};
SnapshotStore.prototype.__proto__ = new Store();
function BookmarksStore() {
this._init();
}
BookmarksStore.prototype = {
_logName: "BStore",
__bms: null,
get _bms() {
if (!this.__bms)
this.__bms = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
getService(Ci.nsINavBookmarksService);
return this.__bms;
},
__hsvc: null,
get _hsvc() {
if (!this.__hsvc)
this.__hsvc = Cc["@mozilla.org/browser/nav-history-service;1"].
getService(Ci.nsINavHistoryService);
return this.__hsvc;
},
__ls: null,
get _ls() {
if (!this.__ls)
this.__ls = Cc["@mozilla.org/browser/livemark-service;2"].
getService(Ci.nsILivemarkService);
return this.__ls;
},
__ms: null,
get _ms() {
if (!this.__ms)
this.__ms = Cc["@mozilla.org/microsummary/service;1"].
getService(Ci.nsIMicrosummaryService);
return this.__ms;
},
__ts: null,
get _ts() {
if (!this.__ts)
this.__ts = Cc["@mozilla.org/browser/tagging-service;1"].
getService(Ci.nsITaggingService);
return this.__ts;
},
__ans: null,
get _ans() {
if (!this.__ans)
this.__ans = Cc["@mozilla.org/browser/annotation-service;1"].
getService(Ci.nsIAnnotationService);
return this.__ans;
},
_getItemIdForGUID: function BStore__getItemIdForGUID(GUID) {
switch (GUID) {
case "menu":
return this._bms.bookmarksMenuFolder;
case "toolbar":
return this._bms.toolbarFolder;
case "unfiled":
return this._bms.unfiledBookmarksFolder;
default:
return this._bms.getItemIdForGUID(GUID);
}
return null;
},
_createCommand: function BStore__createCommand(command) {
let newId;
let parentId = this._getItemIdForGUID(command.data.parentGUID);
if (parentId < 0) {
this._log.warn("Creating node with unknown parent -> reparenting to root");
parentId = this._bms.bookmarksMenuFolder;
}
switch (command.data.type) {
case "query":
case "bookmark":
case "microsummary": {
this._log.debug(" -> creating bookmark \"" + command.data.title + "\"");
let URI = Utils.makeURI(command.data.URI);
newId = this._bms.insertBookmark(parentId,
URI,
command.data.index,
command.data.title);
this._ts.untagURI(URI, null);
this._ts.tagURI(URI, command.data.tags);
this._bms.setKeywordForBookmark(newId, command.data.keyword);
if (command.data.description) {
this._ans.setItemAnnotation(newId, "bookmarkProperties/description",
command.data.description, 0,
this._ans.EXPIRE_NEVER);
}
if (command.data.type == "microsummary") {
this._log.debug(" \-> is a microsummary");
let genURI = Utils.makeURI(command.data.generatorURI);
try {
let micsum = this._ms.createMicrosummary(URI, genURI);
this._ms.setMicrosummary(newId, micsum);
}
catch(ex) { /* ignore "missing local generator" exceptions */ }
}
} break;
case "folder":
this._log.debug(" -> creating folder \"" + command.data.title + "\"");
newId = this._bms.createFolder(parentId,
command.data.title,
command.data.index);
break;
case "livemark":
this._log.debug(" -> creating livemark \"" + command.data.title + "\"");
newId = this._ls.createLivemark(parentId,
command.data.title,
Utils.makeURI(command.data.siteURI),
Utils.makeURI(command.data.feedURI),
command.data.index);
break;
case "mounted-share":
this._log.debug(" -> creating share mountpoint \"" + command.data.title + "\"");
newId = this._bms.createFolder(parentId,
command.data.title,
command.data.index);
this._ans.setItemAnnotation(newId, "weave/mounted-share-id",
command.data.mountId, 0, this._ans.EXPIRE_NEVER);
break;
case "separator":
this._log.debug(" -> creating separator");
newId = this._bms.insertSeparator(parentId, command.data.index);
break;
default:
this._log.error("_createCommand: Unknown item type: " + command.data.type);
break;
}
if (newId)
this._bms.setItemGUID(newId, command.GUID);
},
_removeCommand: function BStore__removeCommand(command) {
if (command.GUID == "menu" ||
command.GUID == "toolbar" ||
command.GUID == "unfiled") {
this._log.warn("Attempted to remove root node (" + command.GUID +
"). Skipping command.");
return;
}
var itemId = this._bms.getItemIdForGUID(command.GUID);
if (itemId < 0) {
this._log.warn("Attempted to remove item " + command.GUID +
", but it does not exist. Skipping.");
return;
}
var type = this._bms.getItemType(itemId);
switch (type) {
case this._bms.TYPE_BOOKMARK:
this._log.debug(" -> removing bookmark " + command.GUID);
this._bms.removeItem(itemId);
break;
case this._bms.TYPE_FOLDER:
this._log.debug(" -> removing folder " + command.GUID);
this._bms.removeFolder(itemId);
break;
case this._bms.TYPE_SEPARATOR:
this._log.debug(" -> removing separator " + command.GUID);
this._bms.removeItem(itemId);
break;
default:
this._log.error("removeCommand: Unknown item type: " + type);
break;
}
},
_editCommand: function BStore__editCommand(command) {
if (command.GUID == "menu" ||
command.GUID == "toolbar" ||
command.GUID == "unfiled") {
this._log.warn("Attempted to edit root node (" + command.GUID +
"). Skipping command.");
return;
}
var itemId = this._bms.getItemIdForGUID(command.GUID);
if (itemId < 0) {
this._log.warn("Item for GUID " + command.GUID + " not found. Skipping.");
return;
}
for (let key in command.data) {
switch (key) {
case "type":
// all commands have this to help in reconciliation, but it makes
// no sense to edit it
break;
case "GUID":
var existing = this._getItemIdForGUID(command.data.GUID);
if (existing < 0)
this._bms.setItemGUID(itemId, command.data.GUID);
else
this._log.warn("Can't change GUID " + command.GUID +
" to " + command.data.GUID + ": GUID already exists.");
break;
case "title":
this._bms.setItemTitle(itemId, command.data.title);
break;
case "URI":
this._bms.changeBookmarkURI(itemId, Utils.makeURI(command.data.URI));
break;
case "index":
this._bms.moveItem(itemId, this._bms.getFolderIdForItem(itemId),
command.data.index);
break;
case "parentGUID": {
let index = -1;
if (command.data.index && command.data.index >= 0)
index = command.data.index;
this._bms.moveItem(
itemId, this._getItemIdForGUID(command.data.parentGUID), index);
} break;
case "tags": {
let tagsURI = this._bms.getBookmarkURI(itemId);
this._ts.untagURI(tagsURI, null);
this._ts.tagURI(tagsURI, command.data.tags);
} break;
case "keyword":
this._bms.setKeywordForBookmark(itemId, command.data.keyword);
break;
case "description":
if (command.data.description) {
this._ans.setItemAnnotation(itemId, "bookmarkProperties/description",
command.data.description, 0,
this._ans.EXPIRE_NEVER);
}
break;
case "generatorURI": {
let micsumURI = Utils.makeURI(this._bms.getBookmarkURI(itemId));
let genURI = Utils.makeURI(command.data.generatorURI);
let micsum = this._ms.createMicrosummary(micsumURI, genURI);
this._ms.setMicrosummary(itemId, micsum);
} break;
case "siteURI":
this._ls.setSiteURI(itemId, Utils.makeURI(command.data.siteURI));
break;
case "feedURI":
this._ls.setFeedURI(itemId, Utils.makeURI(command.data.feedURI));
break;
default:
this._log.warn("Can't change item property: " + key);
break;
}
}
},
_getNode: function BSS__getNode(folder) {
let query = this._hsvc.getNewQuery();
query.setFolders([folder], 1);
return this._hsvc.executeQuery(query, this._hsvc.getNewQueryOptions()).root;
},
__wrap: function BSS___wrap(node, items, parentGUID, index, guidOverride) {
let GUID, item;
// we override the guid for the root items, "menu", "toolbar", etc.
if (guidOverride) {
GUID = guidOverride;
item = {};
} else {
GUID = this._bms.getItemGUID(node.itemId);
item = {parentGUID: parentGUID, index: index};
}
if (node.type == node.RESULT_TYPE_FOLDER) {
if (this._ls.isLivemark(node.itemId)) {
item.type = "livemark";
let siteURI = this._ls.getSiteURI(node.itemId);
let feedURI = this._ls.getFeedURI(node.itemId);
item.siteURI = siteURI? siteURI.spec : "";
item.feedURI = feedURI? feedURI.spec : "";
} else if (this._ans.itemHasAnnotation(node.itemId,
"weave/mounted-share-id")) {
item.type = "mounted-share";
item.title = node.title;
item.mountId = this._ans.getItemAnnotation(node.itemId,
"weave/mounted-share-id");
} else {
item.type = "folder";
node.QueryInterface(Ci.nsINavHistoryQueryResultNode);
node.containerOpen = true;
for (var i = 0; i < node.childCount; i++) {
this.__wrap(node.getChild(i), items, GUID, i);
}
}
if (!guidOverride)
item.title = node.title; // no titles for root nodes
} else if (node.type == node.RESULT_TYPE_URI ||
node.type == node.RESULT_TYPE_QUERY) {
if (this._ms.hasMicrosummary(node.itemId)) {
item.type = "microsummary";
let micsum = this._ms.getMicrosummary(node.itemId);
item.generatorURI = micsum.generator.uri.spec; // breaks local generators
} else if (node.type == node.RESULT_TYPE_QUERY) {
item.type = "query";
item.title = node.title;
} else {
item.type = "bookmark";
item.title = node.title;
}
try {
item.description =
this._ans.getItemAnnotation(node.itemId, "bookmarkProperties/description");
} catch (e) {
item.description = undefined;
}
item.URI = node.uri;
item.tags = this._ts.getTagsForURI(Utils.makeURI(node.uri), {});
item.keyword = this._bms.getKeywordForBookmark(node.itemId);
} else if (node.type == node.RESULT_TYPE_SEPARATOR) {
item.type = "separator";
} else {
this._log.warn("Warning: unknown item type, cannot serialize: " + node.type);
return;
}
items[GUID] = item;
},
// helper
_wrap: function BStore__wrap(node, items, rootName) {
return this.__wrap(node, items, null, null, rootName);
},
_wrapMount: function BStore__wrapMount(node, id) {
if (node.type != node.RESULT_TYPE_FOLDER)
throw "Trying to wrap a non-folder mounted share";
let GUID = this._bms.getItemGUID(node.itemId);
let ret = {rootGUID: GUID, userid: id, snapshot: {}};
node.QueryInterface(Ci.nsINavHistoryQueryResultNode);
node.containerOpen = true;
for (var i = 0; i < node.childCount; i++) {
this.__wrap(node.getChild(i), ret.snapshot, GUID, i);
}
// remove any share mountpoints
for (let guid in ret.snapshot) {
if (ret.snapshot[guid].type == "mounted-share")
delete ret.snapshot[guid];
}
return ret;
},
_resetGUIDs: function BSS__resetGUIDs(node) {
if (this._ans.itemHasAnnotation(node.itemId, "placesInternal/GUID"))
this._ans.removeItemAnnotation(node.itemId, "placesInternal/GUID");
if (node.type == node.RESULT_TYPE_FOLDER &&
!this._ls.isLivemark(node.itemId)) {
node.QueryInterface(Ci.nsINavHistoryQueryResultNode);
node.containerOpen = true;
for (var i = 0; i < node.childCount; i++) {
this._resetGUIDs(node.getChild(i));
}
}
},
findMounts: function BStore_findMounts() {
let ret = [];
let a = this._ans.getItemsWithAnnotation("weave/mounted-share-id", {});
for (let i = 0; i < a.length; i++) {
let id = this._ans.getItemAnnotation(a[i], "weave/mounted-share-id");
ret.push(this._wrapMount(this._getNode(a[i]), id));
}
return ret;
},
wrap: function BStore_wrap() {
var items = {};
this._wrap(this._getNode(this._bms.bookmarksMenuFolder), items, "menu");
this._wrap(this._getNode(this._bms.toolbarFolder), items, "toolbar");
this._wrap(this._getNode(this._bms.unfiledBookmarksFolder), items, "unfiled");
return items;
},
wipe: function BStore_wipe() {
this._bms.removeFolderChildren(this._bms.bookmarksMenuFolder);
this._bms.removeFolderChildren(this._bms.toolbarFolder);
this._bms.removeFolderChildren(this._bms.unfiledBookmarksFolder);
},
resetGUIDs: function BStore_resetGUIDs() {
this._resetGUIDs(this._getNode(this._bms.bookmarksMenuFolder));
this._resetGUIDs(this._getNode(this._bms.toolbarFolder));
this._resetGUIDs(this._getNode(this._bms.unfiledBookmarksFolder));
}
};
BookmarksStore.prototype.__proto__ = new Store();
function HistoryStore() {
this._init();
}
HistoryStore.prototype = {
_logName: "HistStore",
__hsvc: null,
get _hsvc() {
if (!this.__hsvc) {
this.__hsvc = Cc["@mozilla.org/browser/nav-history-service;1"].
getService(Ci.nsINavHistoryService);
this.__hsvc.QueryInterface(Ci.nsIGlobalHistory2);
this.__hsvc.QueryInterface(Ci.nsIBrowserHistory);
}
return this.__hsvc;
},
_createCommand: function HistStore__createCommand(command) {
this._log.debug(" -> creating history entry: " + command.GUID);
try {
let uri = Utils.makeURI(command.data.URI);
this._hsvc.addVisit(uri, command.data.time, null,
this._hsvc.TRANSITION_TYPED, false, null);
this._hsvc.setPageTitle(uri, command.data.title);
} catch (e) {
this._log.error("Exception caught: " + (e.message? e.message : e));
}
},
_removeCommand: function HistStore__removeCommand(command) {
this._log.trace(" -> NOT removing history entry: " + command.GUID);
// we can't remove because we only sync the last 1000 items, not
// the whole store. So we don't know if remove commands were
// generated due to the user removing an entry or because it
// dropped past the 1000 item mark.
},
_editCommand: function HistStore__editCommand(command) {
this._log.trace(" -> FIXME: NOT editing history entry: " + command.GUID);
// FIXME: implement!
},
_historyRoot: function HistStore__historyRoot() {
let query = this._hsvc.getNewQuery(),
options = this._hsvc.getNewQueryOptions();
query.minVisits = 1;
options.maxResults = 1000;
options.resultType = options.RESULTS_AS_VISIT; // FULL_VISIT does not work
options.sortingMode = options.SORT_BY_DATE_DESCENDING;
options.queryType = options.QUERY_TYPE_HISTORY;
let root = this._hsvc.executeQuery(query, options).root;
root.QueryInterface(Ci.nsINavHistoryQueryResultNode);
return root;
},
wrap: function HistStore_wrap() {
let root = this._historyRoot();
root.containerOpen = true;
let items = {};
for (let i = 0; i < root.childCount; i++) {
let item = root.getChild(i);
let guid = item.time + ":" + item.uri
items[guid] = {parentGUID: '',
title: item.title,
URI: item.uri,
time: item.time
};
// FIXME: sync transition type - requires FULL_VISITs
}
return items;
},
wipe: function HistStore_wipe() {
this._hsvc.removeAllPages();
}
};
HistoryStore.prototype.__proto__ = new Store();
function CookieStore() {
this._init();
}
CookieStore.prototype = {
_logName: "CookieStore",
// Documentation of the nsICookie interface says:
// name ACString The name of the cookie. Read only.
// value ACString The cookie value. Read only.
// isDomain boolean True if the cookie is a domain cookie, false otherwise. Read only.
// host AUTF8String The host (possibly fully qualified) of the cookie. Read only.
// path AUTF8String The path pertaining to the cookie. Read only.
// isSecure boolean True if the cookie was transmitted over ssl, false otherwise. Read only.
// expires PRUint64 Expiration time (local timezone) expressed as number of seconds since Jan 1, 1970. Read only.
// status nsCookieStatus Holds the P3P status of cookie. Read only.
// policy nsCookiePolicy Holds the site's compact policy value. Read only.
// nsICookie2 deprecates expires, status, and policy, and adds:
//rawHost AUTF8String The host (possibly fully qualified) of the cookie without a leading dot to represent if it is a domain cookie. Read only.
//isSession boolean True if the cookie is a session cookie. Read only.
//expiry PRInt64 the actual expiry time of the cookie (where 0 does not represent a session cookie). Read only.
//isHttpOnly boolean True if the cookie is an http only cookie. Read only.
__cookieManager: null,
get _cookieManager() {
if (!this.__cookieManager)
this.__cookieManager = Cc["@mozilla.org/cookiemanager;1"].
getService(Ci.nsICookieManager2);
// need the 2nd revision of the ICookieManager interface
// because it supports add() and the 1st one doesn't.
return this.__cookieManager
},
_createCommand: function HistStore__createCommand(command) {
/* we got a command to create a cookie in the local browser
in order to sync with the server. */
this._log.info("CookieStore got createCommand: " + command );
// this assumes command.data fits the nsICookie2 interface
if ( command.data.expiry ) {
// Add only persistent cookies (those with an expiry date).
// TODO: throw out cookies with expiration date in the past?
this._cookieManager.add( command.data.host,
command.data.path,
command.data.name,
command.data.value,
command.data.isSecure,
command.data.isHttpOnly,
command.data.isSession,
command.data.expiry );
}
},
_removeCommand: function CookieStore__removeCommand(command) {
/* we got a command to remove a cookie from the local browser
in order to sync with the server.
command.data appears to be equivalent to what wrap() puts in
the JSON dictionary. */
this._log.info("CookieStore got removeCommand: " + command );
/* I think it goes like this, according to
http://developer.mozilla.org/en/docs/nsICookieManager
the last argument is "always block cookies from this domain?"
and the answer is "no". */
this._cookieManager.remove( command.data.host,
command.data.name,
command.data.path,
false );
},
_editCommand: function CookieStore__editCommand(command) {
/* we got a command to change a cookie in the local browser
in order to sync with the server. */
this._log.info("CookieStore got editCommand: " + command );
/* Look up the cookie that matches the one in the command: */
var iter = this._cookieManager.enumerator;
var matchingCookie = null;
while (iter.hasMoreElements()){
let cookie = iter.getNext();
if (cookie instanceof Ci.nsICookie){
// see if host:path:name of cookie matches GUID given in command
let key = cookie.host + ":" + cookie.path + ":" + cookie.name;
if (key == command.GUID) {
matchingCookie = cookie;
break;
}
}
}
// Update values in the cookie:
for (var key in command.data) {
// Whatever values command.data has, use them
matchingCookie[ key ] = command.data[ key ]
}
// Remove the old incorrect cookie from the manager:
this._cookieManager.remove( matchingCookie.host,
matchingCookie.name,
matchingCookie.path,
false );
// Re-add the new updated cookie:
if ( command.data.expiry ) {
/* ignore single-session cookies, add only persistent
cookies.
TODO: throw out cookies with expiration dates in the past?*/
this._cookieManager.add( matchingCookie.host,
matchingCookie.path,
matchingCookie.name,
matchingCookie.value,
matchingCookie.isSecure,
matchingCookie.isHttpOnly,
matchingCookie.isSession,
matchingCookie.expiry );
}
// Also, there's an exception raised because
// this._data[comand.GUID] is undefined
},
wrap: function CookieStore_wrap() {
/* Return contents of this store, as JSON.
A dictionary of cookies where the keys are GUIDs and the
values are sub-dictionaries containing all cookie fields. */
let items = {};
var iter = this._cookieManager.enumerator;
while (iter.hasMoreElements()){
var cookie = iter.getNext();
if (cookie instanceof Ci.nsICookie){
// String used to identify cookies is
// host:path:name
if ( !cookie.expiry ) {
/* Skip cookies that do not have an expiration date.
(Persistent cookies have one, session-only cookies don't.)
TODO: Throw out any cookies that have expiration dates in the
past?*/
continue;
}
let key = cookie.host + ":" + cookie.path + ":" + cookie.name;
items[ key ] = { parentGUID: '',
name: cookie.name,
value: cookie.value,
isDomain: cookie.isDomain,
host: cookie.host,
path: cookie.path,
isSecure: cookie.isSecure,
// nsICookie2 values:
rawHost: cookie.rawHost,
isSession: cookie.isSession,
expiry: cookie.expiry,
isHttpOnly: cookie.isHttpOnly }
/* See http://developer.mozilla.org/en/docs/nsICookie
Note: not syncing "expires", "status", or "policy"
since they're deprecated. */
}
}
return items;
},
wipe: function CookieStore_wipe() {
/* Remove everything from the store. Return nothing.
TODO are the semantics of this just wiping out an internal
buffer, or am I supposed to wipe out all cookies from
the browser itself for reals? */
this._cookieManager.removeAll()
},
resetGUIDs: function CookieStore_resetGUIDs() {
/* called in the case where remote/local sync GUIDs do not
match. We do need to override this, but since we're deriving
GUIDs from the cookie data itself and not generating them,
there's basically no way they can get "out of sync" so there's
nothing to do here. */
}
};
CookieStore.prototype.__proto__ = new Store();