diff --git a/src/storage/keyvalue/EncodingPathStorage.ts b/src/storage/keyvalue/EncodingPathStorage.ts index da79e351e..867fda181 100644 --- a/src/storage/keyvalue/EncodingPathStorage.ts +++ b/src/storage/keyvalue/EncodingPathStorage.ts @@ -62,7 +62,15 @@ export class EncodingPathStorage implements KeyValueStorage { * Converts an internal storage path string into the original path key. */ protected pathToKey(path: string): string { - const buffer = Buffer.from(path.slice(this.basePath.length), 'base64'); + // While the main part of a base64 encoded string is same from any changes from encoding or decoding URL parts, + // the `=` symbol that is used for padding is not. + // This can cause incorrect results when calling these function, + // where the original path contains `YXBwbGU%3D` instead of `YXBwbGU=`. + // This does not create any issues when the source store does not encode the string, so is safe to always call. + // For consistency, we might want to also always encode when creating the path in `keyToPath()`, + // but that would potentially break existing implementations that do not do encoding, + // and is not really necessary to solve any issues. + const buffer = Buffer.from(decodeURIComponent(path.slice(this.basePath.length)), 'base64'); return buffer.toString('utf-8'); } } diff --git a/test/unit/storage/keyvalue/EncodingPathStorage.test.ts b/test/unit/storage/keyvalue/EncodingPathStorage.test.ts index 6e1c7abe5..0ad221252 100644 --- a/test/unit/storage/keyvalue/EncodingPathStorage.test.ts +++ b/test/unit/storage/keyvalue/EncodingPathStorage.test.ts @@ -48,4 +48,20 @@ describe('An EncodingPathStorage', (): void => { expect(results).toHaveLength(1); expect(results[0]).toEqual([ 'key', data ]); }); + + it('correctly handles keys that have been encoded by the source storage.', async(): Promise => { + // Base 64 encoding of 'apple' + const encodedKey = 'YXBwbGU='; + const generatedPath = `${relativePath}${encodeURIComponent(encodedKey)}`; + const data = 'data'; + + map.set(generatedPath, data); + + const results = []; + for await (const entry of storage.entries()) { + results.push(entry); + } + expect(results).toHaveLength(1); + expect(results[0]).toEqual([ 'apple', data ]); + }); });