From bb0ddbd8723aca1ba1d25ae9e43906f71aacbb3e Mon Sep 17 00:00:00 2001 From: Fletcher Hazlehurst Date: Mon, 12 Oct 2020 09:56:43 -0600 Subject: [PATCH] Fix server not handling empty body in multi-part request An empty body is still valid. Was causing an issue for empty favicons. https://forums.zotero.org/discussion/85600/bug-report-no-snapshot-in-zotero-beta --- chrome/content/zotero/xpcom/server.js | 12 ++++--- test/tests/serverTest.js | 47 +++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 5 deletions(-) diff --git a/chrome/content/zotero/xpcom/server.js b/chrome/content/zotero/xpcom/server.js index 7cf9ddbf92..cf9c65f8c9 100755 --- a/chrome/content/zotero/xpcom/server.js +++ b/chrome/content/zotero/xpcom/server.js @@ -466,6 +466,7 @@ Zotero.Server.DataListener.prototype._processEndpoint = Zotero.Promise.coroutine } else if(this.contentType === "multipart/form-data") { let boundary = /boundary=([^\s]*)/i.exec(this.header); if (!boundary) { + Zotero.debug('Invalid boundary: ' + this.header, 1); return this._requestFinished(this._generateResponse(400, "text/plain", "Invalid multipart/form-data provided\n")); } boundary = '--' + boundary[1]; @@ -603,17 +604,18 @@ Zotero.Server.DataListener.prototype._decodeMultipartData = function(data, bound data = data.slice(1, data.length-1); for (let field of data) { let fieldData = {}; - field = field.trim(); // Split header and body let unixHeaderBoundary = field.indexOf("\n\n"); let windowsHeaderBoundary = field.indexOf("\r\n\r\n"); if (unixHeaderBoundary < windowsHeaderBoundary && unixHeaderBoundary != -1) { - fieldData.header = field.slice(0, unixHeaderBoundary); - fieldData.body = field.slice(unixHeaderBoundary+2); + fieldData.header = field.slice(0, unixHeaderBoundary).trim(); + fieldData.body = field.slice(unixHeaderBoundary+2).trim(); } else if (windowsHeaderBoundary != -1) { - fieldData.header = field.slice(0, windowsHeaderBoundary); - fieldData.body = field.slice(windowsHeaderBoundary+4); + fieldData.header = field.slice(0, windowsHeaderBoundary).trim(); + fieldData.body = field.slice(windowsHeaderBoundary+4).trim(); } else { + // Only log first 200 characters in case the part is large + Zotero.debug('Malformed multipart/form-data body: ' + field.substr(0, 200), 1); throw new Error('Malformed multipart/form-data body'); } diff --git a/test/tests/serverTest.js b/test/tests/serverTest.js index c9bac2644e..f73ed5b3b7 100644 --- a/test/tests/serverTest.js +++ b/test/tests/serverTest.js @@ -240,6 +240,53 @@ describe("Zotero.Server", function () { assert.ok(called); assert.equal(req.status, 204); }); + + it("should support an empty body", async function () { + var called = false; + var endpoint = "/test/" + Zotero.Utilities.randomString(); + + Zotero.Server.Endpoints[endpoint] = function () {}; + Zotero.Server.Endpoints[endpoint].prototype = { + supportedMethods: ["POST"], + supportedDataTypes: ["multipart/form-data"], + + init: function (options) { + called = true; + assert.isObject(options); + assert.property(options.headers, "Content-Type"); + assert(options.headers["Content-Type"].startsWith("multipart/form-data; boundary=")); + assert.isArray(options.data); + assert.equal(options.data.length, 1); + + let expected = { + header: "Content-Disposition: form-data; name=\"foo\"", + body: "", + params: { + name: "foo" + } + }; + assert.deepEqual(options.data[0], expected); + return 204; + } + }; + + let formData = new FormData(); + formData.append("foo", ""); + + let req = await Zotero.HTTP.request( + "POST", + serverPath + endpoint, + { + headers: { + "Content-Type": "multipart/form-data" + }, + body: formData + } + ); + + assert.ok(called); + assert.equal(req.status, 204); + }); }); }); })