Fix Nextcloud WebDAV syncing
Add Zotero.HTTP.CookieBlocker mechanism to block storing and sending of all cookies for a given URL prefix, and use that for the configured WebDAV URL. https://forums.zotero.org/discussion/80429/sync-error-in-5-0-80
This commit is contained in:
parent
8c8140e74e
commit
6070743ff0
4 changed files with 102 additions and 7 deletions
|
@ -432,11 +432,15 @@ Zotero_Preferences.Sync = {
|
||||||
|
|
||||||
if (oldProtocol == 'webdav') {
|
if (oldProtocol == 'webdav') {
|
||||||
this.unverifyStorageServer();
|
this.unverifyStorageServer();
|
||||||
|
// The controller is getting replaced anyway, but this removes the WebDAV URL from
|
||||||
|
// Zotero.HTTP.CookieBlocker
|
||||||
|
Zotero.Sync.Runner.getStorageController('webdav').clearCachedCredentials();
|
||||||
Zotero.Sync.Runner.resetStorageController(oldProtocol);
|
Zotero.Sync.Runner.resetStorageController(oldProtocol);
|
||||||
|
|
||||||
var username = document.getElementById('storage-username').value;
|
var username = document.getElementById('storage-username').value;
|
||||||
var password = document.getElementById('storage-password').value;
|
var password = document.getElementById('storage-password').value;
|
||||||
if (username) {
|
if (username) {
|
||||||
|
// Get a new controller
|
||||||
Zotero.Sync.Runner.getStorageController('webdav').password = password;
|
Zotero.Sync.Runner.getStorageController('webdav').password = password;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -519,7 +519,13 @@ Zotero.HTTP = new function() {
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
|
|
||||||
if (options.cookieSandbox) {
|
if (options.cookieSandbox) {
|
||||||
options.cookieSandbox.attachToInterfaceRequestor(xmlhttp);
|
if (xmlhttp instanceof XMLHttpRequest) {
|
||||||
|
options.cookieSandbox.attachToInterfaceRequestor(xmlhttp);
|
||||||
|
}
|
||||||
|
// Mocked XHR in tests
|
||||||
|
else {
|
||||||
|
Zotero.debug("Not a real XMLHttpRequest -- not attaching cookie sandbox", 2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send binary data
|
// Send binary data
|
||||||
|
@ -807,6 +813,70 @@ Zotero.HTTP = new function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
this.CookieBlocker = {
|
||||||
|
registered: false,
|
||||||
|
observeredTopics: [
|
||||||
|
"http-on-examine-response",
|
||||||
|
"http-on-modify-request",
|
||||||
|
//"quit-application"
|
||||||
|
],
|
||||||
|
urls: [],
|
||||||
|
|
||||||
|
observe: function (channel, topic) {
|
||||||
|
channel.QueryInterface(Components.interfaces.nsIHttpChannel);
|
||||||
|
if (topic == "http-on-modify-request") {
|
||||||
|
for (let url of this.urls) {
|
||||||
|
if (channel.URI.spec.startsWith(url)) {
|
||||||
|
let dispURL = Zotero.HTTP.getDisplayURI(channel.URI).spec;
|
||||||
|
Zotero.debug("CookieBlocker: Ignoring cookies for " + dispURL);
|
||||||
|
channel.setRequestHeader("Cookie", "", false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (topic == "http-on-examine-response") {
|
||||||
|
for (let url of this.urls) {
|
||||||
|
if (channel.URI.spec.startsWith(url)) {
|
||||||
|
let dispURL = Zotero.HTTP.getDisplayURI(channel.URI).spec;
|
||||||
|
channel.setResponseHeader("Set-Cookie", "", false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
addURL: function (url) {
|
||||||
|
if (!this.registered) {
|
||||||
|
Zotero.debug("CookieBlocker: Registering observers");
|
||||||
|
for (let topic of this.observeredTopics) {
|
||||||
|
Services.obs.addObserver(this, topic, false);
|
||||||
|
}
|
||||||
|
this.registered = true;
|
||||||
|
}
|
||||||
|
if (!this.urls.includes(url)) {
|
||||||
|
let dispURL = Zotero.HTTP.getDisplayURI(NetUtil.newURI(url)).spec;
|
||||||
|
Zotero.debug("CookieBlocker: Adding " + dispURL + " to blocklist");
|
||||||
|
this.urls.push(url);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
removeURL: function (url) {
|
||||||
|
let pos = this.urls.indexOf(url);
|
||||||
|
if (pos != -1) {
|
||||||
|
let dispURL = Zotero.HTTP.getDisplayURI(NetUtil.newURI(url)).spec;
|
||||||
|
Zotero.debug("CookieBlocker: Removing " + dispURL + " from blocklist");
|
||||||
|
this.urls.splice(pos, 1);
|
||||||
|
}
|
||||||
|
if (!this.urls.length) {
|
||||||
|
Zotero.debug("CookieBlocker: Removing observers");
|
||||||
|
for (let topic of this.observeredTopics) {
|
||||||
|
Services.obs.removeObserver(this, topic, false);
|
||||||
|
}
|
||||||
|
this.registered = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make a foreground HTTP request in order to trigger a proxy authentication dialog
|
* Make a foreground HTTP request in order to trigger a proxy authentication dialog
|
||||||
*
|
*
|
||||||
|
@ -956,6 +1026,7 @@ Zotero.HTTP = new function() {
|
||||||
|
|
||||||
|
|
||||||
this.getDisplayURI = function (uri) {
|
this.getDisplayURI = function (uri) {
|
||||||
|
if (!uri.password) return uri;
|
||||||
return uri.mutate().setPassword('********').finalize();
|
return uri.mutate().setPassword('********').finalize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,6 @@ Zotero.Sync.Storage.Mode.WebDAV.prototype = {
|
||||||
Zotero.Prefs.set("sync.storage.verified", !!val)
|
Zotero.Prefs.set("sync.storage.verified", !!val)
|
||||||
},
|
},
|
||||||
|
|
||||||
_initialized: false,
|
|
||||||
_parentURI: null,
|
_parentURI: null,
|
||||||
_rootURI: null,
|
_rootURI: null,
|
||||||
_cachedCredentials: false,
|
_cachedCredentials: false,
|
||||||
|
@ -205,6 +204,7 @@ Zotero.Sync.Storage.Mode.WebDAV.prototype = {
|
||||||
var io = Services.io;
|
var io = Services.io;
|
||||||
this._parentURI = io.newURI(url, null, null);
|
this._parentURI = io.newURI(url, null, null);
|
||||||
this._rootURI = io.newURI(url + "zotero/", null, null);
|
this._rootURI = io.newURI(url + "zotero/", null, null);
|
||||||
|
Zotero.HTTP.CookieBlocker.addURL(this._rootURI.spec);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
@ -242,6 +242,10 @@ Zotero.Sync.Storage.Mode.WebDAV.prototype = {
|
||||||
|
|
||||||
|
|
||||||
clearCachedCredentials: function() {
|
clearCachedCredentials: function() {
|
||||||
|
Zotero.debug("WebDAV: Clearing cached credentials");
|
||||||
|
if (this._rootURI) {
|
||||||
|
Zotero.HTTP.CookieBlocker.removeURL(this._rootURI.spec);
|
||||||
|
}
|
||||||
this._rootURI = this._parentURI = undefined;
|
this._rootURI = this._parentURI = undefined;
|
||||||
this._cachedCredentials = false;
|
this._cachedCredentials = false;
|
||||||
},
|
},
|
||||||
|
@ -390,7 +394,7 @@ Zotero.Sync.Storage.Mode.WebDAV.prototype = {
|
||||||
.createInstance(nsIWBP);
|
.createInstance(nsIWBP);
|
||||||
wbp.persistFlags = nsIWBP.PERSIST_FLAGS_BYPASS_CACHE;
|
wbp.persistFlags = nsIWBP.PERSIST_FLAGS_BYPASS_CACHE;
|
||||||
wbp.progressListener = listener;
|
wbp.progressListener = listener;
|
||||||
Zotero.Utilities.Internal.saveURI(wbp, uri, destPath, null, true);
|
Zotero.Utilities.Internal.saveURI(wbp, uri, destPath);
|
||||||
|
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -548,7 +548,13 @@ describe("Zotero.Sync.Storage.Mode.WebDAV", function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
// For compatibility with NextCloud
|
// As a security measure, Nextcloud sets a regular cookie and two SameSite cookies and
|
||||||
|
// throws a 503 if the regular cookie gets returned without the SameSite cookies.
|
||||||
|
// As of Fx60 (Zotero 5.0.78), which added SameSite support, SameSite cookies don't get
|
||||||
|
// returned properly (because we don't have a load context?), triggering the 503. To avoid
|
||||||
|
// this, we just don't store or send any cookies for WebDAV requests.
|
||||||
|
//
|
||||||
|
// https://forums.zotero.org/discussion/80429/sync-error-in-5-0-80
|
||||||
it("shouldn't send cookies", function* () {
|
it("shouldn't send cookies", function* () {
|
||||||
// Make real requests so we can test the internal cookie-handling behavior
|
// Make real requests so we can test the internal cookie-handling behavior
|
||||||
Zotero.HTTP.mock = null;
|
Zotero.HTTP.mock = null;
|
||||||
|
@ -596,6 +602,12 @@ describe("Zotero.Sync.Storage.Mode.WebDAV", function () {
|
||||||
response.setHeader('WWW-Authenticate', 'Basic realm="WebDAV"', false);
|
response.setHeader('WWW-Authenticate', 'Basic realm="WebDAV"', false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Cookie shouldn't be passed
|
||||||
|
if (request.hasHeader('Cookie')) {
|
||||||
|
response.setStatusLine(null, 400, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
response.setHeader('Set-Cookie', 'foo=bar', false);
|
||||||
response.setHeader('DAV', '1', false);
|
response.setHeader('DAV', '1', false);
|
||||||
response.setStatusLine(null, 200, "OK");
|
response.setStatusLine(null, 200, "OK");
|
||||||
}
|
}
|
||||||
|
@ -608,7 +620,6 @@ describe("Zotero.Sync.Storage.Mode.WebDAV", function () {
|
||||||
handle: function (request, response) {
|
handle: function (request, response) {
|
||||||
if (request.method != 'GET') {
|
if (request.method != 'GET') {
|
||||||
response.setStatusLine(null, 400, "Bad Request");
|
response.setStatusLine(null, 400, "Bad Request");
|
||||||
response.write("");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// An XHR should already include Authorization
|
// An XHR should already include Authorization
|
||||||
|
@ -616,6 +627,11 @@ describe("Zotero.Sync.Storage.Mode.WebDAV", function () {
|
||||||
response.setStatusLine(null, 400, null);
|
response.setStatusLine(null, 400, null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Cookie shouldn't be passed
|
||||||
|
if (request.hasHeader('Cookie')) {
|
||||||
|
response.setStatusLine(null, 400, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Set a cookie
|
// Set a cookie
|
||||||
response.setHeader('Set-Cookie', 'foo=bar', false);
|
response.setHeader('Set-Cookie', 'foo=bar', false);
|
||||||
response.setStatusLine(null, 200, "OK");
|
response.setStatusLine(null, 200, "OK");
|
||||||
|
@ -633,12 +649,12 @@ describe("Zotero.Sync.Storage.Mode.WebDAV", function () {
|
||||||
// Make sure the cookie isn't returned
|
// Make sure the cookie isn't returned
|
||||||
if (request.hasHeader('Cookie')) {
|
if (request.hasHeader('Cookie')) {
|
||||||
response.setStatusLine(null, 503, "Service Unavailable");
|
response.setStatusLine(null, 503, "Service Unavailable");
|
||||||
response.write("");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Private context won't include Authorization automatically
|
// In case nsIWebBrowserPersist doesn't use the cached Authorization
|
||||||
if (!request.hasHeader('Authorization')) {
|
if (!request.hasHeader('Authorization')) {
|
||||||
response.setStatusLine(null, 401, null);
|
response.setStatusLine(null, 401, null);
|
||||||
|
response.setHeader('Set-Cookie', 'foo=bar', false);
|
||||||
response.setHeader('WWW-Authenticate', 'Basic realm="WebDAV"', false);
|
response.setHeader('WWW-Authenticate', 'Basic realm="WebDAV"', false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue