Improve our chances of getting attachments from the connector. We attach cookies sent from the connector to attachment downloads, which should get most proxies working, provided that the user is logged in. We also use WeakMaps to keep track of active CookieSandboxes, rather than manually removing them from a list.

This commit is contained in:
Simon Kornblith 2012-02-10 19:10:43 -05:00
parent 6a4bcc683e
commit 5619555866
9 changed files with 81 additions and 111 deletions

View file

@ -200,7 +200,7 @@ Zotero.Attachments = new function(){
function importFromURL(url, sourceItemID, forceTitle, forceFileBaseName, parentCollectionIDs, function importFromURL(url, sourceItemID, forceTitle, forceFileBaseName, parentCollectionIDs,
mimeType, libraryID, callback) { mimeType, libraryID, callback, cookieSandbox) {
Zotero.debug('Importing attachment from URL'); Zotero.debug('Importing attachment from URL');
if (sourceItemID && parentCollectionIDs) { if (sourceItemID && parentCollectionIDs) {
@ -222,6 +222,7 @@ Zotero.Attachments = new function(){
// Save using a hidden browser // Save using a hidden browser
var nativeHandlerImport = function () { var nativeHandlerImport = function () {
var browser = Zotero.Browser.createHiddenBrowser(); var browser = Zotero.Browser.createHiddenBrowser();
if(cookieSandbox) cookieSandbox.attachToBrowser(browser);
var imported = false; var imported = false;
var onpageshow = function() { var onpageshow = function() {
// ignore spurious about:blank loads // ignore spurious about:blank loads
@ -263,6 +264,7 @@ Zotero.Attachments = new function(){
.classes["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"] .classes["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"]
.createInstance(nsIWBP); .createInstance(nsIWBP);
wbp.persistFlags = nsIWBP.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION; wbp.persistFlags = nsIWBP.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION;
if(cookieSandbox) cookieSandbox.attachToInterfaceRequestor(wbp);
var encodingFlags = false; var encodingFlags = false;
Zotero.DB.beginTransaction(); Zotero.DB.beginTransaction();
@ -396,7 +398,7 @@ Zotero.Attachments = new function(){
else { else {
Zotero.MIME.getMIMETypeFromURL(url, function (mimeType, hasNativeHandler) { Zotero.MIME.getMIMETypeFromURL(url, function (mimeType, hasNativeHandler) {
process(mimeType, hasNativeHandler); process(mimeType, hasNativeHandler);
}); }, cookieSandbox);
} }
} }

View file

@ -1,7 +1,7 @@
/* /*
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright © 2009 Center for History and New Media Copyright © 2012 Center for History and New Media
George Mason University, Fairfax, Virginia, USA George Mason University, Fairfax, Virginia, USA
http://zotero.org http://zotero.org
@ -23,10 +23,15 @@
***** END LICENSE BLOCK ***** ***** END LICENSE BLOCK *****
*/ */
Zotero.Translate.ItemSaver = function(libraryID, attachmentMode, forceTagType) { Zotero.Translate.ItemSaver = function(libraryID, attachmentMode, forceTagType, document,
cookieSandbox) {
this.newItems = []; this.newItems = [];
this._timeoutID = null; this._timeoutID = null;
if(document) {
this._uri = document.location.toString();
this._cookie = document.cookie;
}
} }
Zotero.Translate.ItemSaver.ATTACHMENT_MODE_IGNORE = 0; Zotero.Translate.ItemSaver.ATTACHMENT_MODE_IGNORE = 0;
@ -40,7 +45,13 @@ Zotero.Translate.ItemSaver.prototype = {
"saveItems":function(items, callback) { "saveItems":function(items, callback) {
var me = this; var me = this;
// first try to save items via connector // first try to save items via connector
Zotero.Connector.callMethod("saveItems", {"items":items}, function(success, status) { var payload = {"items":items};
if(this._uri && this._cookie) {
payload.uri = this._uri;
payload.cookie = this._cookie;
}
Zotero.Connector.callMethod("saveItems", payload, function(success, status) {
if(success !== false) { if(success !== false) {
Zotero.debug("Translate: Save via Standalone succeeded"); Zotero.debug("Translate: Save via Standalone succeeded");
callback(true, items); callback(true, items);

View file

@ -27,16 +27,12 @@
* Manage cookies in a sandboxed fashion * Manage cookies in a sandboxed fashion
* *
* @constructor * @constructor
* @param {browser} browser Hidden browser object * @param {browser} [browser] Hidden browser object
* @param {String|nsIURI} uri URI of page to manage cookies for (cookies for domains that are not * @param {String|nsIURI} uri URI of page to manage cookies for (cookies for domains that are not
* subdomains of this URI are ignored) * subdomains of this URI are ignored)
* @param {String} cookieData Cookies with which to initiate the sandbox * @param {String} cookieData Cookies with which to initiate the sandbox
*/ */
Zotero.CookieSandbox = function(browser, uri, cookieData) { 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"]. this._observerService = Components.classes["@mozilla.org/observer-service;1"].
getService(Components.interfaces.nsIObserverService); getService(Components.interfaces.nsIObserverService);
@ -58,30 +54,13 @@ Zotero.CookieSandbox = function(browser, uri, cookieData) {
} }
} }
// register with observer if(browser) {
Zotero.CookieSandbox.Observer.register(this); this.attachToBrowser(browser);
}
Zotero.CookieSandbox.Observer.register();
} }
Zotero.CookieSandbox.prototype = { 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 * Adds cookies to this CookieSandbox based on a cookie header
* @param {String} cookieString; * @param {String} cookieString;
@ -108,27 +87,19 @@ Zotero.CookieSandbox.prototype = {
}, },
/** /**
* Attach CookieSandbox to a specific XMLHttpRequest * Attach CookieSandbox to a specific browser
* @param {XMLHttpRequest} xhr * @param {Browser} browser
*/ */
"attachToBrowser":function(browser) { "attachToBrowser":function(browser) {
this._watchedBrowsers.push(browser); Zotero.CookieSandbox.Observer.trackedBrowsers.set(browser, this);
}, },
/** /**
* Attach CookieSandbox to a specific XMLHttpRequest * Attach CookieSandbox to a specific XMLHttpRequest
* @param {XMLHttpRequest} xhr * @param {nsIInterfaceRequestor} ir
*/ */
"attachToXHR": function(xhr) { "attachToInterfaceRequestor": function(ir) {
this._watchedXHRs.push(xhr); Zotero.CookieSandbox.Observer.trackedInterfaceRequestors.set(ir.QueryInterface(Components.interfaces.nsIInterfaceRequestor), this);
},
/**
* Destroys this CookieSandbox (intended to be executed when the browser is destroyed)
*/
"destroy": function() {
// unregister with observer
Zotero.CookieSandbox.Observer.unregister(this);
} }
} }
@ -144,36 +115,21 @@ Zotero.CookieSandbox.Observer = new function() {
var observerService = Components.classes["@mozilla.org/observer-service;1"]. var observerService = Components.classes["@mozilla.org/observer-service;1"].
getService(Components.interfaces.nsIObserverService), getService(Components.interfaces.nsIObserverService),
observing = false, observing = false;
cookieSandboxes = [];
this.trackedBrowsers = new WeakMap();
this.trackedInterfaceRequestors = new WeakMap();
/** /**
* Registers cookie manager and observer, if necessary * Registers cookie manager and observer, if necessary
*/ */
this.register = function(CookieSandbox) { this.register = function(CookieSandbox) {
cookieSandboxes.push(CookieSandbox);
if(!observing) { if(!observing) {
Zotero.debug("CookieSandbox: Registering observers"); Zotero.debug("CookieSandbox: Registering observers");
for each(var topic in observeredTopics) observerService.addObserver(this, topic, false); for each(var topic in observeredTopics) observerService.addObserver(this, topic, false);
observing = true; observing = true;
} }
} };
/**
* Unregisters cookie manager and observer
*/
this.unregister = function(CookieSandbox) {
// remove cookie manager from list
cookieSandboxes.splice(cookieSandboxes.indexOf(CookieSandbox), 1);
// remove observer if this is the last and this is not translation-server
if(cookieSandboxes.length === 0 && !Zotero.isServer) {
Zotero.debug("CookieSandbox: Unregistering observers");
for each(var topic in observeredTopics) observerService.removeObserver(this, topic);
observing = false;
}
}
/** /**
* Implements nsIObserver to watch for new cookies and to add sandboxed cookies * Implements nsIObserver to watch for new cookies and to add sandboxed cookies
@ -185,50 +141,48 @@ Zotero.CookieSandbox.Observer = new function() {
} }
channel.QueryInterface(Components.interfaces.nsIHttpChannel); channel.QueryInterface(Components.interfaces.nsIHttpChannel);
var trackedBy, tested, doc, xhr, var trackedBy, tested, browser, callbacks,
channelURI = channel.URI.spec, channelURI = channel.URI.spec,
notificationCallbacks = channel.notificationCallbacks; notificationCallbacks = channel.notificationCallbacks;
// try the document // try the notification callbacks
try { trackedBy = this.trackedInterfaceRequestors.get(notificationCallbacks);
doc = notificationCallbacks.getInterface(Components.interfaces.nsIDOMWindow).top.document; if(trackedBy) {
} catch(e) {}
if(doc) {
tested = true; tested = true;
for(var i=0, n=cookieSandboxes.length; i<n; i++) {
if(cookieSandboxes[i].isDocumentTracked(doc)) {
trackedBy = cookieSandboxes[i];
}
}
} else { } else {
// try the document for the load group // try the browser
try { try {
doc = channel.loadGroup.notificationCallbacks.getInterface(Components.interfaces.nsIDOMWindow).top.document; browser = notificationCallbacks.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell).chromeEventHandler;
} catch(e) {} } catch(e) {}
if(doc) { if(browser) {
tested = true; tested = true;
for(var i=0, n=cookieSandboxes.length; i<n; i++) { trackedBy = this.trackedBrowsers.get(browser);
if(cookieSandboxes[i].isDocumentTracked(doc)) {
trackedBy = cookieSandboxes[i];
}
}
} else { } else {
// try getting as an XHR // try the document for the load group
try { try {
xhr = notificationCallbacks.QueryInterface(Components.interfaces.nsIXMLHttpRequest); browser = channel.loadGroup.notificationCallbacks.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell).chromeEventHandler;
} catch(e) {} } catch(e) {}
if(xhr) { if(browser) {
tested = true; tested = true;
for(var i=0, n=cookieSandboxes.length; i<n; i++) { trackedBy = this.trackedBrowsers.get(browser);
if(cookieSandboxes[i].isXHRTracked(xhr)) { } else {
trackedBy = cookieSandboxes[i]; // try getting as an XHR or nsIWBP
} try {
notificationCallbacks.QueryInterface(Components.interfaces.nsIXMLHttpRequest);
tested = true;
} catch(e) {}
if(!tested) {
try {
notificationCallbacks.QueryInterface(Components.interfaces.nsIWebBrowserPersist);
tested = true;
} catch(e) {}
} }
} }
} }
} }
// isTracked is now either true, false, or null
// trackedBy => we should manage cookies for this request // trackedBy => we should manage cookies for this request
// tested && !trackedBy => we should not 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. // !tested && !trackedBy => this request is of a type we couldn't match to this request.
@ -256,7 +210,6 @@ Zotero.CookieSandbox.Observer = new function() {
} }
// add cookies to be sent to this domain // add cookies to be sent to this domain
Zotero.debug(trackedBy.cookieString);
channel.setRequestHeader("Cookie", trackedBy.cookieString, false); channel.setRequestHeader("Cookie", trackedBy.cookieString, false);
Zotero.debug("CookieSandbox: Added cookies for request to "+channelURI, 5); Zotero.debug("CookieSandbox: Added cookies for request to "+channelURI, 5);
} else if(topic == "http-on-examine-response") { } else if(topic == "http-on-examine-response") {
@ -276,7 +229,6 @@ Zotero.CookieSandbox.Observer = new function() {
} }
// put new cookies into our sandbox // put new cookies into our sandbox
Zotero.debug(cookieHeader);
if(cookieHeader) trackedBy.addCookiesFromHeader(cookieHeader); if(cookieHeader) trackedBy.addCookiesFromHeader(cookieHeader);
Zotero.debug("CookieSandbox: Slurped cookies from "+channelURI, 5); Zotero.debug("CookieSandbox: Slurped cookies from "+channelURI, 5);

View file

@ -52,7 +52,7 @@ Zotero.HTTP = new function() {
_stateChange(xmlhttp, onDone, responseCharset); _stateChange(xmlhttp, onDone, responseCharset);
}; };
if(cookieSandbox) cookieSandbox.attachToXHR(xmlhttp); if(cookieSandbox) cookieSandbox.attachToInterfaceRequestor(xmlhttp);
xmlhttp.send(null); xmlhttp.send(null);
return xmlhttp; return xmlhttp;
@ -151,7 +151,7 @@ Zotero.HTTP = new function() {
_stateChange(xmlhttp, onDone, responseCharset); _stateChange(xmlhttp, onDone, responseCharset);
}; };
if(cookieSandbox) cookieSandbox.attachToXHR(xmlhttp); if(cookieSandbox) cookieSandbox.attachToInterfaceRequestor(xmlhttp);
xmlhttp.send(body); xmlhttp.send(body);
return xmlhttp; return xmlhttp;
@ -163,9 +163,10 @@ Zotero.HTTP = new function() {
* @param {String} url URL to request * @param {String} url URL to request
* @param {Function} onDone Callback to be executed upon request completion * @param {Function} onDone Callback to be executed upon request completion
* @param {Object} requestHeaders HTTP headers to include with request * @param {Object} requestHeaders HTTP headers to include with request
* @param {Zotero.CookieSandbox} [cookieSandbox] Cookie sandbox object
* @return {Boolean} True if the request was sent, or false if the browser is offline * @return {Boolean} True if the request was sent, or false if the browser is offline
*/ */
this.doHead = function(url, onDone, requestHeaders) { this.doHead = function(url, onDone, requestHeaders, cookieSandbox) {
if (url instanceof Components.interfaces.nsIURI) { if (url instanceof Components.interfaces.nsIURI) {
// Don't display password in console // Don't display password in console
var disp = url.clone(); var disp = url.clone();
@ -229,6 +230,7 @@ Zotero.HTTP = new function() {
_stateChange(xmlhttp, onDone); _stateChange(xmlhttp, onDone);
}; };
if(cookieSandbox) cookieSandbox.attachToInterfaceRequestor(xmlhttp);
xmlhttp.send(null); xmlhttp.send(null);
return xmlhttp; return xmlhttp;

View file

@ -280,7 +280,7 @@ Zotero.MIME = new function(){
} }
this.getMIMETypeFromURL = function (url, callback) { this.getMIMETypeFromURL = function (url, callback, cookieSandbox) {
Zotero.HTTP.doHead(url, function(xmlhttp) { Zotero.HTTP.doHead(url, function(xmlhttp) {
if (xmlhttp.status != 200 && xmlhttp.status != 204) { if (xmlhttp.status != 200 && xmlhttp.status != 204) {
Zotero.debug("Attachment HEAD request returned with status code " Zotero.debug("Attachment HEAD request returned with status code "
@ -308,7 +308,7 @@ Zotero.MIME = new function(){
var hasNativeHandler = Zotero.MIME.hasNativeHandler(mimeType, ext) var hasNativeHandler = Zotero.MIME.hasNativeHandler(mimeType, ext)
callback(mimeType, hasNativeHandler); callback(mimeType, hasNativeHandler);
}); }, undefined, cookieSandbox);
} }

View file

@ -156,7 +156,6 @@ Zotero.Server.Connector.Detect.prototype = {
} }
this.sendResponse(200, "application/json", JSON.stringify(jsons)); this.sendResponse(200, "application/json", JSON.stringify(jsons));
this._translate.cookieSandbox.destroy();
Zotero.Browser.deleteHiddenBrowser(this._browser); Zotero.Browser.deleteHiddenBrowser(this._browser);
} }
} }
@ -225,7 +224,6 @@ Zotero.Server.Connector.SavePage.prototype = {
"_translatorsAvailable":function(translate, translators) { "_translatorsAvailable":function(translate, translators) {
// make sure translatorsAvailable succeded // make sure translatorsAvailable succeded
if(!translators.length) { if(!translators.length) {
me._translate.cookieSandbox.destroy();
Zotero.Browser.deleteHiddenBrowser(this._browser); Zotero.Browser.deleteHiddenBrowser(this._browser);
this.sendResponse(500); this.sendResponse(500);
return; return;
@ -251,7 +249,6 @@ Zotero.Server.Connector.SavePage.prototype = {
jsonItems.push(jsonItem); jsonItems.push(jsonItem);
}); });
translate.setHandler("done", function(obj, item) { translate.setHandler("done", function(obj, item) {
me._translate.cookieSandbox.destroy();
Zotero.Browser.deleteHiddenBrowser(me._browser); Zotero.Browser.deleteHiddenBrowser(me._browser);
if(jsonItems.length || me.selectedItems === false) { if(jsonItems.length || me.selectedItems === false) {
me.sendResponse(201, "application/json", JSON.stringify({"items":jsonItems})); me.sendResponse(201, "application/json", JSON.stringify({"items":jsonItems}));
@ -296,9 +293,12 @@ Zotero.Server.Connector.SaveItem.prototype = {
var collection = zp.getSelectedCollection(); var collection = zp.getSelectedCollection();
} catch(e) {} } catch(e) {}
var cookieSandbox = data["uri"] && data["cookie"] ? new Zotero.CookieSandbox(null, data["uri"],
data["cookie"]) : null;
// save items // save items
var itemSaver = new Zotero.Translate.ItemSaver(libraryID, var itemSaver = new Zotero.Translate.ItemSaver(libraryID,
Zotero.Translate.ItemSaver.ATTACHMENT_MODE_DOWNLOAD, 1); Zotero.Translate.ItemSaver.ATTACHMENT_MODE_DOWNLOAD, 1, undefined, cookieSandbox);
itemSaver.saveItems(data.items, function(returnValue, data) { itemSaver.saveItems(data.items, function(returnValue, data) {
if(returnValue) { if(returnValue) {
try { try {
@ -390,8 +390,6 @@ Zotero.Server.Connector.SaveSnapshot.prototype = {
// remove browser // remove browser
Zotero.Browser.deleteHiddenBrowser(browser); Zotero.Browser.deleteHiddenBrowser(browser);
// destroy cookieSandbox
cookieSandbox.destroy();
sendResponseCallback(201); sendResponseCallback(201);
} catch(e) { } catch(e) {
sendResponseCallback(500); sendResponseCallback(500);

View file

@ -1437,7 +1437,8 @@ Zotero.Translate.Web.prototype._getParameters = function() { return [this.docume
*/ */
Zotero.Translate.Web.prototype._prepareTranslation = function() { Zotero.Translate.Web.prototype._prepareTranslation = function() {
this._itemSaver = new Zotero.Translate.ItemSaver(this._libraryID, this._itemSaver = new Zotero.Translate.ItemSaver(this._libraryID,
Zotero.Translate.ItemSaver[(this._saveAttachments ? "ATTACHMENT_MODE_DOWNLOAD" : "ATTACHMENT_MODE_IGNORE")], 1); Zotero.Translate.ItemSaver[(this._saveAttachments ? "ATTACHMENT_MODE_DOWNLOAD" : "ATTACHMENT_MODE_IGNORE")], 1,
this.document, this._cookieSandbox);
this.newItems = []; this.newItems = [];
} }

View file

@ -1,7 +1,7 @@
/* /*
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright © 2009 Center for History and New Media Copyright © 2012 Center for History and New Media
George Mason University, Fairfax, Virginia, USA George Mason University, Fairfax, Virginia, USA
http://zotero.org http://zotero.org
@ -23,7 +23,8 @@
***** END LICENSE BLOCK ***** ***** END LICENSE BLOCK *****
*/ */
Zotero.Translate.ItemSaver = function(libraryID, attachmentMode, forceTagType) { Zotero.Translate.ItemSaver = function(libraryID, attachmentMode, forceTagType, document,
cookieSandbox) {
// initialize constants // initialize constants
this.newItems = []; this.newItems = [];
this.newCollections = []; this.newCollections = [];
@ -65,6 +66,7 @@ Zotero.Translate.ItemSaver = function(libraryID, attachmentMode, forceTagType) {
// force tag types if requested // force tag types if requested
this._forceTagType = forceTagType; this._forceTagType = forceTagType;
this._cookieSandbox = cookieSandbox;
}; };
Zotero.Translate.ItemSaver.ATTACHMENT_MODE_IGNORE = 0; Zotero.Translate.ItemSaver.ATTACHMENT_MODE_IGNORE = 0;
@ -346,7 +348,9 @@ Zotero.Translate.ItemSaver.prototype = {
var fileBaseName = Zotero.Attachments.getFileBaseNameFromItem(parentID); var fileBaseName = Zotero.Attachments.getFileBaseNameFromItem(parentID);
try { try {
Zotero.Attachments.importFromURL(attachment.url, parentID, title, fileBaseName); Zotero.debug('Importing attachment from URL');
Zotero.Attachments.importFromURL(attachment.url, parentID, title,
fileBaseName, null, mimeType, this._libraryID, null, this._cookieSandbox);
} catch(e) { } catch(e) {
Zotero.debug("Translate: Error adding attachment "+attachment.url, 2); Zotero.debug("Translate: Error adding attachment "+attachment.url, 2);
} }

@ -1 +1 @@
Subproject commit 3daf3bdf2ce13d7e1a329f5648f18937d8251dee Subproject commit 94bcbbcdc6c4db94391db64dd4b7270e12401e13