From f9a0db633c2fdac65c3a222047697fa175f2b593 Mon Sep 17 00:00:00 2001 From: Simon Kornblith Date: Thu, 1 Sep 2011 06:53:20 +0000 Subject: [PATCH] IE cross-domain messaging hack --- .../zotero/xpcom/connector/connector.js | 77 ++++++++++++++++--- chrome/content/zotero/xpcom/server.js | 25 +++--- .../content/zotero/xpcom/server_connector.js | 29 ++++++- 3 files changed, 111 insertions(+), 20 deletions(-) diff --git a/chrome/content/zotero/xpcom/connector/connector.js b/chrome/content/zotero/xpcom/connector/connector.js index 721e1a77fa..36b8c40fcf 100644 --- a/chrome/content/zotero/xpcom/connector/connector.js +++ b/chrome/content/zotero/xpcom/connector/connector.js @@ -26,7 +26,9 @@ Zotero.Connector = new function() { const CONNECTOR_URI = "http://127.0.0.1:23119/"; const CONNECTOR_API_VERSION = 2; + const IE_HACK_MSG = "ZOTERO_IE_HACK_MSG "; + var _ieStandaloneIframeTarget; this.isOnline = null; /** @@ -34,9 +36,32 @@ Zotero.Connector = new function() { * @param {Function} callback */ this.checkIsOnline = function(callback) { - Zotero.Connector.callMethod("ping", {}, function(status) { - callback(status !== false); - }); + // Only check once in bookmarklet + if(Zotero.isBookmarklet && this.isOnline !== null) callback(this.isOnline); + + if(Zotero.isIE) { + var listener = function(event) { + if(event.origin === "http://www.zotero.org" && event.data === "ZOTERO_IE_STANDALONE_LOADED false") { + callback(false); + } else if(event.origin === "http://127.0.0.1:23119" && event.data === "ZOTERO_IE_STANDALONE_LOADED true") { + _ieStandaloneIframeTarget = event.source; + callback(true); + } else { + return; + } + window.removeEventListener("message", listener, false); + }; + window.addEventListener("message", listener, false); + + Zotero.debug("Connector: Trying IE hack"); + var s = document.createElement("iframe"); + s.src = "http://www.zotero.org/bookmarklet/ie_hack.html"; + (document.body ? document.body : document.documentElement).appendChild(s); + } else { + Zotero.Connector.callMethod("ping", {}, function(status) { + callback(status !== false); + }); + } } // saner descriptions of some HTTP error codes @@ -56,6 +81,9 @@ Zotero.Connector = new function() { * @param {Function} callback Function to be called when requests complete. */ this.callMethod = function(method, data, callback) { + // Don't bother trying if not online in bookmarklet + if(Zotero.isBookmarklet && this.isOnline === false) callback(false, 0); + var newCallback = function(req) { try { var isOnline = req.status !== Zotero.Connector.EXCEPTION_NOT_AVAILABLE @@ -98,14 +126,43 @@ Zotero.Connector = new function() { return; } }; - var uri = CONNECTOR_URI+"connector/"+method; - Zotero.HTTP.doPost(uri, JSON.stringify(data), - newCallback, { - "Content-Type":"application/json", - "X-Zotero-Version":Zotero.version, - "X-Zotero-Connector-API-Version":CONNECTOR_API_VERSION - }); + var uri = CONNECTOR_URI+"connector/"+method; + if(Zotero.isIE) { // IE requires XDR for CORS + if(_ieStandaloneIframeTarget) { + var requestID = Zotero.Utilities.randomString(); + + var listener = function(event) { + if(event.origin === "http://127.0.0.1:23119" && event.data.substr(0, IE_HACK_MSG.length) === IE_HACK_MSG) { + var data = JSON.parse(event.data.substr(IE_HACK_MSG.length)); + if(data[0] !== "connectorResponse" || data[1][0] !== requestID) return; + + window.removeEventListener("message", listener, false); + + var xhrSurrogate = { + "status":data[1][1], + "responseText":data[1][2], + "getResponseHeader":function(x) { return data[1][3][x] } + }; + newCallback(xhrSurrogate); + } + }; + + window.addEventListener("message", listener, false); + _ieStandaloneIframeTarget.postMessage(IE_HACK_MSG+" "+JSON.stringify(["connectorRequest", + [requestID, method, JSON.stringify(data)]])); + } else { + Zotero.debug("Connector: No iframe target; not sending to Standalone"); + callback(false, 0); + } + } else { // Other browsers can use plain doPost + Zotero.HTTP.doPost(uri, JSON.stringify(data), + newCallback, { + "Content-Type":"application/json", + "X-Zotero-Version":Zotero.version, + "X-Zotero-Connector-API-Version":CONNECTOR_API_VERSION + }); + } } } diff --git a/chrome/content/zotero/xpcom/server.js b/chrome/content/zotero/xpcom/server.js index e301e856df..1df8ea9d75 100755 --- a/chrome/content/zotero/xpcom/server.js +++ b/chrome/content/zotero/xpcom/server.js @@ -65,14 +65,12 @@ Zotero.Server = new function() { /** * generates the response to an HTTP request */ - this.generateResponse = function (status, contentType, body) { + this.generateResponse = function (status, contentType, body, headers) { var response = "HTTP/1.0 "+status+" "+responseCodes[status]+"\r\n"; if(!Zotero.isServer) { response += "X-Zotero-Version: "+Zotero.version+"\r\n"; response += "X-Zotero-Connector-API-Version: "+CONNECTOR_API_VERSION+"\r\n"; - response += "Access-Control-Allow-Origin: "+ZOTERO_CONFIG.BOOKMARKLET_URL+"iframe.html\r\n"; - response += "Access-Control-Allow-Methods: POST, GET, OPTIONS\r\n"; - response += "Access-Control-Allow-Headers: Content-Type,X-Zotero-Connector-API-Version,X-Zotero-Version\r\n"; + if(headers) response += headers; } if(body) { @@ -315,11 +313,20 @@ Zotero.Server.DataListener.prototype._bodyData = function() { */ Zotero.Server.DataListener.prototype._processEndpoint = function(method, postData) { try { + var headers = ""; + const originRe = /[\r\n]Origin: +([^ \r\n]+)/i; + var m = originRe.exec(this.header); + if(m && m[1] === "https://www.zotero.org" || m[1] === "http://www.zotero.org") { Zotero.debug(m[1]); + headers += "Access-Control-Allow-Origin: "+m[1]+"\r\n"; + headers += "Access-Control-Allow-Methods: POST, GET, OPTIONS\r\n"; + headers += "Access-Control-Allow-Headers: Content-Type,X-Zotero-Connector-API-Version,X-Zotero-Version\r\n"; + } + var endpoint = new this.endpoint; // check that endpoint supports method if(endpoint.supportedMethods && endpoint.supportedMethods.indexOf(method) === -1) { - this._requestFinished(Zotero.Server.generateResponse(400, "text/plain", "Endpoint does not support method\n")); + this._requestFinished(Zotero.Server.generateResponse(400, "text/plain", "Endpoint does not support method\n", headers)); return; } @@ -328,7 +335,7 @@ Zotero.Server.DataListener.prototype._processEndpoint = function(method, postDat // check that endpoint supports contentType var supportedDataTypes = endpoint.supportedDataTypes; if(supportedDataTypes && supportedDataTypes.indexOf(this.contentType) === -1) { - this._requestFinished(Zotero.Server.generateResponse(400, "text/plain", "Endpoint does not support content-type\n")); + this._requestFinished(Zotero.Server.generateResponse(400, "text/plain", "Endpoint does not support content-type\n", headers)); return; } @@ -337,7 +344,7 @@ Zotero.Server.DataListener.prototype._processEndpoint = function(method, postDat try { decodedData = JSON.parse(postData); } catch(e) { - this._requestFinished(Zotero.Server.generateResponse(400, "text/plain", "Invalid JSON provided\n")); + this._requestFinished(Zotero.Server.generateResponse(400, "text/plain", "Invalid JSON provided\n", headers)); return; } } else if(supportedDataTypes && this.contentType === "application/x-www-urlencoded") { @@ -350,7 +357,7 @@ Zotero.Server.DataListener.prototype._processEndpoint = function(method, postDat // set up response callback var me = this; var sendResponseCallback = function(code, contentType, arg) { - me._requestFinished(Zotero.Server.generateResponse(code, contentType, arg)); + me._requestFinished(Zotero.Server.generateResponse(code, contentType, arg, headers)); } // pass to endpoint @@ -366,7 +373,7 @@ Zotero.Server.DataListener.prototype._processEndpoint = function(method, postDat } } catch(e) { Zotero.debug(e); - this._requestFinished(Zotero.Server.generateResponse(500), "text/plain", "An error occurred\n"); + this._requestFinished(Zotero.Server.generateResponse(500), "text/plain", "An error occurred\n", headers); throw e; } } diff --git a/chrome/content/zotero/xpcom/server_connector.js b/chrome/content/zotero/xpcom/server_connector.js index 55772a64a5..5f19667124 100755 --- a/chrome/content/zotero/xpcom/server_connector.js +++ b/chrome/content/zotero/xpcom/server_connector.js @@ -489,7 +489,7 @@ Zotero.Server.Connector.Ping = function() {}; Zotero.Server.Endpoints["/connector/ping"] = Zotero.Server.Connector.Ping; Zotero.Server.Connector.Ping.prototype = { "supportedMethods":["POST"], - "supportedDataTypes":["application/json"], + "supportedDataTypes":["application/json", "text/plain"], /** * Finishes up translation when item selection is complete @@ -501,6 +501,33 @@ Zotero.Server.Connector.Ping.prototype = { } } +/** + * Test connection + * + * Accepts: + * Nothing + * Returns: + * Nothing (200 OK response) + */ +Zotero.Server.Connector.Ping = function() {}; +Zotero.Server.Endpoints["/connector/ieHack"] = Zotero.Server.Connector.Ping; +Zotero.Server.Connector.Ping.prototype = { + "supportedMethods":["POST"], + "supportedDataTypes":["application/json"], + + /** + * Finishes up translation when item selection is complete + * @param {String} data POST data or GET query string + * @param {Function} sendResponseCallback function to send HTTP response + */ + "init":function(postData, sendResponseCallback) { + sendResponseCallback(200, "text/html", + ''+ + ''+ + ''+ + ''); + } +} // XXX For compatibility with older connectors; to be removed Zotero.Server.Connector.IncompatibleVersion = function() {};