diff --git a/chrome/content/zotero/xpcom/server_connector.js b/chrome/content/zotero/xpcom/server_connector.js index f54030622..adbbe3b43 100644 --- a/chrome/content/zotero/xpcom/server_connector.js +++ b/chrome/content/zotero/xpcom/server_connector.js @@ -605,7 +605,7 @@ Zotero.Server.Connector.Import.prototype = { translate.setString(data); let translators = yield translate.getTranslators(); if (!translators || !translators.length) { - return sendResponseCallback(404); + return sendResponseCallback(400); } translate.setTranslator(translators[0]); let items = yield translate.translate(); @@ -628,7 +628,11 @@ Zotero.Server.Connector.InstallStyle.prototype = { permitBookmarklet: false, init: Zotero.Promise.coroutine(function* (url, data, sendResponseCallback){ - let styleName = yield Zotero.Styles.install(data, url.query.origin || null, true); + try { + var styleName = yield Zotero.Styles.install(data, url.query.origin || null, true); + } catch (e) { + sendResponseCallback(400, "text/plain", e.message) + } sendResponseCallback(201, "application/json", JSON.stringify({name: styleName})); }) }; diff --git a/chrome/content/zotero/xpcom/style.js b/chrome/content/zotero/xpcom/style.js index 5bc409863..79192046c 100644 --- a/chrome/content/zotero/xpcom/style.js +++ b/chrome/content/zotero/xpcom/style.js @@ -242,31 +242,40 @@ Zotero.Styles = new function() { * containing the style data * @param {String} origin The origin of the style, either a filename or URL, to be * displayed in dialogs referencing the style - * @param {Boolean} [noPrompt=false] Skip the confirmation prompt + * @param {Boolean} [silent=false] Skip prompts */ - this.install = Zotero.Promise.coroutine(function* (style, origin, noPrompt=false) { + this.install = Zotero.Promise.coroutine(function* (style, origin, silent=false) { var styleTitle; + origin = origin || Zotero.getString('styles.unknownOrigin'); try { if (style instanceof Components.interfaces.nsIFile) { // handle nsIFiles var url = Services.io.newFileURI(style); var xmlhttp = yield Zotero.HTTP.request("GET", url.spec); - styleTitle = yield _install(xmlhttp.responseText, style.leafName, false, noPrompt); + styleTitle = yield _install(xmlhttp.responseText, style.leafName, false, silent); } else { - styleTitle = yield _install(style, origin, false, noPrompt); + styleTitle = yield _install(style, origin, false, silent); } } catch (error) { // Unless user cancelled, show an alert with the error if(typeof error === "object" && error instanceof Zotero.Exception.UserCancelled) return; if(typeof error === "object" && error instanceof Zotero.Exception.Alert) { - error.present(); error.log(); + if (silent) { + throw (error) + } else { + error.present(); + } } else { Zotero.logError(error); - (new Zotero.Exception.Alert("styles.install.unexpectedError", - origin, "styles.install.title", error)).present(); + if (silent) { + throw error + } else { + (new Zotero.Exception.Alert("styles.install.unexpectedError", + origin, "styles.install.title", error)).present(); + } } } return styleTitle; @@ -278,10 +287,10 @@ Zotero.Styles = new function() { * @param {String} origin The origin of the style, either a filename or URL, to be * displayed in dialogs referencing the style * @param {Boolean} [hidden] Whether style is to be hidden. - * @param {Boolean} [noPrompt=false] Skip the confirmation prompt + * @param {Boolean} [silent=false] Skip prompts * @return {Promise} */ - var _install = Zotero.Promise.coroutine(function* (style, origin, hidden, noPrompt=false) { + var _install = Zotero.Promise.coroutine(function* (style, origin, hidden, silent=false) { if (!_initialized) yield Zotero.Styles.init(); var existingFile, destFile, source; @@ -364,7 +373,7 @@ Zotero.Styles = new function() { // display a dialog to tell the user we're about to install the style if(hidden) { destFile = destFileHidden; - } else if (!noPrompt) { + } else if (!silent) { if(existingTitle) { var text = Zotero.getString('styles.updateStyle', [existingTitle, title, origin]); } else { diff --git a/chrome/content/zotero/xpcom/translation/translate.js b/chrome/content/zotero/xpcom/translation/translate.js index d3029a5a5..c5f58f5fc 100644 --- a/chrome/content/zotero/xpcom/translation/translate.js +++ b/chrome/content/zotero/xpcom/translation/translate.js @@ -1094,6 +1094,8 @@ Zotero.Translate.Base.prototype = { if(this._currentState === "detect") throw new Error("getTranslators: detection is already running"); this._currentState = "detect"; this._getAllTranslators = getAllTranslators; + this._potentialTranslators = []; + this._foundTranslators = []; if(checkSetTranslator) { // setTranslator must be called beforehand if checkSetTranslator is set @@ -1123,8 +1125,6 @@ Zotero.Translate.Base.prototype = { return potentialTranslators.then(function(result) { var allPotentialTranslators = result[0]; var properToProxyFunctions = result[1]; - this._potentialTranslators = []; - this._foundTranslators = []; // this gets passed out by Zotero.Translators.getWebTranslatorsForLocation() because it is // specific for each translator, but we want to avoid making a copy of a translator whenever @@ -1441,7 +1441,6 @@ Zotero.Translate.Base.prototype = { var errorString = null; if(!returnValue && error) errorString = this._generateErrorString(error); - if(this._currentState === "detect") { if(this._potentialTranslators.length) { var lastTranslator = this._potentialTranslators.shift(); diff --git a/chrome/locale/en-US/zotero/zotero.properties b/chrome/locale/en-US/zotero/zotero.properties index e242cbbf0..2941eac88 100644 --- a/chrome/locale/en-US/zotero/zotero.properties +++ b/chrome/locale/en-US/zotero/zotero.properties @@ -832,6 +832,7 @@ styles.validationWarning = "%S" is not a valid CSL 1.0.1 style file, and may n styles.installSourceError = %1$S references an invalid or non-existent CSL file at %2$S as its source. styles.deleteStyle = Are you sure you want to delete the style "%1$S"? styles.deleteStyles = Are you sure you want to delete the selected styles? +styles.unknownOrigin = External Style styles.abbreviations.title = Load Abbreviations styles.abbreviations.parseError = The abbreviations file "%1$S" is not valid JSON. diff --git a/test/tests/server_connectorTest.js b/test/tests/server_connectorTest.js index c166e6dfd..44897a59f 100644 --- a/test/tests/server_connectorTest.js +++ b/test/tests/server_connectorTest.js @@ -6,11 +6,13 @@ describe("Connector Server", function () { var testServerPort = 16213; before(function* () { + this.timeout(20000); Zotero.Prefs.set("httpServer.enabled", true); yield resetDB({ thisArg: this, skipBundledFiles: true }); + yield Zotero.Translators.init(); win = yield loadZoteroPane(); connectorServerPath = 'http://127.0.0.1:' + Zotero.Prefs.get('httpServer.port'); @@ -314,30 +316,30 @@ describe("Connector Server", function () { }); }); - describe('/connector/importStyle', function() { + describe('/connector/installStyle', function() { var endpoint; before(function() { - endpoint = connectorServerPath + "/connector/importStyle"; + endpoint = connectorServerPath + "/connector/installStyle"; }); - it('should reject application/json requests', function* () { - try { - var response = yield Zotero.HTTP.request( - 'POST', - endpoint, - { - headers: { "Content-Type": "application/json" }, - body: '{}' - } - ); - } catch(e) { - assert.instanceOf(e, Zotero.HTTP.UnexpectedStatusException); - assert.equal(e.xmlhttp.status, 400); - } + it('should reject styles with invalid text', function* () { + var error = yield getPromiseError(Zotero.HTTP.request( + 'POST', + endpoint, + { + headers: { "Content-Type": "application/json" }, + body: '{}' + } + )); + assert.instanceOf(error, Zotero.HTTP.UnexpectedStatusException); + assert.equal(error.xmlhttp.status, 400); + assert.equal(error.xmlhttp.responseText, + Zotero.getString("styles.installError", + Zotero.getString('styles.unknownOrigin'))); }); - it('should import a style with text/x-csl content-type', function* () { + it('should import a style with application/vnd.citationstyles.style+xml content-type', function* () { sinon.stub(Zotero.Styles, 'install', function(style) { var parser = Components.classes["@mozilla.org/xmlextras/domparser;1"] .createInstance(Components.interfaces.nsIDOMParser), @@ -362,7 +364,7 @@ describe("Connector Server", function () { 'POST', endpoint, { - headers: { "Content-Type": "text/x-csl" }, + headers: { "Content-Type": "application/vnd.citationstyles.style+xml" }, body: style } ); @@ -371,4 +373,44 @@ describe("Connector Server", function () { Zotero.Styles.install.restore(); }); }); + + describe('/connector/import', function() { + var endpoint; + + before(function() { + endpoint = connectorServerPath + "/connector/import"; + }); + + it('should reject resources that do not contain import data', function* () { + var error = yield getPromiseError(Zotero.HTTP.request( + 'POST', + endpoint, + { + headers: { "Content-Type": "text/plain" }, + body: 'Owl' + } + )); + assert.instanceOf(error, Zotero.HTTP.UnexpectedStatusException); + assert.equal(error.xmlhttp.status, 400); + }); + + it('should import resources (BibTeX)', function* () { + var resource = `@book{test1, + title={Test1}, + author={Owl}, + year={1000}, + publisher={Curly Braces Publishing} +}`; + var response = yield Zotero.HTTP.request( + 'POST', + endpoint, + { + headers: { "Content-Type": "application/x-bibtex" }, + body: resource + } + ); + assert.equal(response.status, 201); + assert.equal(JSON.parse(response.responseText)[0].title, 'Test1'); + }); + }); });