diff --git a/chrome/content/zotero/xpcom/storage/webdav.js b/chrome/content/zotero/xpcom/storage/webdav.js index c22ffd77a..5af54a2aa 100644 --- 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 index ef5264f1a..8d3cac27b 100644 --- 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: '' + + '' + + '' + + `${davBasePath}${encodedStrD}/zotero/` + + '' + + '' + + `${beforeTime}` + + '' + + 'HTTP/1.1 200 OK' + + '' + + '' + + '' + + `${davBasePath}${encodedStrD}/zotero/lastsync` + + '' + + '' + + `${beforeTime}` + + '' + + 'HTTP/1.1 200 OK' + + '' + + '' + + + '' + + `${davBasePath}${encodedStrD}/zotero/AAAAAAAA.zip` + + '' + + '' + + `${beforeTime}` + + '' + + 'HTTP/1.1 200 OK' + + '' + + '' + + '' + + `${davBasePath}${encodedStrD}/zotero/AAAAAAAA.prop` + + '' + + '' + + `${beforeTime}` + + '' + + 'HTTP/1.1 200 OK' + + '' + + '' + + '' + }); + + Zotero.Prefs.set("sync.storage.url", davHostPath + strC + "/"); + yield controller.purgeOrphanedStorageFiles(); }) }) })