From 79d759fd8d42db9b45de0e9dfdac873fa5b98613 Mon Sep 17 00:00:00 2001 From: Simon Kornblith Date: Tue, 29 Jul 2008 05:08:26 +0000 Subject: [PATCH] Better support for proxy-by-port EZProxies (like the GMU EZProxy). When we see a link or redirect from (e.g.) mutex.gmu.edu:3793 to mutex.gmu.edu:3618, we now make a background request to the latter site with no cookies in order to get the EZProxy log-in screen. From the log-in screen, we can find the real URL of the latter site. --- chrome/content/zotero/xpcom/proxy.js | 161 ++++++++++++++++++++------- 1 file changed, 119 insertions(+), 42 deletions(-) diff --git a/chrome/content/zotero/xpcom/proxy.js b/chrome/content/zotero/xpcom/proxy.js index 4ca54dd4a..1785b7283 100644 --- a/chrome/content/zotero/xpcom/proxy.js +++ b/chrome/content/zotero/xpcom/proxy.js @@ -35,6 +35,8 @@ Zotero.Proxies = new function() { .getService(Components.interfaces.nsIIOService); var autoRecognize = false; var transparent = false; + var lastRecognizedURI = false; + var lastButton = false; this.init = function() { if(!on) { @@ -76,20 +78,23 @@ Zotero.Proxies = new function() { var url = channel.URI.spec; // see if there is a proxy we already know + var m = false; var proxy; for each(proxy in proxies) { - if(proxy.regexp && proxy.autoAssociate) { - var m = proxy.regexp.exec(url); + if(proxy.regexp && proxy.multiHost) { + m = proxy.regexp.exec(url); if(m) break; } } if(m) { // add this host if we know a proxy - var host = m[proxy.parameters.indexOf("%h")+1]; - if(proxy.hosts.indexOf(host) == -1) { - proxy.hosts.push(host); - proxy.save(); + if(proxy.autoAssociate) { + var host = m[proxy.parameters.indexOf("%h")+1]; + if(proxy.hosts.indexOf(host) == -1) { + proxy.hosts.push(host); + proxy.save(); + } } } else if(autoRecognize) { // otherwise, try to detect a proxy @@ -109,14 +114,17 @@ Zotero.Proxies = new function() { .getService(Components.interfaces.nsIWindowMediator) .getMostRecentWindow("navigator:browser"); - var button = ps.confirmEx(window, - Zotero.getString("proxies.recognized"), - Zotero.getString("proxies.recognized.message"), - ((proxies.length ? 0 : ps.BUTTON_DELAY_ENABLE) + ps.BUTTON_POS_0 * ps.BUTTON_TITLE_OK + - ps.BUTTON_POS_1 * ps.BUTTON_TITLE_CANCEL + ps.BUTTON_POS_1_DEFAULT), - null, null, null, Zotero.getString("proxies.recognized.disable"), checkState); + if(!lastRecognizedURI || !channel.originalURI || !channel.originalURI.equals(lastRecognizedURI)) { + lastButton = ps.confirmEx(window, + Zotero.getString("proxies.recognized"), + Zotero.getString("proxies.recognized.message"), + ((proxies.length ? 0 : ps.BUTTON_DELAY_ENABLE) + ps.BUTTON_POS_0 * ps.BUTTON_TITLE_OK + + ps.BUTTON_POS_1 * ps.BUTTON_TITLE_CANCEL + ps.BUTTON_POS_1_DEFAULT), + null, null, null, Zotero.getString("proxies.recognized.disable"), checkState); + lastRecognizedURI = channel.originalURI ? channel.originalURI : channel.URI; + } - if(button == 0) proxy.save(); + if(lastButton == 0) proxy.save(); if(checkState.value) { autoRecognize = false; Zotero.Prefs.set("proxies.autoRecognize", false); @@ -457,53 +465,122 @@ Zotero.Proxies.Detectors = new Object(); */ Zotero.Proxies.Detectors.EZProxy = function(channel) { const ezProxyRe = /\?(?:.+&)?(url|qurl)=([^&]+)/i; - try { - if(channel.getResponseHeader("Server") != "EZproxy" || channel.responseStatus != "302") { - return false; + + // Try to catch links from one proxy-by-port site to another + 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 + // If it's a redirect, we'll have to catch the Location: header + var toProxy = false; + var fromProxy = false; + if([301, 302, 303].indexOf(channel.responseStatus) !== -1) { + try { + toProxy = Zotero.Proxies.Detectors.EZProxy.ios.newURI( + channel.getResponseHeader("Location"), null, null); + fromProxy = channel.URI; + } catch(e) {} + } else { + toProxy = channel.URI; + fromProxy = channel.referrer; } + + if(fromProxy && toProxy && fromProxy.host == toProxy.host && fromProxy.port != toProxy.port + && [80, 443, -1].indexOf(toProxy.port) == -1) { + var proxy; + for each(proxy in Zotero.Proxies.get()) { + if(proxy.regexp) { + var m = proxy.regexp.exec(fromProxy.spec); + if(m) break; + } + } + if(m) { + // Make sure caught proxy is not multi-host and that we don't have this new proxy already + if(proxy.multiHost || Zotero.Proxies.proxyToProper(toProxy.spec, true)) return false; + + // Create a new nsIObserver and nsIChannel to figure out real URL (by failing to + // send cookies, so we get back to the login page) + var newChannel = Zotero.Proxies.Detectors.EZProxy.ios.newChannelFromURI(toProxy); + newChannel.originalURI = channel.originalURI ? channel.originalURI : channel.URI; + newChannel.QueryInterface(Components.interfaces.nsIRequest).loadFlags = newChannel.loadFlags | + Components.interfaces.nsIHttpChannel.LOAD_DOCUMENT_URI; + + Zotero.Proxies.Detectors.EZProxy.obs.addObserver( + new Zotero.Proxies.Detectors.EZProxy.Observer(newChannel), + "http-on-modify-request", false); + newChannel.asyncOpen(new Zotero.Proxies.Detectors.EZProxy.DummyStreamListener(), null); + return false; + } + } + } + + // Now try to catch redirects + try { + if(channel.getResponseHeader("Server") != "EZproxy") return false; } catch(e) { return false } - - // We should be able to scrape the URL out of this + + // Get the new URL + if(channel.responseStatus != 302) return false; + var proxiedURL = channel.getResponseHeader("Location"); + if(!proxiedURL) return false; + var proxiedURI = Zotero.Proxies.Detectors.EZProxy.ios.newURI(proxiedURL, null, null); + // look for query var m = ezProxyRe.exec(channel.URI.spec); if(!m) return false; - var ioService = Components.classes["@mozilla.org/network/io-service;1"] - .getService(Components.interfaces.nsIIOService); + // Ignore if we already know about it + if(Zotero.Proxies.proxyToProper(proxiedURI.spec, true)) return false; // Found URL - var properURL = m[2]; - if(m[1].toLowerCase() == "qurl") properURL = unescape(properURL); - var properURI = ioService.newURI(properURL, null, null); - if(!properURI) return false; + var properURL = (m[1].toLowerCase() == "qurl" ? unescape(m[2]) : m[2]); + var properURI = Zotero.Proxies.Detectors.EZProxy.ios.newURI(properURL, null, null); - // Get the new URL - var newURL = channel.getResponseHeader("Location"); - if(!newURL) return false; - - // Ignore if we already know about it - if(Zotero.Proxies.proxyToProper(newURL, true)) return false; - - // parse into nsIURI - var newURI = ioService.newURI(newURL, null, null); - if(!newURI) return false; - - if(channel.URI.host == newURI.host && [channel.URI.port, 80, 443, -1].indexOf(newURI.port) == -1) { - // Old style per-port + if(channel.URI.host == proxiedURI.host && [channel.URI.port, 80, 443, -1].indexOf(proxiedURI.port) == -1) { + // Proxy by port var proxy = new Zotero.Proxy(); proxy.multiHost = false; - proxy.scheme = newURI.scheme+"://"+newURI.hostPort+"/%p"; + proxy.scheme = proxiedURI.scheme+"://"+proxiedURI.hostPort+"/%p"; proxy.hosts = [properURI.hostPort]; - } else if(newURI.host != channel.URI.host) { - // New style rewriting + } else if(proxiedURI.host != channel.URI.host) { + // Proxy by host var proxy = new Zotero.Proxy(); proxy.multiHost = proxy.autoAssociate = true; - proxy.scheme = newURI.scheme+"://"+newURI.hostPort.replace(properURI.host, "%h")+"/%p"; + proxy.scheme = proxiedURI.scheme+"://"+proxiedURI.hostPort.replace(properURI.host, "%h")+"/%p"; proxy.hosts = [properURI.hostPort]; } 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); + +/** + * Do-nothing stream listener + */ +Zotero.Proxies.Detectors.EZProxy.DummyStreamListener = function() {} +Zotero.Proxies.Detectors.EZProxy.DummyStreamListener.prototype.onDataAvailable = function(request, + context, inputStream, offset, count) {} +Zotero.Proxies.Detectors.EZProxy.DummyStreamListener.prototype.onStartRequest = function(request, context) {} +Zotero.Proxies.Detectors.EZProxy.DummyStreamListener.prototype.onStopRequest = function(request, context, status) {} + +/** + * Observer to clear cookies on an HTTP request, then remove itself + */ +Zotero.Proxies.Detectors.EZProxy.Observer = function(newChannel) { + this.channel = newChannel; +} +Zotero.Proxies.Detectors.EZProxy.Observer.prototype.observe = function(aSubject, aTopic, aData) { + if (aSubject == this.channel) { + aSubject.QueryInterface(Components.interfaces.nsIHttpChannel).setRequestHeader("Cookie", "", false); + Zotero.Proxies.Detectors.EZProxy.obs.removeObserver(this, "http-on-modify-request"); + } +} +Zotero.Proxies.Detectors.EZProxy.Observer.prototype.QueryInterface = function(aIID) { + if (aIID.equals(Components.interfaces.nsISupports) || + aIID.equals(Components.interfaces.nsIObserver)) return this; + throw Components.results.NS_NOINTERFACE; +} /** * Detector for Juniper Networks WebVPN @@ -519,7 +596,7 @@ Zotero.Proxies.Detectors.Juniper = function(channel) { if(!m) return false; var proxy = new Zotero.Proxy(); - proxy.multiHost = true; + proxy.multiHost = proxy.autoAssociate = true; proxy.scheme = m[1]+"/%d"+",DanaInfo=%h%a+%f"; proxy.hosts = [m[3]]; return proxy;