diff --git a/chrome/content/zotero/xpcom/http.js b/chrome/content/zotero/xpcom/http.js
index 9333c9a73..7ee49f543 100644
--- a/chrome/content/zotero/xpcom/http.js
+++ b/chrome/content/zotero/xpcom/http.js
@@ -181,9 +181,25 @@ Zotero.HTTP = new function() {
}
// Send headers
- var headers = (options && options.headers) || {};
- if (options.body && !headers["Content-Type"]) {
- headers["Content-Type"] = "application/x-www-form-urlencoded";
+ var headers = {};
+ if (options && options.headers) {
+ Object.assign(headers, options.headers);
+ }
+ var compressedBody = false;
+ if (options.body) {
+ if (!headers["Content-Type"]) {
+ headers["Content-Type"] = "application/x-www-form-urlencoded";
+ }
+
+ if (options.compressBody && this.isWriteMethod(method)) {
+ headers['Content-Encoding'] = 'gzip';
+ compressedBody = yield Zotero.Utilities.Internal.gzip(options.body);
+
+ let oldLen = options.body.length;
+ let newLen = compressedBody.length;
+ Zotero.debug(`${method} body gzipped from ${oldLen} to ${newLen}; `
+ + Math.round(((oldLen - newLen) / oldLen) * 100) + "% savings");
+ }
}
if (options.debug) {
if (headers["Zotero-API-Key"]) {
@@ -248,7 +264,19 @@ Zotero.HTTP = new function() {
options.cookieSandbox.attachToInterfaceRequestor(xmlhttp);
}
- xmlhttp.send(options.body || null);
+ // Send binary data
+ if (compressedBody) {
+ let numBytes = compressedBody.length;
+ let ui8Data = new Uint8Array(numBytes);
+ for (let i = 0; i < numBytes; i++) {
+ ui8Data[i] = compressedBody.charCodeAt(i) & 0xff;
+ }
+ xmlhttp.send(ui8Data);
+ }
+ // Send regular request
+ else {
+ xmlhttp.send(options.body || null);
+ }
return deferred.promise;
});
@@ -705,6 +733,11 @@ Zotero.HTTP = new function() {
}
+ this.isWriteMethod = function (method) {
+ return method == 'POST' || method == 'PUT' || method == 'PATCH';
+ };
+
+
this.getDisplayURI = function (uri) {
var disp = uri.clone();
if (disp.password) {
diff --git a/chrome/content/zotero/xpcom/sync/syncAPIClient.js b/chrome/content/zotero/xpcom/sync/syncAPIClient.js
index ca93fe33d..8d9662f44 100644
--- a/chrome/content/zotero/xpcom/sync/syncAPIClient.js
+++ b/chrome/content/zotero/xpcom/sync/syncAPIClient.js
@@ -43,6 +43,7 @@ Zotero.Sync.APIClient = function (options) {
Zotero.Sync.APIClient.prototype = {
MAX_OBJECTS_PER_REQUEST: 100,
+ MIN_GZIP_SIZE: 1000,
getKeyInfo: Zotero.Promise.coroutine(function* (options={}) {
@@ -571,6 +572,10 @@ Zotero.Sync.APIClient.prototype = {
opts.dontCache = true;
opts.foreground = !options.background;
opts.responseType = options.responseType || 'text';
+ if (options.body && options.body.length >= this.MIN_GZIP_SIZE) {
+ opts.compressBody = true;
+ }
+
var tries = 0;
var failureDelayGenerator = null;
while (true) {
diff --git a/chrome/content/zotero/xpcom/utilities_internal.js b/chrome/content/zotero/xpcom/utilities_internal.js
index b726d3b8f..e4cec993d 100644
--- a/chrome/content/zotero/xpcom/utilities_internal.js
+++ b/chrome/content/zotero/xpcom/utilities_internal.js
@@ -225,6 +225,125 @@ Zotero.Utilities.Internal = {
},
+ gzip: Zotero.Promise.coroutine(function* (data) {
+ var deferred = Zotero.Promise.defer();
+
+ // Get input stream from POST data
+ var unicodeConverter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
+ .createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
+ unicodeConverter.charset = "UTF-8";
+ var is = unicodeConverter.convertToInputStream(data);
+
+ // Initialize stream converter
+ var converter = Components.classes["@mozilla.org/streamconv;1?from=uncompressed&to=gzip"]
+ .createInstance(Components.interfaces.nsIStreamConverter);
+ converter.asyncConvertData(
+ "uncompressed",
+ "gzip",
+ {
+ binaryInputStream: null,
+ size: 0,
+ data: '',
+
+ onStartRequest: function (request, context) {},
+
+ onStopRequest: function (request, context, status) {
+ this.binaryInputStream.close();
+ delete this.binaryInputStream;
+
+ deferred.resolve(this.data);
+ },
+
+ onDataAvailable: function (request, context, inputStream, offset, count) {
+ this.size += count;
+
+ this.binaryInputStream = Components.classes["@mozilla.org/binaryinputstream;1"]
+ .createInstance(Components.interfaces.nsIBinaryInputStream)
+ this.binaryInputStream.setInputStream(inputStream);
+ this.data += this.binaryInputStream.readBytes(this.binaryInputStream.available());
+ },
+
+ QueryInterface: function (iid) {
+ if (iid.equals(Components.interfaces.nsISupports)
+ || iid.equals(Components.interfaces.nsIStreamListener)) {
+ return this;
+ }
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ }
+ },
+ null
+ );
+
+ // Send input stream to stream converter
+ var pump = Components.classes["@mozilla.org/network/input-stream-pump;1"]
+ .createInstance(Components.interfaces.nsIInputStreamPump);
+ pump.init(is, -1, -1, 0, 0, true);
+ pump.asyncRead(converter, null);
+
+ return deferred.promise;
+ }),
+
+
+ gunzip: Zotero.Promise.coroutine(function* (data) {
+ var deferred = Zotero.Promise.defer();
+
+ Components.utils.import("resource://gre/modules/NetUtil.jsm");
+
+ var is = Components.classes["@mozilla.org/io/string-input-stream;1"]
+ .createInstance(Ci.nsIStringInputStream);
+ is.setData(data, data.length);
+
+ var bis = Components.classes["@mozilla.org/binaryinputstream;1"]
+ .createInstance(Components.interfaces.nsIBinaryInputStream);
+ bis.setInputStream(is);
+
+ // Initialize stream converter
+ var converter = Components.classes["@mozilla.org/streamconv;1?from=gzip&to=uncompressed"]
+ .createInstance(Components.interfaces.nsIStreamConverter);
+ converter.asyncConvertData(
+ "gzip",
+ "uncompressed",
+ {
+ data: '',
+
+ onStartRequest: function (request, context) {},
+
+ onStopRequest: function (request, context, status) {
+ deferred.resolve(this.data);
+ },
+
+ onDataAvailable: function (request, context, inputStream, offset, count) {
+ this.data += NetUtil.readInputStreamToString(
+ inputStream,
+ inputStream.available(),
+ {
+ charset: 'UTF-8',
+ replacement: 65533
+ }
+ )
+ },
+
+ QueryInterface: function (iid) {
+ if (iid.equals(Components.interfaces.nsISupports)
+ || iid.equals(Components.interfaces.nsIStreamListener)) {
+ return this;
+ }
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ }
+ },
+ null
+ );
+
+ // Send input stream to stream converter
+ var pump = Components.classes["@mozilla.org/network/input-stream-pump;1"]
+ .createInstance(Components.interfaces.nsIInputStreamPump);
+ pump.init(bis, -1, -1, 0, 0, true);
+ pump.asyncRead(converter, null);
+
+ return deferred.promise;
+ }),
+
+
/**
* Unicode normalization
*/
diff --git a/test/content/runtests.html b/test/content/runtests.html
index abb0e7944..955773a0e 100644
--- a/test/content/runtests.html
+++ b/test/content/runtests.html
@@ -11,6 +11,7 @@
+