commit 678a6e15cc5c6c5a1c7e040667ff88ee87c4f1d6
parent 83fe445f8f5a66c70a46617384f2e28cfd7121a0
Author: Dan Stillman <dstillman@zotero.org>
Date: Thu, 10 Aug 2017 04:41:16 +0200
Better Unicode path comparison in WebDAV.purgeOrphanedStorageFiles()
Diffstat:
2 files changed, 81 insertions(+), 18 deletions(-)
diff --git a/chrome/content/zotero/xpcom/storage/webdav.js b/chrome/content/zotero/xpcom/storage/webdav.js
@@ -1013,46 +1013,40 @@ Zotero.Sync.Storage.Mode.WebDAV.prototype = {
// Absolute
if (href.match(/^https?:\/\//)) {
- var ios = Components.classes["@mozilla.org/network/io-service;1"].
- getService(Components.interfaces.nsIIOService);
- var href = ios.newURI(href, null, null);
- href = href.path;
+ let ios = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+ href = ios.newURI(href, null, null).path;
}
+ let decodedHref = decodeURIComponent(href).normalize();
+ let decodedPath = decodeURIComponent(path).normalize();
+
// Skip root URI
- if (href == path
+ if (decodedHref == decodedPath
// Some Apache servers respond with a "/zotero" href
// even for a "/zotero/" request
- || (trailingSlash && href + '/' == path)
- // Try URL-encoded as well, as above
- || decodeURIComponent(href) == path) {
+ || (trailingSlash && decodedHref + '/' == decodedPath)) {
continue;
}
- if (href.indexOf(path) == -1
- // Try URL-encoded as well, in case there's a '~' or similar
- // character in the URL and the server (e.g., Sakai) is
- // encoding the value
- && decodeURIComponent(href).indexOf(path) == -1) {
+ if (!decodedHref.startsWith(decodedPath)) {
throw new Error(`DAV:href '${href}' does not begin with path '${path}'`);
}
var matches = href.match(/[^\/]+$/);
if (!matches) {
- throw new Error(
- "Unexpected href '" + href + "' in " + funcName
- );
+ throw new Error(`Unexpected href '${href}'`);
}
var file = matches[0];
- if (file.indexOf('.') == 0) {
+ if (file.startsWith('.')) {
Zotero.debug("Skipping hidden file " + file);
continue;
}
var isLastSyncFile = file == 'lastsync.txt' || file == 'lastsync';
if (!isLastSyncFile) {
- if (!file.match(/\.zip$/) && !file.match(/\.prop$/)) {
+ if (!file.endsWith('.zip') && !file.endsWith('.prop')) {
Zotero.debug("Skipping file " + file);
continue;
}
diff --git a/test/tests/webdavTest.js b/test/tests/webdavTest.js
@@ -862,6 +862,75 @@ describe("Zotero.Sync.Storage.Mode.WebDAV", function () {
Zotero.Prefs.set("lastWebDAVOrphanPurge", Math.round(new Date().getTime() / 1000) - 3600);
yield assert.eventually.equal(controller.purgeOrphanedStorageFiles(), false);
assertRequestCount(0);
+ });
+
+
+ it("should handle unnormalized Unicode characters", function* () {
+ var library = Zotero.Libraries.userLibrary;
+ library.updateLastSyncTime();
+ yield library.saveTx();
+
+ const daysBeforeSyncTime = 7;
+
+ var beforeTime = new Date(Date.now() - (daysBeforeSyncTime * 86400 * 1000 + 1)).toUTCString();
+ var currentTime = new Date(Date.now() - 3600000).toUTCString();
+
+ var strC = '\u1E9B\u0323';
+ var encodedStrC = encodeURIComponent(strC);
+ var strD = '\u1E9B\u0323'.normalize('NFD');
+ var encodedStrD = encodeURIComponent(strD);
+
+ setResponse({
+ method: "PROPFIND",
+ url: `${encodedStrC}/zotero/`,
+ status: 207,
+ headers: {
+ "Content-Type": 'text/xml; charset="utf-8"'
+ },
+ text: '<?xml version="1.0" encoding="utf-8"?>'
+ + '<D:multistatus xmlns:D="DAV:" xmlns:ns0="DAV:">'
+ + '<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">'
+ + `<D:href>${davBasePath}${encodedStrD}/zotero/</D:href>`
+ + '<D:propstat>'
+ + '<D:prop>'
+ + `<lp1:getlastmodified>${beforeTime}</lp1:getlastmodified>`
+ + '</D:prop>'
+ + '<D:status>HTTP/1.1 200 OK</D:status>'
+ + '</D:propstat>'
+ + '</D:response>'
+ + '<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">'
+ + `<D:href>${davBasePath}${encodedStrD}/zotero/lastsync</D:href>`
+ + '<D:propstat>'
+ + '<D:prop>'
+ + `<lp1:getlastmodified>${beforeTime}</lp1:getlastmodified>`
+ + '</D:prop>'
+ + '<D:status>HTTP/1.1 200 OK</D:status>'
+ + '</D:propstat>'
+ + '</D:response>'
+
+ + '<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">'
+ + `<D:href>${davBasePath}${encodedStrD}/zotero/AAAAAAAA.zip</D:href>`
+ + '<D:propstat>'
+ + '<D:prop>'
+ + `<lp1:getlastmodified>${beforeTime}</lp1:getlastmodified>`
+ + '</D:prop>'
+ + '<D:status>HTTP/1.1 200 OK</D:status>'
+ + '</D:propstat>'
+ + '</D:response>'
+ + '<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">'
+ + `<D:href>${davBasePath}${encodedStrD}/zotero/AAAAAAAA.prop</D:href>`
+ + '<D:propstat>'
+ + '<D:prop>'
+ + `<lp1:getlastmodified>${beforeTime}</lp1:getlastmodified>`
+ + '</D:prop>'
+ + '<D:status>HTTP/1.1 200 OK</D:status>'
+ + '</D:propstat>'
+ + '</D:response>'
+ + '</D:multistatus>'
+ });
+
+ Zotero.Prefs.set("sync.storage.url", davHostPath + strC + "/");
+ yield controller.purgeOrphanedStorageFiles();
})
})
})