Disallow slashes in stored-file attachment paths

Someone ended up (via a plugin, presumably) with stored-file attachments
with a full path after 'storage:', which broke file syncing. Throw when
setting a stored-file path containing a slash, and strip paths from
existing filenames in a schema update step. No particular reason to
think that the file with that basename will exist in the storage dir,
but at least it will be looking for the right file and not be totally
broken.

Separately, the dataserver will clean up filenames with full paths and
block going forward.

https://forums.zotero.org/discussion/132822/reference-sychronization-error
This commit is contained in:
Dan Stillman 2026-07-20 23:25:21 -04:00
parent 5877952954
commit ee68452b05
4 changed files with 28 additions and 2 deletions

View file

@ -3663,6 +3663,9 @@ Zotero.defineProperty(Zotero.Item.prototype, 'attachmentPath', {
}
val = 'storage:' + PathUtils.filename(val);
}
if (/^storage:.*[/\\]/.test(val)) {
throw new Error(`Stored file filename cannot contain a slash -- got '${val}'`);
}
}
if (val == this.attachmentPath) {

View file

@ -3690,6 +3690,19 @@ Zotero.Schema = new function () {
await Zotero.DB.queryAsync("INSERT INTO savedSearchConditions SELECT savedSearchID, searchConditionID, condition, operator, value FROM savedSearchConditionsOld");
await Zotero.DB.queryAsync("DROP TABLE savedSearchConditionsOld");
}
else if (i == 128) {
// Strip full paths (e.g., from third-party tools) from stored-file attachment
// paths, which should contain only a filename after 'storage:'
let rows = await Zotero.DB.queryAsync("SELECT itemID, path FROM itemAttachments WHERE linkMode IN (0, 1) AND (path LIKE ? OR path LIKE ?)", ['storage:%/%', 'storage:%\\%']);
for (let row of rows) {
let filename = row.path.substr(8).split(/[/\\]/).pop();
if (!filename) {
continue;
}
await Zotero.DB.queryAsync("UPDATE itemAttachments SET path=? WHERE itemID=?", ['storage:' + filename, row.itemID]);
}
}
}
await _updateDBVersion('userdata', toVersion);

View file

@ -1,4 +1,4 @@
-- 127
-- 128
-- Copyright (c) 2009 Center for History and New Media
-- George Mason University, Fairfax, Virginia, USA

View file

@ -1158,7 +1158,17 @@ describe("Zotero.Item", function () {
assert.equal(attachment.attachmentFilename, filename);
});
it("should reject a filename containing a slash", async function () {
var item = await createDataObject('item');
var attachment = new Zotero.Item("attachment");
attachment.attachmentLinkMode = Zotero.Attachments.LINK_MODE_IMPORTED_FILE;
attachment.parentID = item.id;
assert.throws(() => attachment.attachmentFilename = "D:/Foo/Bar/test.pdf", /slash/);
assert.throws(() => attachment.attachmentFilename = "D:\\Foo\\Bar\\test.pdf", /slash/);
});
it("should get a filename for a base-dir-relative file", function () {
var dir = getTestDataDirectory().path;
Zotero.Prefs.set('saveRelativeAttachmentPath', true)