diff --git a/chrome/content/zotero/xpcom/http.js b/chrome/content/zotero/xpcom/http.js index 2ae32769f..898b89251 100644 --- a/chrome/content/zotero/xpcom/http.js +++ b/chrome/content/zotero/xpcom/http.js @@ -473,29 +473,116 @@ Zotero.HTTP = new function() { var deferred = Q.defer(); Zotero.proxyAuthComplete = deferred.promise; - var uri = ZOTERO_CONFIG.PROXY_AUTH_URL; - - Zotero.debug("HTTP GET " + uri); - - var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"] - .createInstance(); - xmlhttp.open("GET", uri, true); - - xmlhttp.channel.loadFlags |= Components.interfaces.nsIRequest.LOAD_BYPASS_CACHE; - - var useMethodjit = Components.utils.methodjit; - /** @ignore */ - xmlhttp.onreadystatechange = function() { - // XXX Remove when we drop support for Fx <24 - if(useMethodjit !== undefined) Components.utils.methodjit = useMethodjit; - _stateChange(xmlhttp, function (xmlhttp) { - Zotero.debug("Proxy auth request completed with status " - + xmlhttp.status + ": " + xmlhttp.responseText); + Q.fcall(function () { + var uris = Zotero.Prefs.get('proxyAuthenticationURLs').split(','); + uris = Zotero.Utilities.arrayShuffle(uris); + uris.unshift(ZOTERO_CONFIG.PROXY_AUTH_URL); + + return Q.async(function () { + let max = 3; // how many URIs to try after the general Zotero one + for (let i = 0; i <= max; i++) { + let uri = uris.shift(); + if (!uri) { + break; + } + + // For non-Zotero URLs, wait for PAC initialization, + // in a rather ugly and inefficient manner + if (i == 1) { + let installed = yield Q.fcall(_pacInstalled) + .then(function (installed) { + if (installed) throw true; + }) + .delay(500) + .then(_pacInstalled) + .then(function (installed) { + if (installed) throw true; + }) + .delay(1000) + .then(_pacInstalled) + .then(function (installed) { + if (installed) throw true; + }) + .delay(2000) + .then(_pacInstalled) + .catch(function () { + return true; + }); + if (!installed) { + Zotero.debug("No general proxy or PAC file found -- assuming direct connection"); + break; + } + } + + let proxyInfo = yield _proxyAsyncResolve(uri); + if (proxyInfo) { + Zotero.debug("Proxy required for " + uri + " -- making HEAD request to trigger auth prompt"); + yield Zotero.HTTP.promise("HEAD", uri, { + foreground: true, + dontCache: true + }) + .catch(function (e) { + Components.utils.reportError(e); + var msg = "Error connecting to proxy -- proxied requests may not work"; + Zotero.log(msg, 'error'); + Zotero.debug(msg, 1); + }); + break; + } + else { + Zotero.debug("Proxy not required for " + uri); + } + } deferred.resolve(); - }); - }; - xmlhttp.send(null); - return xmlhttp; + })(); + }) + .catch(function (e) { + Components.utils.reportError(e); + Zotero.debug(e, 1); + deferred.resolve(); + }); + } + + + /** + * Test if a PAC file is installed + * + * There might be a better way to do this that doesn't require stepping + * through the error log and doing a fragile string comparison. + */ + _pacInstalled = function () { + return Zotero.getErrors(true).some(function (val) val.indexOf("PAC file installed") == 0) + } + + + _proxyAsyncResolve = function (uri) { + Components.utils.import("resource://gre/modules/NetUtil.jsm"); + var pps = Components.classes["@mozilla.org/network/protocol-proxy-service;1"] + .getService(Components.interfaces.nsIProtocolProxyService); + var deferred = Q.defer(); + pps.asyncResolve( + NetUtil.newURI(uri), + 0, + { + onProxyAvailable: function (req, uri, proxyInfo, status) { + //Zotero.debug("onProxyAvailable"); + //Zotero.debug(status); + deferred.resolve(proxyInfo); + }, + + QueryInterface: function (iid) { + const interfaces = [ + Components.interfaces.nsIProtocolProxyCallback, + Components.interfaces.nsISupports + ]; + if (!interfaces.some(function(v) { return iid.equals(v) })) { + throw Components.results.NS_ERROR_NO_INTERFACE; + } + return this; + }, + } + ); + return deferred.promise; } diff --git a/chrome/content/zotero/xpcom/utilities.js b/chrome/content/zotero/xpcom/utilities.js index d00362c78..bf62b133d 100644 --- a/chrome/content/zotero/xpcom/utilities.js +++ b/chrome/content/zotero/xpcom/utilities.js @@ -575,6 +575,31 @@ Zotero.Utilities = { return vals; }, + /** + * Return new array with values shuffled + * + * From http://stackoverflow.com/a/6274398 + * + * @param {Array} arr + * @return {Array} + */ + "arrayShuffle": function (array) { + var counter = array.length, temp, index; + + // While there are elements in the array + while (counter--) { + // Pick a random index + index = (Math.random() * counter) | 0; + + // And swap the last element with it + temp = array[counter]; + array[counter] = array[index]; + array[index] = temp; + } + + return array; + }, + /** * Return new array with duplicate values removed diff --git a/defaults/preferences/zotero.js b/defaults/preferences/zotero.js index 5e4b710c5..9c171f84f 100644 --- a/defaults/preferences/zotero.js +++ b/defaults/preferences/zotero.js @@ -22,6 +22,8 @@ pref("extensions.zotero.debug.time", false); pref("extensions.zotero.automaticScraperUpdates",true); pref("extensions.zotero.zoteroDotOrgVersionHeader", true); pref("extensions.zotero.triggerProxyAuthentication", true); +// Proxy auth URLs should respond successfully to HEAD requests over HTTP and HTTPS (in case of forced HTTPS requests) +pref("extensions.zotero.proxyAuthenticationURLs", 'http://www.acm.org,http://www.ebscohost.com,http://www.elsevier.com,http://www.ieee.org,http://www.jstor.org,http://www.ovid.com,http://www.springer.com,http://www.tandfonline.com'); pref("extensions.zotero.cacheTranslatorData",true); pref("extensions.zotero.showIn", 1); pref("extensions.zotero.statusBarIcon", 2);