Fix duplicate requests to provider sites
When the user follows a link from a proxy-by-port EZProxy to another proxy-by-port EZProxy, we make an additional request to the target site with no cookies. This yields a redirect to the EZProxy login page. We use the query parameters to determine the true domain of the site. Previously, we allowed the redirect to continue, and used the same code we use to detect EZProxy logins to map the proxied domain to the true domain. This caused duplicate requests to providers and apparently infinite loops. We now read the query parameter directly from the redirect to the EZProxy login page and then cancel the request so that the redirect doesn't get followed. The changeset also improves logging of proxy-related information. See https://forums.zotero.org/discussion/28505/ for further discussion.
This commit is contained in:
parent
253c22f58c
commit
107dc081b9
1 changed files with 71 additions and 40 deletions
|
@ -112,14 +112,17 @@ Zotero.Proxies = new function() {
|
||||||
} else {
|
} else {
|
||||||
// otherwise, try to detect a proxy
|
// otherwise, try to detect a proxy
|
||||||
var proxy = false;
|
var proxy = false;
|
||||||
for each(var detector in Zotero.Proxies.Detectors) {
|
for(var detectorName in Zotero.Proxies.Detectors) {
|
||||||
|
var detector = Zotero.Proxies.Detectors[detectorName];
|
||||||
try {
|
try {
|
||||||
proxy = detector(channel);
|
proxy = detector(channel);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
Components.utils.reportError(e);
|
Zotero.logError(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!proxy) continue;
|
if(!proxy) continue;
|
||||||
|
Zotero.debug("Proxies: Detected "+detectorName+" proxy "+proxy.scheme+
|
||||||
|
(proxy.multiHost ? " (multi-host)" : " for "+proxy.hosts[0]));
|
||||||
|
|
||||||
var bw = _getBrowserAndWindow(channel.notificationCallbacks);
|
var bw = _getBrowserAndWindow(channel.notificationCallbacks);
|
||||||
if(!bw) return;
|
if(!bw) return;
|
||||||
|
@ -191,13 +194,13 @@ Zotero.Proxies = new function() {
|
||||||
// If the referrer is a proxiable host, we already have access (e.g., we're
|
// If the referrer is a proxiable host, we already have access (e.g., we're
|
||||||
// on-campus) and shouldn't redirect
|
// on-campus) and shouldn't redirect
|
||||||
if(Zotero.Proxies.properToProxy(channel.referrer.spec, true)) {
|
if(Zotero.Proxies.properToProxy(channel.referrer.spec, true)) {
|
||||||
Zotero.debug("Zotero.Proxies: skipping redirect; referrer was proxiable");
|
Zotero.debug("Proxies: skipping redirect; referrer was proxiable");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// If the referrer is the same host as we're about to redirect to, we shouldn't
|
// If the referrer is the same host as we're about to redirect to, we shouldn't
|
||||||
// or we risk a loop
|
// or we risk a loop
|
||||||
if(channel.referrer.host == proxiedURI.host) {
|
if(channel.referrer.host == proxiedURI.host) {
|
||||||
Zotero.debug("Zotero.Proxies: skipping redirect; redirect URI and referrer have same host");
|
Zotero.debug("Proxies: skipping redirect; redirect URI and referrer have same host");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -206,13 +209,13 @@ Zotero.Proxies = new function() {
|
||||||
// If the original URI was a proxied host, we also shouldn't redirect, since any
|
// If the original URI was a proxied host, we also shouldn't redirect, since any
|
||||||
// links handed out by the proxy should already be proxied
|
// links handed out by the proxy should already be proxied
|
||||||
if(Zotero.Proxies.proxyToProper(channel.originalURI.spec, true)) {
|
if(Zotero.Proxies.proxyToProper(channel.originalURI.spec, true)) {
|
||||||
Zotero.debug("Zotero.Proxies: skipping redirect; original URI was proxied");
|
Zotero.debug("Proxies: skipping redirect; original URI was proxied");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Finally, if the original URI is the same as the host we're about to redirect
|
// Finally, if the original URI is the same as the host we're about to redirect
|
||||||
// to, then we also risk a loop
|
// to, then we also risk a loop
|
||||||
if(channel.originalURI.host == proxiedURI.host) {
|
if(channel.originalURI.host == proxiedURI.host) {
|
||||||
Zotero.debug("Zotero.Proxies: skipping redirect; redirect URI and original URI have same host");
|
Zotero.debug("Proxies: skipping redirect; redirect URI and original URI have same host");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -223,7 +226,7 @@ Zotero.Proxies = new function() {
|
||||||
top21 = top2DomainsRe.exec(channel.URI.host);
|
top21 = top2DomainsRe.exec(channel.URI.host);
|
||||||
top22 = top2DomainsRe.exec(proxiedURI.host);
|
top22 = top2DomainsRe.exec(proxiedURI.host);
|
||||||
if(!top21 || !top22 || top21[0] == top22[0]) {
|
if(!top21 || !top22 || top21[0] == top22[0]) {
|
||||||
Zotero.debug("Zotero.Proxies: skipping redirect; redirect URI and URI have same top 2 domains");
|
Zotero.debug("Proxies: skipping redirect; redirect URI and URI have same top 2 domains");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -300,7 +303,7 @@ Zotero.Proxies = new function() {
|
||||||
var m = proxy.regexp.exec(url);
|
var m = proxy.regexp.exec(url);
|
||||||
if(m) {
|
if(m) {
|
||||||
var toProper = proxy.toProper(m);
|
var toProper = proxy.toProper(m);
|
||||||
Zotero.debug("Zotero.Proxies.proxyToProper: "+url+" to "+toProper);
|
Zotero.debug("Proxies.proxyToProper: "+url+" to "+toProper);
|
||||||
return toProper;
|
return toProper;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -320,7 +323,7 @@ Zotero.Proxies = new function() {
|
||||||
var uri = ioService.newURI(url, null, null);
|
var uri = ioService.newURI(url, null, null);
|
||||||
if(Zotero.Proxies.hosts[uri.hostPort] && Zotero.Proxies.hosts[uri.hostPort].proxyID) {
|
if(Zotero.Proxies.hosts[uri.hostPort] && Zotero.Proxies.hosts[uri.hostPort].proxyID) {
|
||||||
var toProxy = Zotero.Proxies.hosts[uri.hostPort].toProxy(uri);
|
var toProxy = Zotero.Proxies.hosts[uri.hostPort].toProxy(uri);
|
||||||
Zotero.debug("Zotero.Proxies.properToProxy: "+url+" to "+toProxy);
|
Zotero.debug("Proxies.properToProxy: "+url+" to "+toProxy);
|
||||||
return toProxy;
|
return toProxy;
|
||||||
}
|
}
|
||||||
return (onlyReturnIfProxied ? false : url);
|
return (onlyReturnIfProxied ? false : url);
|
||||||
|
@ -536,9 +539,9 @@ Zotero.Proxy.prototype.validate = function() {
|
||||||
return ["scheme.noHTTP"];
|
return ["scheme.noHTTP"];
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!this.multiSite && (!this.hosts.length || !this.hosts[0])) {
|
if(!this.multiHost && (!this.hosts.length || !this.hosts[0])) {
|
||||||
return ["host.invalid"];
|
return ["host.invalid"];
|
||||||
} else if(this.multiSite && !Zotero_Proxy_schemeParameterRegexps["%h"].test(this.scheme)) {
|
} else if(this.multiHost && !Zotero_Proxy_schemeParameterRegexps["%h"].test(this.scheme)) {
|
||||||
return ["scheme.noHost"];
|
return ["scheme.noHost"];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -570,7 +573,7 @@ Zotero.Proxy.prototype.validate = function() {
|
||||||
Zotero.Proxy.prototype.save = function(transparent) {
|
Zotero.Proxy.prototype.save = function(transparent) {
|
||||||
// ensure this proxy is valid
|
// ensure this proxy is valid
|
||||||
var hasErrors = this.validate();
|
var hasErrors = this.validate();
|
||||||
if(hasErrors) throw "Zotero.Proxy: could not be saved because it is invalid: error "+hasErrors[0];
|
if(hasErrors) throw "Proxy: could not be saved because it is invalid: error "+hasErrors[0];
|
||||||
|
|
||||||
// we never save any changes to non-persisting proxies, so this works
|
// we never save any changes to non-persisting proxies, so this works
|
||||||
var newProxy = !this.proxyID;
|
var newProxy = !this.proxyID;
|
||||||
|
@ -610,7 +613,7 @@ Zotero.Proxy.prototype.save = function(transparent) {
|
||||||
Zotero.Proxies.save(this);
|
Zotero.Proxies.save(this);
|
||||||
} else {
|
} else {
|
||||||
Zotero.Proxies.refreshHostMap(this);
|
Zotero.Proxies.refreshHostMap(this);
|
||||||
if(!transparent) throw "Zotero.Proxy: cannot save transparent proxy without transparent param";
|
if(!transparent) throw "Proxy: cannot save transparent proxy without transparent param";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -719,8 +722,6 @@ Zotero.Proxies.Detectors = new Object();
|
||||||
* @type Boolean|Zotero.Proxy
|
* @type Boolean|Zotero.Proxy
|
||||||
*/
|
*/
|
||||||
Zotero.Proxies.Detectors.EZProxy = function(channel) {
|
Zotero.Proxies.Detectors.EZProxy = function(channel) {
|
||||||
const ezProxyRe = /\?(?:.+&)?(url|qurl)=([^&]+)/i;
|
|
||||||
|
|
||||||
// Try to catch links from one proxy-by-port site to another
|
// Try to catch links from one proxy-by-port site to another
|
||||||
if([80, 443, -1].indexOf(channel.URI.port) == -1) {
|
if([80, 443, -1].indexOf(channel.URI.port) == -1) {
|
||||||
// Two options here: we could have a redirect from an EZProxy site to another, or a link
|
// Two options here: we could have a redirect from an EZProxy site to another, or a link
|
||||||
|
@ -729,8 +730,7 @@ Zotero.Proxies.Detectors.EZProxy = function(channel) {
|
||||||
var fromProxy = false;
|
var fromProxy = false;
|
||||||
if([301, 302, 303].indexOf(channel.responseStatus) !== -1) {
|
if([301, 302, 303].indexOf(channel.responseStatus) !== -1) {
|
||||||
try {
|
try {
|
||||||
toProxy = Zotero.Proxies.Detectors.EZProxy.ios.newURI(
|
toProxy = Services.io.newURI(channel.getResponseHeader("Location"), null, null);
|
||||||
channel.getResponseHeader("Location"), null, null);
|
|
||||||
fromProxy = channel.URI;
|
fromProxy = channel.URI;
|
||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
} else {
|
} else {
|
||||||
|
@ -753,14 +753,12 @@ Zotero.Proxies.Detectors.EZProxy = function(channel) {
|
||||||
|
|
||||||
// Create a new nsIObserver and nsIChannel to figure out real URL (by failing to
|
// Create a new nsIObserver and nsIChannel to figure out real URL (by failing to
|
||||||
// send cookies, so we get back to the login page)
|
// send cookies, so we get back to the login page)
|
||||||
var newChannel = Zotero.Proxies.Detectors.EZProxy.ios.newChannelFromURI(toProxy);
|
var newChannel = Services.io.newChannelFromURI(toProxy);
|
||||||
newChannel.originalURI = channel.originalURI ? channel.originalURI : channel.URI;
|
newChannel.originalURI = channel.originalURI ? channel.originalURI : channel.URI;
|
||||||
newChannel.QueryInterface(Components.interfaces.nsIRequest).loadFlags = newChannel.loadFlags |
|
newChannel.QueryInterface(Components.interfaces.nsIRequest).loadFlags = newChannel.loadFlags;
|
||||||
Components.interfaces.nsIHttpChannel.LOAD_DOCUMENT_URI;
|
Zotero.debug("Proxies: Identified putative port-by-port EZProxy link from "+fromProxy.hostPort+" to "+toProxy.hostPort);
|
||||||
|
|
||||||
Zotero.Proxies.Detectors.EZProxy.obs.addObserver(
|
new Zotero.Proxies.Detectors.EZProxy.Observer(newChannel);
|
||||||
new Zotero.Proxies.Detectors.EZProxy.Observer(newChannel),
|
|
||||||
"http-on-modify-request", false);
|
|
||||||
newChannel.asyncOpen(new Zotero.Proxies.Detectors.EZProxy.DummyStreamListener(), null);
|
newChannel.asyncOpen(new Zotero.Proxies.Detectors.EZProxy.DummyStreamListener(), null);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -768,19 +766,25 @@ Zotero.Proxies.Detectors.EZProxy = function(channel) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now try to catch redirects
|
// Now try to catch redirects
|
||||||
|
if(channel.responseStatus != 302) return false;
|
||||||
try {
|
try {
|
||||||
if(channel.getResponseHeader("Server") != "EZproxy") return false;
|
if(channel.getResponseHeader("Server") != "EZproxy") return false;
|
||||||
|
var proxiedURI = Services.io.newURI(channel.getResponseHeader("Location"), null, null);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
|
return Zotero.Proxies.Detectors.EZProxy.learn(channel.URI, proxiedURI);
|
||||||
// Get the new URL
|
}
|
||||||
if(channel.responseStatus != 302) return false;
|
|
||||||
var proxiedURL = channel.getResponseHeader("Location");
|
/**
|
||||||
if(!proxiedURL) return false;
|
* Learn about a mapping from an EZProxy to a normal proxy
|
||||||
var proxiedURI = Zotero.Proxies.Detectors.EZProxy.ios.newURI(proxiedURL, null, null);
|
* @param {nsIURI} loginURI The URL of the login page
|
||||||
|
* @param {nsIURI} proxiedURI The URI of the page
|
||||||
|
* @return {Zotero.Proxy | false}
|
||||||
|
*/
|
||||||
|
Zotero.Proxies.Detectors.EZProxy.learn = function(loginURI, proxiedURI) {
|
||||||
// look for query
|
// look for query
|
||||||
var m = ezProxyRe.exec(channel.URI.spec);
|
var m = /\?(?:.+&)?(url|qurl)=([^&]+)/i.exec(loginURI.path);
|
||||||
if(!m) return false;
|
if(!m) return false;
|
||||||
|
|
||||||
// Ignore if we already know about it
|
// Ignore if we already know about it
|
||||||
|
@ -788,16 +792,16 @@ Zotero.Proxies.Detectors.EZProxy = function(channel) {
|
||||||
|
|
||||||
// Found URL
|
// Found URL
|
||||||
var properURL = (m[1].toLowerCase() == "qurl" ? unescape(m[2]) : m[2]);
|
var properURL = (m[1].toLowerCase() == "qurl" ? unescape(m[2]) : m[2]);
|
||||||
var properURI = Zotero.Proxies.Detectors.EZProxy.ios.newURI(properURL, null, null);
|
var properURI = Services.io.newURI(properURL, null, null);
|
||||||
|
|
||||||
var proxy = false;
|
var proxy = false;
|
||||||
if(channel.URI.host == proxiedURI.host && [channel.URI.port, 80, 443, -1].indexOf(proxiedURI.port) == -1) {
|
if(loginURI.host == proxiedURI.host && [loginURI.port, 80, 443, -1].indexOf(proxiedURI.port) == -1) {
|
||||||
// Proxy by port
|
// Proxy by port
|
||||||
proxy = new Zotero.Proxy();
|
proxy = new Zotero.Proxy();
|
||||||
proxy.multiHost = false;
|
proxy.multiHost = false;
|
||||||
proxy.scheme = proxiedURI.scheme+"://"+proxiedURI.hostPort+"/%p";
|
proxy.scheme = proxiedURI.scheme+"://"+proxiedURI.hostPort+"/%p";
|
||||||
proxy.hosts = [properURI.hostPort];
|
proxy.hosts = [properURI.hostPort];
|
||||||
} else if(proxiedURI.host != channel.URI.host && proxiedURI.hostPort.indexOf(properURI.host) != -1) {
|
} else if(proxiedURI.host != loginURI.host && proxiedURI.hostPort.indexOf(properURI.host) != -1) {
|
||||||
// Proxy by host
|
// Proxy by host
|
||||||
proxy = new Zotero.Proxy();
|
proxy = new Zotero.Proxy();
|
||||||
proxy.multiHost = proxy.autoAssociate = true;
|
proxy.multiHost = proxy.autoAssociate = true;
|
||||||
|
@ -806,10 +810,6 @@ Zotero.Proxies.Detectors.EZProxy = function(channel) {
|
||||||
}
|
}
|
||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
Zotero.Proxies.Detectors.EZProxy.ios = Components.classes["@mozilla.org/network/io-service;1"]
|
|
||||||
.getService(Components.interfaces.nsIIOService);
|
|
||||||
Zotero.Proxies.Detectors.EZProxy.obs = Components.classes["@mozilla.org/observer-service;1"]
|
|
||||||
.getService(Components.interfaces.nsIObserverService);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class Do-nothing stream listener
|
* @class Do-nothing stream listener
|
||||||
|
@ -827,11 +827,42 @@ Zotero.Proxies.Detectors.EZProxy.DummyStreamListener.prototype.onStopRequest = f
|
||||||
*/
|
*/
|
||||||
Zotero.Proxies.Detectors.EZProxy.Observer = function(newChannel) {
|
Zotero.Proxies.Detectors.EZProxy.Observer = function(newChannel) {
|
||||||
this.channel = newChannel;
|
this.channel = newChannel;
|
||||||
|
Services.obs.addObserver(this, "http-on-modify-request", false);
|
||||||
|
Services.obs.addObserver(this, "http-on-examine-response", false);
|
||||||
}
|
}
|
||||||
Zotero.Proxies.Detectors.EZProxy.Observer.prototype.observe = function(aSubject, aTopic, aData) {
|
Zotero.Proxies.Detectors.EZProxy.Observer.prototype.observe = function(aSubject, aTopic, aData) {
|
||||||
if (aSubject == this.channel) {
|
if (aSubject == this.channel) {
|
||||||
aSubject.QueryInterface(Components.interfaces.nsIHttpChannel).setRequestHeader("Cookie", "", false);
|
if(aTopic === "http-on-modify-request") {
|
||||||
Zotero.Proxies.Detectors.EZProxy.obs.removeObserver(this, "http-on-modify-request");
|
try {
|
||||||
|
aSubject.QueryInterface(Components.interfaces.nsIHttpChannel).setRequestHeader("Cookie", "", false);
|
||||||
|
} catch(e) {
|
||||||
|
Zotero.logError(e);
|
||||||
|
} finally {
|
||||||
|
Services.obs.removeObserver(this, "http-on-modify-request");
|
||||||
|
}
|
||||||
|
} else if(aTopic === "http-on-examine-response") {
|
||||||
|
try {
|
||||||
|
// Make sure this is a redirect involving an EZProxy
|
||||||
|
if(aSubject.responseStatus !== 302) return;
|
||||||
|
try {
|
||||||
|
if(aSubject.getResponseHeader("Server") !== "EZproxy") return;
|
||||||
|
var loginURL = aSubject.getResponseHeader("Location");
|
||||||
|
} catch(e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var proxy = Zotero.Proxies.Detectors.EZProxy.learn(Services.io.newURI(loginURL, null, null), aSubject.URI);
|
||||||
|
if(proxy) {
|
||||||
|
Zotero.debug("Proxies: Proxy-by-port EZProxy "+aSubject.URI.hostPort+" corresponds to "+proxy.hosts[0]);
|
||||||
|
proxy.save();
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
Zotero.logError(e);
|
||||||
|
} finally {
|
||||||
|
Services.obs.removeObserver(this, "http-on-examine-response");
|
||||||
|
aSubject.cancel(0x80004004 /*NS_ERROR_ABORT*/);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Zotero.Proxies.Detectors.EZProxy.Observer.prototype.QueryInterface = function(aIID) {
|
Zotero.Proxies.Detectors.EZProxy.Observer.prototype.QueryInterface = function(aIID) {
|
||||||
|
|
Loading…
Reference in a new issue