www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | Submodules | README | LICENSE

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:
Mchrome/content/zotero/xpcom/storage/webdav.js | 30++++++++++++------------------
Mtest/tests/webdavTest.js | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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(); }) }) })