diff --git a/chrome/content/zotero/xpcom/cookieSandbox.js b/chrome/content/zotero/xpcom/cookieSandbox.js
new file mode 100755
index 000000000..6682ff93c
--- /dev/null
+++ b/chrome/content/zotero/xpcom/cookieSandbox.js
@@ -0,0 +1,285 @@
+/*
+ ***** BEGIN LICENSE BLOCK *****
+
+ Copyright © 2011 Center for History and New Media
+ George Mason University, Fairfax, Virginia, USA
+ http://zotero.org
+
+ This file is part of Zotero.
+
+ Zotero is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Zotero is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with Zotero. If not, see .
+
+ ***** END LICENSE BLOCK *****
+*/
+
+/**
+ * Manage cookies in a sandboxed fashion
+ *
+ * @constructor
+ * @param {browser} browser Hidden browser object
+ * @param {String|nsIURI} uri URI of page to manage cookies for (cookies for domains that are not
+ * subdomains of this URI are ignored)
+ * @param {String} cookieData Cookies with which to initiate the sandbox
+ */
+Zotero.CookieSandbox = function(browser, uri, cookieData) {
+ this._webNav = browser.webNavigation;
+ this._browser = browser;
+ this._watchedBrowsers = [browser];
+ this._watchedXHRs = [];
+ this._observerService = Components.classes["@mozilla.org/observer-service;1"].
+ getService(Components.interfaces.nsIObserverService);
+
+ if(uri instanceof Components.interfaces.nsIURI) {
+ this.URI = uri;
+ } else {
+ this.URI = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService)
+ .newURI(uri, null, null);
+ }
+
+ this._cookies = {};
+ if(cookieData) {
+ var splitCookies = cookieData.split(/; ?/);
+ for each(var cookie in splitCookies) {
+ var key = cookie.substr(0, cookie.indexOf("="));
+ var value = cookie.substr(cookie.indexOf("=")+1);
+ this._cookies[key] = value;
+ }
+ }
+
+ // register with observer
+ Zotero.CookieSandbox.Observer.register(this);
+}
+
+Zotero.CookieSandbox.prototype = {
+ /**
+ * Check whether we track a browser for this document
+ */
+ "isDocumentTracked":function(doc) {
+ var i = this._watchedBrowsers.length;
+ while(i--) {
+ var browser = this._watchedBrowsers[i];
+ if(doc == browser.contentDocument) return true;
+ }
+ return false;
+ },
+
+ /**
+ * Check whether we track an XHR for this document
+ */
+ "isXHRTracked":function(xhr) {
+ return this._watchedXHRs.indexOf(xhr) !== -1;
+ },
+
+ /**
+ * Adds cookies to this CookieSandbox based on a cookie header
+ * @param {String} cookieString;
+ */
+ "addCookiesFromHeader":function(cookieString) {
+ var cookies = cookieString.split("\n");
+ for(var i=0, n=cookies.length; i we should manage cookies for this request
+ // tested && !trackedBy => we should not manage cookies for this request
+ // !tested && !trackedBy => this request is of a type we couldn't match to this request.
+ // one such type is a link prefetch (nsPrefetchNode) but there might be others as
+ // well. for now, we are paranoid and reject these.
+
+ if(tested) {
+ if(trackedBy) {
+ Zotero.debug("CookieSandbox: Managing cookies for "+channelURI, 5);
+ } else {
+ Zotero.debug("CookieSandbox: Not touching channel for "+channelURI, 5);
+ return;
+ }
+ } else {
+ Zotero.debug("CookieSandbox: Being paranoid about channel for "+channelURI, 5);
+ }
+
+ if(topic == "http-on-modify-request") {
+ // clear cookies to be sent to other domains
+ if(!trackedBy || channel.URI.host != trackedBy.URI.host) {
+ channel.setRequestHeader("Cookie", "", false);
+ channel.setRequestHeader("Cookie2", "", false);
+ Zotero.debug("CookieSandbox: Cleared cookies to be sent to "+channelURI, 5);
+ return;
+ }
+
+ // add cookies to be sent to this domain
+ Zotero.debug(trackedBy.cookieString);
+ channel.setRequestHeader("Cookie", trackedBy.cookieString, false);
+ Zotero.debug("CookieSandbox: Added cookies for request to "+channelURI, 5);
+ } else if(topic == "http-on-examine-response") {
+ // clear cookies being received
+ try {
+ var cookieHeader = channel.getResponseHeader("Set-Cookie");
+ } catch(e) {
+ return;
+ }
+ channel.setResponseHeader("Set-Cookie", "", false);
+ channel.setResponseHeader("Set-Cookie2", "", false);
+
+ // don't process further if these cookies are for another set of domains
+ if(!trackedBy || channel.URI.host != trackedBy.URI.host) {
+ Zotero.debug("CookieSandbox: Rejected cookies from "+channelURI, 5);
+ return;
+ }
+
+ // put new cookies into our sandbox
+ Zotero.debug(cookieHeader);
+ if(cookieHeader) trackedBy.addCookiesFromHeader(cookieHeader);
+
+ Zotero.debug("CookieSandbox: Slurped cookies from "+channelURI, 5);
+ }
+ }
+}
\ No newline at end of file
diff --git a/chrome/content/zotero/xpcom/http.js b/chrome/content/zotero/xpcom/http.js
index a420d133b..2e3e9aca0 100644
--- a/chrome/content/zotero/xpcom/http.js
+++ b/chrome/content/zotero/xpcom/http.js
@@ -12,9 +12,10 @@ Zotero.HTTP = new function() {
* @param {nsIURI|String} url URL to request
* @param {Function} onDone Callback to be executed upon request completion
* @param {String} responseCharset Character set to force on the response
+ * @param {Zotero.CookieSandbox} [cookieSandbox] Cookie sandbox object
* @return {Boolean} True if the request was sent, or false if the browser is offline
*/
- this.doGet = function(url, onDone, responseCharset) {
+ this.doGet = function(url, onDone, responseCharset, cookieSandbox) {
if (url instanceof Components.interfaces.nsIURI) {
// Don't display password in console
var disp = url.clone();
@@ -69,6 +70,7 @@ Zotero.HTTP = new function() {
_stateChange(xmlhttp, onDone, responseCharset);
};
+ if(cookieSandbox) cookieSandbox.attachToXHR(xmlhttp);
xmlhttp.send(null);
return xmlhttp;
@@ -82,9 +84,10 @@ Zotero.HTTP = new function() {
* @param {Function} onDone Callback to be executed upon request completion
* @param {String} headers Request HTTP headers
* @param {String} responseCharset Character set to force on the response
+ * @param {Zotero.CookieSandbox} [cookieSandbox] Cookie sandbox object
* @return {Boolean} True if the request was sent, or false if the browser is offline
*/
- this.doPost = function(url, body, onDone, headers, responseCharset) {
+ this.doPost = function(url, body, onDone, headers, responseCharset, cookieSandbox) {
if (url instanceof Components.interfaces.nsIURI) {
// Don't display password in console
var disp = url.clone();
@@ -166,6 +169,7 @@ Zotero.HTTP = new function() {
_stateChange(xmlhttp, onDone, responseCharset);
};
+ if(cookieSandbox) cookieSandbox.attachToXHR(xmlhttp);
xmlhttp.send(body);
return xmlhttp;
@@ -506,9 +510,10 @@ Zotero.HTTP = new function() {
* @param {Function} exception Callback to be executed if an exception occurs
* @param {Boolean} dontDelete Don't delete the hidden browser upon completion; calling function
* must call deleteHiddenBrowser itself.
+ * @param {Zotero.CookieSandbox} [cookieSandbox] Cookie sandbox object
* @return {browser} Hidden browser used for loading
*/
- this.processDocuments = function(urls, processor, done, exception, dontDelete) {
+ this.processDocuments = function(urls, processor, done, exception, dontDelete, cookieSandbox) {
/**
* Removes event listener for the load event and deletes the hidden browser
*/
@@ -573,6 +578,7 @@ Zotero.HTTP = new function() {
var hiddenBrowser = Zotero.Browser.createHiddenBrowser();
hiddenBrowser.addEventListener(loadEvent, onLoad, true);
+ if(cookieSandbox) cookieSandbox.attachToBrowser(hiddenBrowser);
doLoad();
diff --git a/chrome/content/zotero/xpcom/server_connector.js b/chrome/content/zotero/xpcom/server_connector.js
index e4d068e25..fd90687c1 100755
--- a/chrome/content/zotero/xpcom/server_connector.js
+++ b/chrome/content/zotero/xpcom/server_connector.js
@@ -29,169 +29,6 @@ Zotero.Server.Connector = function() {};
Zotero.Server.Connector._waitingForSelection = {};
Zotero.Server.Connector.Data = {};
-/**
- * Manage cookies in a sandboxed fashion
- *
- * @param {browser} browser Hidden browser object
- * @param {String} uri URI of page to manage cookies for (cookies for domains that are not
- * subdomains of this URI are ignored)
- * @param {String} cookieData Cookies with which to initiate the sandbox
- */
-Zotero.Server.Connector.CookieManager = function(browser, uri, cookieData) {
- this._webNav = browser.webNavigation;
- this._browser = browser;
- this._watchedBrowsers = [browser];
- this._observerService = Components.classes["@mozilla.org/observer-service;1"].
- getService(Components.interfaces.nsIObserverService);
-
- this._uri = Components.classes["@mozilla.org/network/io-service;1"]
- .getService(Components.interfaces.nsIIOService)
- .newURI(uri, null, null);
-
- var splitCookies = cookieData.split(/; ?/);
- this._cookies = {};
- for each(var cookie in splitCookies) {
- var key = cookie.substr(0, cookie.indexOf("="));
- var value = cookie.substr(cookie.indexOf("=")+1);
- this._cookies[key] = value;
- }
-
- [this._observerService.addObserver(this, topic, false) for each(topic in this._observerTopics)];
-}
-
-Zotero.Server.Connector.CookieManager.prototype = {
- "_observerTopics":["http-on-examine-response", "http-on-modify-request", "quit-application"],
- "_watchedXHRs":[],
-
- /**
- * nsIObserver implementation for adding, clearing, and slurping cookies
- */
- "observe": function(channel, topic) {
- if(topic == "quit-application") {
- Zotero.debug("WARNING: A Zotero.Server.CookieManager for "+this._uri.spec+" was still open on shutdown");
- } else {
- channel.QueryInterface(Components.interfaces.nsIHttpChannel);
- var isTracked = null;
- try {
- var topDoc = channel.notificationCallbacks.getInterface(Components.interfaces.nsIDOMWindow).top.document;
- for each(var browser in this._watchedBrowsers) {
- isTracked = topDoc == browser.contentDocument;
- if(isTracked) break;
- }
- } catch(e) {}
- if(isTracked === null) {
- try {
- isTracked = channel.loadGroup.notificationCallbacks.getInterface(Components.interfaces.nsIDOMWindow).top.document == this._browser.contentDocument;
- } catch(e) {}
- }
- if(isTracked === null) {
- try {
- isTracked = this._watchedXHRs.indexOf(channel.notificationCallbacks.QueryInterface(Components.interfaces.nsIXMLHttpRequest)) !== -1;
- } catch(e) {}
- }
-
- // isTracked is now either true, false, or null
- // true => we should manage cookies for this request
- // false => we should not manage cookies for this request
- // null => this request is of a type we couldn't match to this request. one such type
- // is a link prefetch (nsPrefetchNode) but there might be others as well. for
- // now, we are paranoid and reject these.
-
- if(isTracked === false) {
- Zotero.debug("Zotero.Server.CookieManager: not touching channel for "+channel.URI.spec);
- return;
- } else if(isTracked) {
- Zotero.debug("Zotero.Server.CookieManager: managing cookies for "+channel.URI.spec);
- } else {
- Zotero.debug("Zotero.Server.CookieManager: being paranoid about channel for "+channel.URI.spec);
- }
-
- if(topic == "http-on-modify-request") {
- // clear cookies to be sent to other domains
- if(isTracked === null || channel.URI.host != this._uri.host) {
- channel.setRequestHeader("Cookie", "", false);
- channel.setRequestHeader("Cookie2", "", false);
- Zotero.debug("Zotero.Server.CookieManager: cleared cookies to be sent to "+channel.URI.spec);
- return;
- }
-
- // add cookies to be sent to this domain
- var cookies = [key+"="+this._cookies[key]
- for(key in this._cookies)].join("; ");
- channel.setRequestHeader("Cookie", cookies, false);
- Zotero.debug("Zotero.Server.CookieManager: added cookies for request to "+channel.URI.spec);
- } else if(topic == "http-on-examine-response") {
- // clear cookies being received
- try {
- var cookieHeader = channel.getResponseHeader("Set-Cookie");
- } catch(e) {
- return;
- }
- channel.setResponseHeader("Set-Cookie", "", false);
- channel.setResponseHeader("Set-Cookie2", "", false);
-
- // don't process further if these cookies are for another set of domains
- if(isTracked === null || channel.URI.host != this._uri.host) {
- Zotero.debug("Zotero.Server.CookieManager: rejected cookies from "+channel.URI.spec);
- return;
- }
-
- // put new cookies into our sandbox
- if(cookieHeader) {
- var cookies = cookieHeader.split(/; ?/);
- var newCookies = {};
- for each(var cookie in cookies) {
- var key = cookie.substr(0, cookie.indexOf("="));
- var value = cookie.substr(cookie.indexOf("=")+1);
- var lcCookie = key.toLowerCase();
-
- if(["comment", "domain", "max-age", "path", "version", "expires"].indexOf(lcCookie) != -1) {
- // ignore cookie parameters; we are only holding cookies for a few minutes
- // with a single domain, and the path attribute doesn't allow any additional
- // security.
- // DEBUG: does ignoring the path attribute break any sites?
- continue;
- } else if(lcCookie == "secure") {
- // don't accept secure cookies
- newCookies = {};
- break;
- } else {
- newCookies[key] = value;
- }
- }
- [this._cookies[key] = newCookies[key] for(key in newCookies)];
- }
-
- Zotero.debug("Zotero.Server.CookieManager: slurped cookies from "+channel.URI.spec);
- }
- }
- },
-
- /**
- * Attach CookieManager to a specific XMLHttpRequest
- * @param {XMLHttpRequest} xhr
- */
- "attachToBrowser": function(browser) {
- this._watchedBrowsers.push(browser);
- },
-
- /**
- * Attach CookieManager to a specific XMLHttpRequest
- * @param {XMLHttpRequest} xhr
- */
- "attachToXHR": function(xhr) {
- this._watchedXHRs.push(xhr);
- },
-
- /**
- * Destroys this CookieManager (intended to be executed when the browser is destroyed)
- */
- "destroy": function() {
- [this._observerService.removeObserver(this, topic) for each(topic in this._observerTopics)];
- }
-}
-
-
/**
* Lists all available translators, including code for translators that should be run on every page
*
@@ -268,7 +105,7 @@ Zotero.Server.Connector.Detect.prototype = {
var pageShowCalled = false;
var me = this;
- this._translate.setCookieManager(new Zotero.Server.Connector.CookieManager(this._browser,
+ this._translate.setCookieSandbox(new Zotero.CookieSandbox(this._browser,
this._parsedPostData["uri"], this._parsedPostData["cookie"]));
this._browser.addEventListener("DOMContentLoaded", function() {
try {
@@ -310,7 +147,7 @@ Zotero.Server.Connector.Detect.prototype = {
}
this._sendResponse(200, "application/json", JSON.stringify(jsons));
- this._translate.cookieManager.destroy();
+ this._translate.cookieSandbox.destroy();
Zotero.Browser.deleteHiddenBrowser(this._browser);
}
}
@@ -396,6 +233,7 @@ Zotero.Server.Connector.SavePage.prototype = {
"_translatorsAvailable":function(translate, translators) {
// make sure translatorsAvailable succeded
if(!translators.length) {
+ me._translate.cookieSandbox.destroy();
Zotero.Browser.deleteHiddenBrowser(this._browser);
this._sendResponse(500);
return;
@@ -420,10 +258,14 @@ Zotero.Server.Connector.SavePage.prototype = {
}
jsonItems.push(jsonItem);
});
- translate.setHandler("done", function(obj, item) {
- me._translate.cookieManager.destroy();
+ translate.setHandler("done", function(obj, item) {
+ me._translate.cookieSandbox.destroy();
Zotero.Browser.deleteHiddenBrowser(me._browser);
- me._sendResponse(201, "application/json", JSON.stringify({"items":jsonItems}));
+ if(jsonItems.length) {
+ me._sendResponse(201, "application/json", JSON.stringify({"items":jsonItems}));
+ } else {
+ me._sendResponse(500);
+ }
});
// set translator and translate
diff --git a/chrome/content/zotero/xpcom/translation/translate.js b/chrome/content/zotero/xpcom/translation/translate.js
index 397b246d7..c55837dbb 100644
--- a/chrome/content/zotero/xpcom/translation/translate.js
+++ b/chrome/content/zotero/xpcom/translation/translate.js
@@ -1315,7 +1315,7 @@ Zotero.Translate.Base.prototype = {
* @class Web translation
*
* @property {Document} document The document object to be used for web scraping (set with setDocument)
- * @property {Zotero.Connector.CookieManager} cookieManager A CookieManager to manage cookies for
+ * @property {Zotero.CookieSandbox} cookieSandbox A CookieSandbox to manage cookies for
* this Translate instance.
*/
Zotero.Translate.Web = function() {
@@ -1336,13 +1336,13 @@ Zotero.Translate.Web.prototype.setDocument = function(doc) {
}
/**
- * Sets a Zotero.Connector.CookieManager to handle cookie management for XHRs initiated from this
+ * Sets a Zotero.CookieSandbox to handle cookie management for XHRs initiated from this
* translate instance
*
- * @param {Zotero.Connector.CookieManager} cookieManager
+ * @param {Zotero.CookieSandbox} cookieSandbox
*/
-Zotero.Translate.Web.prototype.setCookieManager = function(cookieManager) {
- this.cookieManager = cookieManager;
+Zotero.Translate.Web.prototype.setCookieSandbox = function(cookieSandbox) {
+ this.cookieSandbox = cookieSandbox;
}
/**
@@ -1757,9 +1757,9 @@ Zotero.Translate.Search.prototype._entryFunctionSuffix = "Search";
Zotero.Translate.Search.prototype.Sandbox = Zotero.Translate.Sandbox._inheritFromBase(Zotero.Translate.Sandbox.Search);
/**
- * @borrows Zotero.Translate.Web#setCookieManager
+ * @borrows Zotero.Translate.Web#setCookieSandbox
*/
-Zotero.Translate.Search.prototype.setCookieManager = Zotero.Translate.Web.prototype.setCookieManager;
+Zotero.Translate.Search.prototype.setCookieSandbox = Zotero.Translate.Web.prototype.setCookieSandbox;
/**
* Sets the item to be used for searching
diff --git a/chrome/content/zotero/xpcom/utilities.js b/chrome/content/zotero/xpcom/utilities.js
index 6a9613a26..4e2920b5e 100644
--- a/chrome/content/zotero/xpcom/utilities.js
+++ b/chrome/content/zotero/xpcom/utilities.js
@@ -1155,10 +1155,10 @@ Zotero.Utilities.Translate.prototype.processDocuments = function(urls, processor
var translate = this._translate;
translate.incrementAsyncProcesses();
- Zotero.HTTP.processDocuments(urls, processor, function() {
+ var hiddenBrowser = Zotero.HTTP.processDocuments(urls, processor, function() {
if(done) done();
translate.decrementAsyncProcesses();
- }, exception);
+ }, exception, false, translate.cookieSandbox);
}
/**
@@ -1181,6 +1181,8 @@ Zotero.Utilities.Translate.prototype.retrieveDocument = function(url) {
}
var hiddenBrowser = Zotero.Browser.createHiddenBrowser();
+ if(translate.cookieSandbox) translate.cookieSandbox.attachToBrowser(hiddenBrowser);
+
hiddenBrowser.addEventListener("pageshow", listener, true);
hiddenBrowser.loadURI(url);
@@ -1228,16 +1230,16 @@ Zotero.Utilities.Translate.prototype.retrieveSource = function(url, body, header
if(!responseCharset) responseCharset = null;
var mainThread = Zotero.mainThread;
- var xmlhttp = false;
- var listener = function(aXmlhttp) { xmlhttp = aXmlhttp };
+ var finished = false;
+ var listener = function() { finished = true };
if(body) {
- Zotero.HTTP.doPost(url, body, listener, headers, responseCharset);
+ var xmlhttp = Zotero.HTTP.doPost(url, body, listener, headers, responseCharset, translate.cookieSandbox);
} else {
- Zotero.HTTP.doGet(url, listener, responseCharset);
+ var xmlhttp = Zotero.HTTP.doGet(url, listener, responseCharset, translate.cookieSandbox);
}
- while(!xmlhttp) mainThread.processNextEvent(true);
+ while(!finished) mainThread.processNextEvent(true);
} else {
// Use a synchronous XMLHttpRequest, even though this is inadvisable
var xmlhttp = new XMLHttpRequest();
@@ -1274,7 +1276,7 @@ Zotero.Utilities.Translate.prototype.doGet = function(urls, processor, done, res
var me = this;
this._translate.incrementAsyncProcesses();
- Zotero.HTTP.doGet(url, function(xmlhttp) {
+ var xmlhttp = Zotero.HTTP.doGet(url, function(xmlhttp) {
try {
if(processor) {
processor(xmlhttp.responseText, xmlhttp, url);
@@ -1291,7 +1293,7 @@ Zotero.Utilities.Translate.prototype.doGet = function(urls, processor, done, res
} catch(e) {
me._translate.complete(false, e);
}
- }, responseCharset);
+ }, responseCharset, this._translate.cookieSandbox);
}
/**
@@ -1303,14 +1305,14 @@ Zotero.Utilities.Translate.prototype.doPost = function(url, body, onDone, header
var translate = this._translate;
this._translate.incrementAsyncProcesses();
- Zotero.HTTP.doPost(url, body, function(xmlhttp) {
+ var xmlhttp = Zotero.HTTP.doPost(url, body, function(xmlhttp) {
try {
onDone(xmlhttp.responseText, xmlhttp);
translate.decrementAsyncProcesses();
} catch(e) {
translate.complete(false, e);
}
- }, headers, responseCharset);
+ }, headers, responseCharset, translate.cookieSandbox ? translate.cookieSandbox : undefined);
}
/**
diff --git a/components/zotero-service.js b/components/zotero-service.js
index c05cf00c8..08a3e3e8b 100644
--- a/components/zotero-service.js
+++ b/components/zotero-service.js
@@ -55,6 +55,7 @@ const xpcomFilesLocal = [
'attachments',
'cite',
'commons',
+ 'cookieSandbox',
'data_access',
'data/dataObjects',
'data/cachedTypes',