diff --git a/src/main.test.ts b/src/main.test.ts index 97af08c..fb50705 100644 --- a/src/main.test.ts +++ b/src/main.test.ts @@ -608,11 +608,12 @@ describe('set', () => { siteID, }) - const expectedError = `Keys can only contain letters, numbers, percentage signs (%), exclamation marks (!), dots (.), asterisks (*), single quotes ('), parentheses (()), dashes (-) and underscores (_) up to a maximum of 600 characters. Keys can also contain forward slashes (/), but must not start with one.` - - expect(async () => await blobs.set('kéy', 'value')).rejects.toThrowError(expectedError) - expect(async () => await blobs.set('/key', 'value')).rejects.toThrowError(expectedError) - expect(async () => await blobs.set('a'.repeat(801), 'value')).rejects.toThrowError(expectedError) + expect(async () => await blobs.set('/key', 'value')).rejects.toThrowError( + 'Blob key must not start with forward slash (/).', + ) + expect(async () => await blobs.set('a'.repeat(801), 'value')).rejects.toThrowError( + 'Blob key must be a sequence of Unicode characters whose UTF-8 encoding is at most 600 bytes long.', + ) }) test('Retries failed operations', async () => { @@ -1228,9 +1229,7 @@ describe(`getStore`, () => { token: apiToken, siteID, }), - ).toThrowError( - `Store name can only contain letters, numbers, percentage signs (%), exclamation marks (!), dots (.), asterisks (*), single quotes ('), parentheses (()), dashes (-) and underscores (_) up to a maximum of 64 characters.`, - ) + ).toThrowError(`Store name must not contain forward slashes (/).`) expect(() => getStore({ @@ -1238,9 +1237,7 @@ describe(`getStore`, () => { token: apiToken, siteID, }), - ).toThrowError( - `Store name can only contain letters, numbers, percentage signs (%), exclamation marks (!), dots (.), asterisks (*), single quotes ('), parentheses (()), dashes (-) and underscores (_) up to a maximum of 64 characters.`, - ) + ).toThrowError(`Store name must be a sequence of Unicode characters whose UTF-8 encoding is at most 64 bytes long.`) expect(() => getStore({ @@ -1248,7 +1245,7 @@ describe(`getStore`, () => { token: apiToken, siteID, }), - ).toThrowError('Store name cannot start with the string `deploy:`, which is a reserved namespace.') + ).toThrowError('Store name must not start with the `deploy:` reserved keyword.') const context = { siteID, @@ -1257,9 +1254,7 @@ describe(`getStore`, () => { env.NETLIFY_BLOBS_CONTEXT = Buffer.from(JSON.stringify(context)).toString('base64') - expect(() => getStore('deploy:foo')).toThrowError( - 'Store name cannot start with the string `deploy:`, which is a reserved namespace.', - ) + expect(() => getStore('deploy:foo')).toThrowError('Store name must not start with the `deploy:` reserved keyword.') }) test('Throws when there is no `fetch` implementation available', async () => { diff --git a/src/store.ts b/src/store.ts index 5b8a809..10b732d 100644 --- a/src/store.ts +++ b/src/store.ts @@ -1,3 +1,5 @@ +import { Buffer } from 'node:buffer' + import { ListResponse, ListResponseBlob } from './backend/list.ts' import { Client } from './client.ts' import { decodeMetadata, Metadata, METADATA_HEADER_INTERNAL } from './metadata.ts' @@ -305,9 +307,13 @@ export class Store { } private static validateKey(key: string) { - if (key.startsWith('/') || !/^[\w%!.*'()/-]{1,600}$/.test(key)) { + if (key.startsWith('/') || key.startsWith('%2F')) { + throw new Error('Blob key must not start with forward slash (/).') + } + + if (Buffer.byteLength(key, 'utf8') > 600) { throw new Error( - "Keys can only contain letters, numbers, percentage signs (%), exclamation marks (!), dots (.), asterisks (*), single quotes ('), parentheses (()), dashes (-) and underscores (_) up to a maximum of 600 characters. Keys can also contain forward slashes (/), but must not start with one.", + 'Blob key must be a sequence of Unicode characters whose UTF-8 encoding is at most 600 bytes long.', ) } } @@ -323,13 +329,17 @@ export class Store { } private static validateStoreName(name: string) { - if (name.startsWith('deploy:')) { - throw new Error('Store name cannot start with the string `deploy:`, which is a reserved namespace.') + if (name.startsWith('deploy:') || name.startsWith('deploy%3A1')) { + throw new Error('Store name must not start with the `deploy:` reserved keyword.') + } + + if (name.includes('/') || name.includes('%2F')) { + throw new Error('Store name must not contain forward slashes (/).') } - if (!/^[\w%!.*'()-]{1,64}$/.test(name)) { + if (Buffer.byteLength(name, 'utf8') > 64) { throw new Error( - "Store name can only contain letters, numbers, percentage signs (%), exclamation marks (!), dots (.), asterisks (*), single quotes ('), parentheses (()), dashes (-) and underscores (_) up to a maximum of 64 characters.", + 'Store name must be a sequence of Unicode characters whose UTF-8 encoding is at most 64 bytes long.', ) } }