diff --git a/lib/stores/filesystem.js b/lib/stores/filesystem.js index f89821ca..369fa6da 100644 --- a/lib/stores/filesystem.js +++ b/lib/stores/filesystem.js @@ -14,6 +14,23 @@ const { concatStreams, walk } = require("../utils"); const S3RVER_SUFFIX = "%s._S3rver_%s"; class FilesystemStore { + static decodeKeyPath(keyPath) { + return process.platform === "win32" + ? keyPath.replace(/&../g, ent => + Buffer.from(ent.slice(1), "hex").toString() + ) + : keyPath; + } + + static encodeKeyPath(key) { + return process.platform === "win32" + ? key.replace( + /[<>:"\\|?*]/g, + ch => "&" + Buffer.from(ch, "utf8").toString("hex") + ) + : key; + } + constructor(rootDirectory) { this.rootDirectory = rootDirectory; } @@ -25,7 +42,7 @@ class FilesystemStore { } getResourcePath(bucket, key = "", resource) { - const parts = key.split("/"); + const parts = FilesystemStore.encodeKeyPath(key).split("/"); const suffix = format(S3RVER_SUFFIX, parts.pop(), resource); return path.join(this.rootDirectory, bucket, ...parts, suffix); } @@ -128,7 +145,10 @@ class FilesystemStore { const objectSuffix = format(S3RVER_SUFFIX, "", "object"); let keys = [...walk(bucketPath)] .filter(file => file.endsWith(objectSuffix)) - .map(key => key.slice(bucketPath.length + 1, -objectSuffix.length)); + .map(keyPath => + keyPath.slice(bucketPath.length + 1, -objectSuffix.length) + ) + .map(FilesystemStore.decodeKeyPath); if (!keys.length) { return { diff --git a/test/test.js b/test/test.js index 9c087c70..e59b009f 100644 --- a/test/test.js +++ b/test/test.js @@ -395,6 +395,23 @@ describe("S3rver Tests", function() { expect(data.ETag).to.match(/"[a-fA-F0-9]{32}"/); }); + it("should store a text object with invalid win32 path characters and retrieve it", async function() { + const reservedChars = '\\/:*?"<>|'; + await s3Client + .putObject({ + Bucket: buckets[0].name, + Key: `mykey-&-${reservedChars}`, + Body: "Hello!" + }) + .promise(); + + const object = await s3Client + .getObject({ Bucket: buckets[0].name, Key: `mykey-&-${reservedChars}` }) + .promise(); + + expect(object.Body.toString()).to.equal("Hello!"); + }); + it("should store a text object with no content type and retrieve it", async function() { const res = await request({ method: "PUT",