www

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

commit 2edda44b125058062de3dfea81539d312d1efaa6
parent 39092b7c82f78085635eb6c4af8710a173d4425e
Author: Dan Stillman <dstillman@zotero.org>
Date:   Tue, 15 Dec 2009 08:49:18 +0000

- Better handle invalid characters in filenames when syncing
- Automatically rename downloaded file to match known filename if different


Diffstat:
Mchrome/content/zotero/xpcom/data/item.js | 17++++++++++++++---
Mchrome/content/zotero/xpcom/file.js | 18+++++++++++++-----
Mchrome/content/zotero/xpcom/storage.js | 97++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
3 files changed, 99 insertions(+), 33 deletions(-)

diff --git a/chrome/content/zotero/xpcom/data/item.js b/chrome/content/zotero/xpcom/data/item.js @@ -2492,6 +2492,19 @@ Zotero.Item.prototype.getFile = function(row, skipExistsCheck) { } // Strip "storage:" var path = row.path.substr(8); + // setRelativeDescriptor() silently uses the parent directory on Windows + // if the filename contains certain characters, so strip them — + // but don't skip characters outside of XML range, since they may be + // correct in the opaque relative descriptor string + // + // This is a bad place for this, since the change doesn't make it + // back up to the sync server, but we do it to make sure we don't + // accidentally use the parent dir. Syncing to OS X, which doesn't + // exhibit this bug, will properly correct such filenames in + // storage.js and propagate the change + if (Zotero.isWin) { + path = Zotero.File.getValidFileName(path, true); + } var file = Zotero.Attachments.getStorageDirectory(this.id); file.QueryInterface(Components.interfaces.nsILocalFile); file.setRelativeDescriptor(file, path); @@ -2554,9 +2567,6 @@ Zotero.Item.prototype.getFile = function(row, skipExistsCheck) { } -/** - * _row_ is optional itemAttachments row if available to skip queries - */ Zotero.Item.prototype.getFilename = function () { if (!this.isAttachment()) { throw ("getFileName() can only be called on attachment items in Zotero.Item.getFilename()"); @@ -2652,6 +2662,7 @@ Zotero.Item.prototype.relinkAttachmentFile = function(file) { var path = Zotero.Attachments.getPath(file, linkMode); this.attachmentPath = path; this.save(); + return false; } diff --git a/chrome/content/zotero/xpcom/file.js b/chrome/content/zotero/xpcom/file.js @@ -206,16 +206,24 @@ Zotero.File = new function(){ } - // Strip potentially invalid characters - // See http://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words - function getValidFileName(fileName) { + /** + * Strip potentially invalid characters + * + * See http://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words + * + * @param {String} fileName + * @param {Boolean} [skipXML=false] Don't strip characters invalid in XML + */ + function getValidFileName(fileName, skipXML) { // TODO: use space instead, and figure out what's doing extra // URL encode when saving attachments that trigger this fileName = fileName.replace(/[\/\\\?%\*:|"<>]/g, ''); // Replace newlines (which shouldn't be in the string in the first place) with spaces fileName = fileName.replace(/\n/g, ' '); - // Strip characters not valid in XML, since they won't sync and they're probably unwanted - fileName = fileName.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\ud800-\udfff\ufffe\uffff]/g, ''); + if (!skipXML) { + // Strip characters not valid in XML, since they won't sync and they're probably unwanted + fileName = fileName.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\ud800-\udfff\ufffe\uffff]/g, ''); + } // Don't allow blank filename if (!fileName) { fileName = '_'; diff --git a/chrome/content/zotero/xpcom/storage.js b/chrome/content/zotero/xpcom/storage.js @@ -639,7 +639,7 @@ Zotero.Sync.Storage = new function () { // Only save hash if file isn't compressed if (!data.compressed) { - Zotero.Sync.Storage.setSyncedHash(item.id, syncHash, false); + Zotero.Sync.Storage.setSyncedHash(item.id, syncHash); } Zotero.Sync.Storage.setSyncState(item.id, Zotero.Sync.Storage.SYNC_STATE_IN_SYNC); } @@ -793,16 +793,35 @@ Zotero.Sync.Storage = new function () { if (!file) { throw ("Empty path for item " + item.key + " in " + funcName); } - var newName = file.leafName; - var returnFile = null - Zotero.debug("Moving download file " + tempFile.leafName + " into attachment directory"); + var itemFileName = item.getFilename(); + var fileName = file.leafName; + var renamed = false; + + // If file doesn't match the known filename, use that name + if (itemFileName != fileName) { + Zotero.debug("Renaming file '" + fileName + "' to known filename '" + itemFileName + "'"); + fileName = itemFileName; + renamed = true; + } + + // Make sure the new filename is valid, in case an invalid character made it over + // (e.g., from before we checked for them) + var filteredName = Zotero.File.getValidFileName(fileName); + if (filteredName != fileName) { + Zotero.debug("Filtering filename '" + fileName + "' to '" + filteredName + "'"); + fileName = filteredName; + renamed = true; + } + + Zotero.debug("Moving download file " + tempFile.leafName + " into attachment directory as '" + fileName + "'"); try { - tempFile.moveTo(parentDir, newName); + tempFile.moveTo(parentDir, fileName); } catch (e) { var destFile = parentDir.clone(); - destFile.append(newName); + destFile.QueryInterface(Components.interfaces.nsILocalFile); + destFile.setRelativeDescriptor(parentDir, fileName); var windowsLength = false; var nameLength = false; @@ -818,8 +837,8 @@ Zotero.Sync.Storage = new function () { else if (e.name == "NS_ERROR_FAILURE" && destFile.leafName.length >= 244) { nameLength = true; } - // ecrypt (on Ubuntu, at least) can result in a lower limit -- - // not much we can do about this, but log a warning + // ecrypt (on Ubuntu, at least) can result in a lower limit — + // not much we can do about this, but throw a specific error else if (e.name == "NS_ERROR_FAILURE" && Zotero.isLinux && destFile.leafName.length > 130) { var e = "Error creating file '" + destFile.leafName + "' " + "(Are you using filesystem encryption such as eCryptfs " @@ -850,24 +869,28 @@ Zotero.Sync.Storage = new function () { // Shorten file if it's too long -- we don't relink it, but this should // be pretty rare and probably only occurs on extraneous files with // gibberish for filenames - var newName = destFile.leafName.substr(0, newLength - (ext.length + 1)) + ext; - var msg = "Shortening filename to '" + newName + "'"; + var fileName = destFile.leafName.substr(0, newLength - (ext.length + 1)) + ext; + var msg = "Shortening filename to '" + fileName + "'"; Zotero.debug(msg, 2); Components.utils.reportError(msg); - tempFile.moveTo(parentDir, newName); - - destFile = parentDir.clone(); - destFile.append(newName); - - // processDownload() needs to know that we're renaming the file - returnFile = destFile; + tempFile.moveTo(parentDir, fileName); + renamed = true; } else { throw(e); } } + var returnFile = null; + // processDownload() needs to know that we're renaming the file + if (renamed) { + destFile = parentDir.clone(); + destFile.QueryInterface(Components.interfaces.nsILocalFile); + destFile.setRelativeDescriptor(parentDir, fileName); + returnFile = destFile; + } + return returnFile; } @@ -914,9 +937,12 @@ Zotero.Sync.Storage = new function () { } var returnFile = null; + var count = 0; var entries = zipReader.findEntries(null); while (entries.hasMore()) { + var renamed = false; + count++; var entryName = entries.getNext(); var b64re = /%ZB64$/; if (entryName.match(b64re)) { @@ -933,10 +959,30 @@ Zotero.Sync.Storage = new function () { continue; } + var itemFileName = item.getFilename(); + + // If only one file in zip and it doesn't match the known filename, + // take our chances and use that name + if (count == 1 && !entries.hasMore()) { + if (itemFileName != fileName) { + Zotero.debug("Renaming single file '" + fileName + "' in ZIP to known filename '" + itemFileName + "'"); + fileName = itemFileName; + renamed = true; + } + } + + var primaryFile = itemFileName == fileName; + // Make sure the new filename is valid, in case an invalid character - // for this OS somehow make it into the ZIP (e.g., from before we checked - // for them or if a user manually renamed and relinked a file on another OS) - fileName = Zotero.File.getValidFileName(fileName); + // somehow make it into the ZIP (e.g., from before we checked for them) + var filteredName = Zotero.File.getValidFileName(fileName); + if (filteredName != fileName) { + Zotero.debug("Filtering filename '" + fileName + "' to '" + filteredName + "'"); + fileName = filteredName; + if (primaryFile) { + renamed = true; + } + } Zotero.debug("Extracting " + fileName); var destFile = parentDir.clone(); @@ -976,9 +1022,6 @@ Zotero.Sync.Storage = new function () { } if (windowsLength || nameLength) { - // Is this the main attachment file? - var primaryFile = item.getFile(null, true).leafName == destFile.leafName; - // Preserve extension var matches = destFile.leafName.match(/\.[a-z0-9]{0,8}$/); var ext = matches ? matches[0] : ""; @@ -1026,9 +1069,8 @@ Zotero.Sync.Storage = new function () { destFile.create(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0644); - // If we're renaming the main file, processDownload() needs to know if (primaryFile) { - returnFile = destFile; + renamed = true; } } else { @@ -1049,6 +1091,11 @@ Zotero.Sync.Storage = new function () { continue; } destFile.permissions = 0644; + + // If we're renaming the main file, processDownload() needs to know + if (renamed) { + returnFile = destFile; + } } zipReader.close(); zipFile.remove(false);