diff --git a/chrome/content/zotero/webpagedump/common.js b/chrome/content/zotero/webpagedump/common.js index 87810a624..288a2cfcb 100644 --- a/chrome/content/zotero/webpagedump/common.js +++ b/chrome/content/zotero/webpagedump/common.js @@ -644,7 +644,7 @@ var wpdCommon = { aDir.initWithPath(destdir); - aFile.copyTo(aDir, destfile); + aFile.copyToFollowingLinks(aDir, destfile); return true; // Added by Dan S. for Zotero }, diff --git a/chrome/content/zotero/xpcom/attachments.js b/chrome/content/zotero/xpcom/attachments.js index 76fe34e7b..13f3f6796 100644 --- a/chrome/content/zotero/xpcom/attachments.js +++ b/chrome/content/zotero/xpcom/attachments.js @@ -55,6 +55,10 @@ Zotero.Attachments = new function(){ throw ("'" + file.leafName + "' must be a file in Zotero.Attachments.importFromFile()"); } + if (file.leafName.endsWith(".lnk")) { + throw new Error("Cannot add Windows shortcut"); + } + Zotero.DB.beginTransaction(); try { @@ -165,7 +169,7 @@ Zotero.Attachments = new function(){ var storageDir = Zotero.getStorageDirectory(); var destDir = this.getStorageDirectory(itemID); _moveOrphanedDirectory(destDir); - file.parent.copyTo(storageDir, destDir.leafName); + file.parent.copyToFollowingLinks(storageDir, destDir.leafName); // Point to copied file var newFile = destDir.clone(); diff --git a/chrome/content/zotero/xpcom/data/item.js b/chrome/content/zotero/xpcom/data/item.js index 49f572f62..b1dd7e68e 100644 --- a/chrome/content/zotero/xpcom/data/item.js +++ b/chrome/content/zotero/xpcom/data/item.js @@ -2863,6 +2863,11 @@ Zotero.Item.prototype.getFile = function(row, skipExistsCheck) { if (Zotero.isWin || path.indexOf('/') != -1) { path = Zotero.File.getValidFileName(path, true); } + // Ignore .zotero* files that were relinked before we started blocking them + if (path.startsWith(".zotero")) { + Zotero.debug("Ignoring attachment file " + path, 2); + return false; + } var file = Zotero.Attachments.getStorageDirectory(this.id); file.QueryInterface(Components.interfaces.nsILocalFile); file.setRelativeDescriptor(file, path); @@ -3109,15 +3114,47 @@ Zotero.Item.prototype.relinkAttachmentFile = function(file, skipItemUpdate) { throw('Cannot relink linked URL in Zotero.Items.relinkAttachmentFile()'); } + if (file.leafName.endsWith(".lnk")) { + throw new Error("Cannot relink to Windows shortcut"); + } + var newName = Zotero.File.getValidFileName(file.leafName); if (!newName) { throw ("No valid characters in filename after filtering in Zotero.Item.relinkAttachmentFile()"); } - // Rename file to filtered name if necessary - if (file.leafName != newName) { - Zotero.debug("Renaming file '" + file.leafName + "' to '" + newName + "'"); - file.moveTo(null, newName); + try { + // If selected file isn't in the attachment's storage directory, + // copy it in and use that one instead + var storageDir = Zotero.Attachments.getStorageDirectory(this.id); + if (this.isImportedAttachment() && !file.parent.equals(storageDir)) { + // If file with same name already exists in the storage directory, + // move it out of the way + let targetFile = storageDir.clone(); + targetFile.append(newName); + let renamedFile; + if (targetFile.exists()) { + renamedFile = targetFile.clone(); + renamedFile.moveTo(null, newName + ".bak"); + } + + Zotero.File.copyToUnique(file, targetFile); + file = targetFile; + + // Delete backup file + if (renamedFile) { + renamedFile.remove(false); + } + } + // Rename file to filtered name if necessary + else if (file.leafName != newName) { + Zotero.debug("Renaming file '" + file.leafName + "' to '" + newName + "'"); + file.moveTo(null, newName); + } + } + catch (e) { + Zotero.logError(e); + return false; } var path = Zotero.Attachments.getPath(file, linkMode); @@ -3128,7 +3165,7 @@ Zotero.Item.prototype.relinkAttachmentFile = function(file, skipItemUpdate) { skipClientDateModifiedUpdate: skipItemUpdate }); - return false; + return true; } @@ -3342,7 +3379,8 @@ Zotero.Item.prototype.__defineGetter__('attachmentPath', function () { var sql = "SELECT path FROM itemAttachments WHERE itemID=?"; var path = Zotero.DB.valueQuery(sql, this.id); - if (!path) { + // Ignore .zotero* files that were relinked before we started blocking them + if (!path || path.startsWith('.zotero')) { path = ''; } this._attachmentPath = path; diff --git a/chrome/content/zotero/xpcom/file.js b/chrome/content/zotero/xpcom/file.js index b971b5fb5..bbb81729a 100644 --- a/chrome/content/zotero/xpcom/file.js +++ b/chrome/content/zotero/xpcom/file.js @@ -426,7 +426,7 @@ Zotero.File = new function(){ newFile.remove(null); // Copy file to unique name - file.copyTo(newFile.parent, newName); + file.copyToFollowingLinks(newFile.parent, newName); return newFile; } @@ -442,7 +442,7 @@ Zotero.File = new function(){ while (otherFiles.hasMoreElements()) { var file = otherFiles.getNext(); file.QueryInterface(Components.interfaces.nsIFile); - file.copyTo(newDir, null); + file.copyToFollowingLinks(newDir, null); } } @@ -505,6 +505,8 @@ Zotero.File = new function(){ // 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 hidden files + fileName = fileName.replace(/^\./, ''); // Don't allow blank or illegal filenames if (!fileName || fileName == '.' || fileName == '..') { fileName = '_'; diff --git a/chrome/content/zotero/xpcom/itemTreeView.js b/chrome/content/zotero/xpcom/itemTreeView.js index 883b6952f..7aca01e2a 100644 --- a/chrome/content/zotero/xpcom/itemTreeView.js +++ b/chrome/content/zotero/xpcom/itemTreeView.js @@ -2810,7 +2810,7 @@ Zotero.ItemTreeView.fileDragDataProvider.prototype = { } } - parentDir.copyTo(destDir, newName ? newName : dirName); + parentDir.copyToFollowingLinks(destDir, newName ? newName : dirName); // Store nsIFile if (useTemp) { @@ -2851,7 +2851,7 @@ Zotero.ItemTreeView.fileDragDataProvider.prototype = { } } - file.copyTo(destDir, newName ? newName : null); + file.copyToFollowingLinks(destDir, newName ? newName : null); // Store nsIFile if (useTemp) { @@ -3219,6 +3219,14 @@ Zotero.ItemTreeView.prototype.drop = function(row, orient, dataTransfer) { var itemID = Zotero.Attachments.linkFromFile(file, sourceItemID); } else { + if (file.leafName.endsWith(".lnk")) { + let wm = Components.classes["@mozilla.org/appshell/window-mediator;1"] + .getService(Components.interfaces.nsIWindowMediator); + let win = wm.getMostRecentWindow("navigator:browser"); + win.ZoteroPane.displayCannotAddShortcutMessage(file.path); + Zotero.DB.commitTransaction(); + continue; + } var itemID = Zotero.Attachments.importFromFile(file, sourceItemID, targetLibraryID); // If moving, delete original file if (dragData.dropEffect == 'move') { diff --git a/chrome/content/zotero/xpcom/storage.js b/chrome/content/zotero/xpcom/storage.js index 5979af532..48a2d959f 100644 --- a/chrome/content/zotero/xpcom/storage.js +++ b/chrome/content/zotero/xpcom/storage.js @@ -1486,6 +1486,10 @@ Zotero.Sync.Storage = new function () { if (!file) { throw ("Empty path for item " + item.key + " in " + funcName); } + // Don't save Windows aliases + if (file.leafName.endsWith('.lnk')) { + return false; + } var fileName = file.leafName; var renamed = false; @@ -1899,8 +1903,9 @@ Zotero.Sync.Storage = new function () { params.push(Zotero.Sync.Storage.SYNC_STATE_TO_DOWNLOAD); } sql += ") " - // Skip attachments with empty path, which can't be saved - + "AND path!=''"; + // Skip attachments with empty path, which can't be saved, and files with .zotero* + // paths, which have somehow ended up in some users' libraries + + "AND path!='' AND path NOT LIKE 'storage:.zotero%'"; var itemIDs = Zotero.DB.columnQuery(sql, params); if (!itemIDs) { return []; diff --git a/chrome/content/zotero/zoteroPane.js b/chrome/content/zotero/zoteroPane.js index 14edc7029..bdc57bfc2 100644 --- a/chrome/content/zotero/zoteroPane.js +++ b/chrome/content/zotero/zoteroPane.js @@ -3116,8 +3116,16 @@ var ZoteroPane = new function() var attachmentID; if(link) attachmentID = Zotero.Attachments.linkFromFile(file, id); - else + else { + if (file.leafName.endsWith(".lnk")) { + let wm = Components.classes["@mozilla.org/appshell/window-mediator;1"] + .getService(Components.interfaces.nsIWindowMediator); + let win = wm.getMostRecentWindow("navigator:browser"); + win.ZoteroPane.displayCannotAddShortcutMessage(file.path); + continue; + } attachmentID = Zotero.Attachments.importFromFile(file, id, libraryID); + } if(attachmentID && !id) { @@ -3757,6 +3765,16 @@ var ZoteroPane = new function() } + // TODO: Figure out a functioning way to get the original path and just copy the real file + this.displayCannotAddShortcutMessage = function (path) { + Zotero.alert( + null, + Zotero.getString("general.error"), + Zotero.getString("file.error.cannotAddShortcut") + (path ? "\n\n" + path : "") + ); + } + + function showAttachmentNotFoundDialog(itemID, noLocate) { var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]. createInstance(Components.interfaces.nsIPromptService); @@ -3951,25 +3969,43 @@ var ZoteroPane = new function() throw('Item ' + itemID + ' not found in ZoteroPane_Local.relinkAttachment()'); } - var nsIFilePicker = Components.interfaces.nsIFilePicker; - var fp = Components.classes["@mozilla.org/filepicker;1"] - .createInstance(nsIFilePicker); - fp.init(window, Zotero.getString('pane.item.attachments.select'), nsIFilePicker.modeOpen); - - - var file = item.getFile(false, true); - var dir = Zotero.File.getClosestDirectory(file); - if (dir) { - dir.QueryInterface(Components.interfaces.nsILocalFile); - fp.displayDirectory = dir; - } - - fp.appendFilters(Components.interfaces.nsIFilePicker.filterAll); - - if (fp.show() == nsIFilePicker.returnOK) { - var file = fp.file; - file.QueryInterface(Components.interfaces.nsILocalFile); - item.relinkAttachmentFile(file); + while (true) { + var nsIFilePicker = Components.interfaces.nsIFilePicker; + var fp = Components.classes["@mozilla.org/filepicker;1"] + .createInstance(nsIFilePicker); + fp.init(window, Zotero.getString('pane.item.attachments.select'), nsIFilePicker.modeOpen); + + + var file = item.getFile(false, true); + var dir = Zotero.File.getClosestDirectory(file); + if (dir) { + dir.QueryInterface(Components.interfaces.nsILocalFile); + fp.displayDirectory = dir; + } + + fp.appendFilters(Components.interfaces.nsIFilePicker.filterAll); + + if (fp.show() == nsIFilePicker.returnOK) { + let file = fp.file; + file.QueryInterface(Components.interfaces.nsILocalFile); + + // Disallow hidden files + // TODO: Display a message + if (file.leafName.startsWith('.')) { + continue; + } + + // Disallow Windows shortcuts + if (file.leafName.endsWith(".lnk")) { + this.displayCannotAddShortcutMessage(file.path); + continue; + } + + item.relinkAttachmentFile(file); + break; + } + + break; } } diff --git a/chrome/locale/en-US/zotero/zotero.properties b/chrome/locale/en-US/zotero/zotero.properties index fa45a5733..90df69202 100644 --- a/chrome/locale/en-US/zotero/zotero.properties +++ b/chrome/locale/en-US/zotero/zotero.properties @@ -963,6 +963,7 @@ file.accessError.message.windows = Check that the file is not currently in use, file.accessError.message.other = Check that the file is not currently in use and that its permissions allow write access. file.accessError.restart = Restarting your computer or disabling security software may also help. file.accessError.showParentDir = Show Parent Directory +file.error.cannotAddShortcut = Shortcut files cannot be added directly. Please select the original file. lookup.failure.title = Lookup Failed lookup.failure.description = Zotero could not find a record for the specified identifier. Please verify the identifier and try again.