diff --git a/chrome/content/zotero/xpcom/server.js b/chrome/content/zotero/xpcom/server.js index 98060f6f55..e51c246024 100755 --- a/chrome/content/zotero/xpcom/server.js +++ b/chrome/content/zotero/xpcom/server.js @@ -39,7 +39,7 @@ Zotero.Server = new function() { /** * initializes a very rudimentary web server */ - this.init = function() { + this.init = function(port, bindAllAddr, maxConcurrentConnections) { if (Zotero.HTTP.browserIsOffline()) { Zotero.debug('Browser is offline -- not initializing HTTP server'); _registerOnlineObserver(); @@ -51,10 +51,10 @@ Zotero.Server = new function() { .createInstance(Components.interfaces.nsIServerSocket); try { // bind to a random port on loopback only - serv.init(Zotero.Prefs.get('httpServer.port'), true, -1); + serv.init(port ? port : Zotero.Prefs.get('httpServer.port'), !bindAllAddr, -1); serv.asyncListen(Zotero.Server.SocketListener); - Zotero.debug("HTTP server listening on 127.0.0.1:"+serv.port); + Zotero.debug("HTTP server listening on "+(bindAllAddr ? "*": " 127.0.0.1")+":"+serv.port); } catch(e) { Zotero.debug("Not initializing HTTP server"); } @@ -82,6 +82,20 @@ Zotero.Server = new function() { return response; } + /** + * Parses a query string into a key => value object + * @param {String} queryString Query string + */ + this.decodeQueryString = function(queryString) { + var splitData = queryString.split("&"); + var decodedData = {}; + for each(var variable in splitData) { + var splitIndex = variable.indexOf("="); + decodedData[decodeURIComponent(variable.substr(0, splitIndex))] = decodeURIComponent(variable.substr(splitIndex+1)); + } + return decodedData; + } + function _registerOnlineObserver() { if (_onlineObserverRegistered) { return; @@ -218,7 +232,7 @@ Zotero.Server.DataListener.prototype.onDataAvailable = function(request, context Zotero.Server.DataListener.prototype._headerFinished = function() { this.headerFinished = true; - Zotero.debug(this.header); + Zotero.debug(this.header, 5); const methodRe = /^([A-Z]+) ([^ \r\n?]+)(\?[^ \r\n]+)?/; const contentTypeRe = /[\r\n]Content-Type: +([^ \r\n]+)/i; @@ -233,33 +247,35 @@ Zotero.Server.DataListener.prototype._headerFinished = function() { } if(!method) { - this._requestFinished(Zotero.Server.generateResponse(400)); + this._requestFinished(Zotero.Server.generateResponse(400, "text/plain", "Invalid method specified\n")); return; } if(!Zotero.Server.Endpoints[method[2]]) { - this._requestFinished(Zotero.Server.generateResponse(404)); + this._requestFinished(Zotero.Server.generateResponse(404, "text/plain", "No endpoint found\n")); return; } + this.pathname = method[2]; this.endpoint = Zotero.Server.Endpoints[method[2]]; + this.query = method[3]; if(method[1] == "HEAD" || method[1] == "OPTIONS") { this._requestFinished(Zotero.Server.generateResponse(200)); } else if(method[1] == "GET") { - this._requestFinished(this._processEndpoint("GET", method[3])); + this._processEndpoint("GET", null); } else if(method[1] == "POST") { const contentLengthRe = /[\r\n]Content-Length: +([0-9]+)/i; // parse content length var m = contentLengthRe.exec(this.header); if(!m) { - this._requestFinished(Zotero.Server.generateResponse(400)); + this._requestFinished(Zotero.Server.generateResponse(400, "text/plain", "Content-length not provided\n")); return; } this.bodyLength = parseInt(m[1]); this._bodyData(); } else { - this._requestFinished(Zotero.Server.generateResponse(501)); + this._requestFinished(Zotero.Server.generateResponse(501, "text/plain", "Method not implemented\n")); return; } } @@ -297,29 +313,30 @@ Zotero.Server.DataListener.prototype._processEndpoint = function(method, postDat var endpoint = new this.endpoint; // check that endpoint supports method - if(endpoint.supportedMethods.indexOf(method) === -1) { - this._requestFinished(Zotero.Server.generateResponse(400)); + if(endpoint.supportedMethods && endpoint.supportedMethods.indexOf(method) === -1) { + this._requestFinished(Zotero.Server.generateResponse(400, "text/plain", "Endpoint does not support method\n")); return; } var decodedData = null; if(postData && this.contentType) { // check that endpoint supports contentType - if(endpoint.supportedDataTypes.indexOf(this.contentType) === -1) { - this._requestFinished(Zotero.Server.generateResponse(400)); + 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")); return; } // decode JSON or urlencoded post data, and pass through anything else - if(this.contentType === "application/json") { - decodedData = JSON.parse(postData); - } else if(this.contentType === "application/x-www-urlencoded") { - var splitData = postData.split("&"); - decodedData = {}; - for each(var variable in splitData) { - var splitIndex = variable.indexOf("="); - data[decodeURIComponent(variable.substr(0, splitIndex))] = decodeURIComponent(variable.substr(splitIndex+1)); + if(supportedDataTypes && this.contentType === "application/json") { + try { + decodedData = JSON.parse(postData); + } catch(e) { + this._requestFinished(Zotero.Server.generateResponse(400, "text/plain", "Invalid JSON provided\n")); + return; } + } else if(supportedDataTypes && this.contentType === "application/x-www-urlencoded") { + decodedData = Zotero.Server.decodeQueryString(postData); } else { decodedData = postData; } @@ -332,10 +349,19 @@ Zotero.Server.DataListener.prototype._processEndpoint = function(method, postDat } // pass to endpoint - endpoint.init(decodedData, sendResponseCallback); + if((endpoint.init.length ? endpoint.init.length : endpoint.init.arity) === 3) { + var url = { + "pathname":this.pathname, + "query":this.query ? Zotero.Server.decodeQueryString(this.query.substr(1)) : {} + }; + + endpoint.init(url, decodedData, sendResponseCallback); + } else { + endpoint.init(decodedData, sendResponseCallback); + } } catch(e) { Zotero.debug(e); - this._requestFinished(Zotero.Server.generateResponse(500)); + this._requestFinished(Zotero.Server.generateResponse(500), "text/plain", "An error occurred\n"); throw e; } } @@ -356,7 +382,7 @@ Zotero.Server.DataListener.prototype._requestFinished = function(response) { intlStream.init(this.oStream, "UTF-8", 1024, "?".charCodeAt(0)); // write response - Zotero.debug(response); + Zotero.debug(response, 5); intlStream.writeString(response); } finally { intlStream.close(); diff --git a/chrome/content/zotero/xpcom/translation/translate.js b/chrome/content/zotero/xpcom/translation/translate.js index e89734b541..32e1c11608 100644 --- a/chrome/content/zotero/xpcom/translation/translate.js +++ b/chrome/content/zotero/xpcom/translation/translate.js @@ -81,7 +81,7 @@ Zotero.Translate.Sandbox = { * @param {SandboxItem} An item created using the Zotero.Item class from the sandbox */ "_itemDone":function(translate, item) { - Zotero.debug("Translate: Saving item"); + //Zotero.debug("Translate: Saving item"); // warn if itemDone called after translation completed if(translate._complete) { @@ -1859,7 +1859,7 @@ Zotero.Translate.IO = { } if(nodes.getElementsByTagName("parsererror").length) { - throw new Error("DOMParser error: loading data into data store failed"); + throw "DOMParser error: loading data into data store failed"; } return nodes; @@ -1901,8 +1901,14 @@ Zotero.Translate.IO.String.prototype = { this.RDF = new Zotero.Translate.IO._RDFSandbox(this._dataStore); if(this._string.length) { + try { + var xml = Zotero.Translate.IO.parseDOMXML(this._string); + } catch(e) { + this._xmlInvalid = true; + throw e; + } var parser = new Zotero.RDF.AJAW.RDFParser(this._dataStore); - parser.parse(Zotero.Translate.IO.parseDOMXML(this._string), this._uri); + parser.parse(xml, this._uri); callback(true); } }, @@ -1931,19 +1937,23 @@ Zotero.Translate.IO.String.prototype = { var oldPointer = this._stringPointer; var lfIndex = this._string.indexOf("\n", this._stringPointer); - if(lfIndex != -1) { + if(lfIndex !== -1) { // in case we have a CRLF this._stringPointer = lfIndex+1; - if(this._string.length > lfIndex && this._string[lfIndex-1] == "\r") { + if(this._string.length > lfIndex && this._string[lfIndex-1] === "\r") { lfIndex--; } return this._string.substr(oldPointer, lfIndex-oldPointer); } - var crIndex = this._string.indexOf("\r", this._stringPointer); - if(crIndex != -1) { - this._stringPointer = crIndex+1; - return this._string.substr(oldPointer, crIndex-oldPointer-1); + if(!this._noCR) { + var crIndex = this._string.indexOf("\r", this._stringPointer); + if(crIndex === -1) { + this._noCR = true; + } else { + this._stringPointer = crIndex+1; + return this._string.substr(oldPointer, crIndex-oldPointer-1); + } } this._stringPointer = this._string.length; @@ -1957,7 +1967,12 @@ Zotero.Translate.IO.String.prototype = { "_getXML":function() { if(this._mode == "xml/dom") { - return Zotero.Translate.IO.parseDOMXML(this._string); + try { + return Zotero.Translate.IO.parseDOMXML(this._string); + } catch(e) { + this._xmlInvalid = true; + throw e; + } } else { return this._string.replace(/<\?xml[^>]+\?>/, ""); } @@ -1965,9 +1980,13 @@ Zotero.Translate.IO.String.prototype = { "init":function(newMode, callback) { this._stringPointer = 0; + this._noCR = undefined; this._mode = newMode; - if(Zotero.Translate.IO.rdfDataModes.indexOf(this._mode) !== -1) { + if(newMode && (Zotero.Translate.IO.rdfDataModes.indexOf(newMode) !== -1 + || newMode.substr(0, 3) === "xml") && this._xmlInvalid) { + throw "XML known invalid"; + } else if(Zotero.Translate.IO.rdfDataModes.indexOf(this._mode) !== -1) { this._initRDF(callback); } else { callback(true); diff --git a/chrome/content/zotero/xpcom/translation/translate_firefox.js b/chrome/content/zotero/xpcom/translation/translate_firefox.js index 70dc4b2f49..e4cebeb3e0 100644 --- a/chrome/content/zotero/xpcom/translation/translate_firefox.js +++ b/chrome/content/zotero/xpcom/translation/translate_firefox.js @@ -148,22 +148,44 @@ Zotero.Translate.SandboxManager.prototype = { if(newExposedProps) newExposedProps[localKey] = "r"; // magical XPCSafeJSObjectWrappers for sandbox - if(typeof object[localKey] === "function" || typeof object[localKey] === "object") { - if(attachTo == this.sandbox) Zotero.debug(localKey); - attachTo[localKey] = function() { - var args = (passAsFirstArgument ? [passAsFirstArgument] : []); - for(var i=0; i