diff --git a/.changeset/selfish-owls-thank.md b/.changeset/selfish-owls-thank.md new file mode 100644 index 000000000..dbc1371bc --- /dev/null +++ b/.changeset/selfish-owls-thank.md @@ -0,0 +1,5 @@ +--- +'@vercel/blob': minor +--- + +BREAKING CHANGE, we're no more accepting non-encoded versions of ?, # and // in pathnames. If you want to use such characters in your pathnames then you will need to encode them. diff --git a/packages/blob/src/copy.ts b/packages/blob/src/copy.ts index 4669daed9..5a4f3befe 100644 --- a/packages/blob/src/copy.ts +++ b/packages/blob/src/copy.ts @@ -1,6 +1,6 @@ import { MAXIMUM_PATHNAME_LENGTH, requestApi } from './api'; import type { CommonCreateBlobOptions } from './helpers'; -import { BlobError } from './helpers'; +import { BlobError, disallowedPathnameCharacters } from './helpers'; export type CopyCommandOptions = CommonCreateBlobOptions; @@ -41,6 +41,14 @@ export async function copy( ); } + for (const invalidCharacter of disallowedPathnameCharacters) { + if (toPathname.includes(invalidCharacter)) { + throw new BlobError( + `pathname cannot contain "${invalidCharacter}", please encode it if needed`, + ); + } + } + const headers: Record = {}; if (options.addRandomSuffix !== undefined) { diff --git a/packages/blob/src/helpers.ts b/packages/blob/src/helpers.ts index a4e4f0a3d..af3ae23bf 100644 --- a/packages/blob/src/helpers.ts +++ b/packages/blob/src/helpers.ts @@ -81,3 +81,5 @@ export function isPlainObject(value: unknown): boolean { !(Symbol.iterator in value) ); } + +export const disallowedPathnameCharacters = ['#', '?', '//']; diff --git a/packages/blob/src/index.node.test.ts b/packages/blob/src/index.node.test.ts index 815c2b987..6b49cea5c 100644 --- a/packages/blob/src/index.node.test.ts +++ b/packages/blob/src/index.node.test.ts @@ -586,6 +586,42 @@ describe('blob client', () => { ); }); + it('throws when pathname contains #', async () => { + await expect( + put('foo#bar.txt', 'Test Body', { + access: 'public', + }), + ).rejects.toThrow( + new Error( + 'Vercel Blob: pathname cannot contain "#", please encode it if needed', + ), + ); + }); + + it('throws when pathname contains ?', async () => { + await expect( + put('foo?bar.txt', 'Test Body', { + access: 'public', + }), + ).rejects.toThrow( + new Error( + 'Vercel Blob: pathname cannot contain "?", please encode it if needed', + ), + ); + }); + + it('throws when pathname contains //', async () => { + await expect( + put('foo//bar.txt', 'Test Body', { + access: 'public', + }), + ).rejects.toThrow( + new Error( + 'Vercel Blob: pathname cannot contain "//", please encode it if needed', + ), + ); + }); + const table: [string, (signal: AbortSignal) => Promise][] = [ [ 'put', diff --git a/packages/blob/src/put-helpers.ts b/packages/blob/src/put-helpers.ts index f36abacf6..b2a0cd42b 100644 --- a/packages/blob/src/put-helpers.ts +++ b/packages/blob/src/put-helpers.ts @@ -2,7 +2,7 @@ import type { Readable } from 'stream'; import type { ClientCommonCreateBlobOptions } from './client'; import type { CommonCreateBlobOptions } from './helpers'; -import { BlobError } from './helpers'; +import { BlobError, disallowedPathnameCharacters } from './helpers'; import { MAXIMUM_PATHNAME_LENGTH } from './api'; export const putOptionHeaderMap = { @@ -92,6 +92,14 @@ export async function createPutOptions< ); } + for (const invalidCharacter of disallowedPathnameCharacters) { + if (pathname.includes(invalidCharacter)) { + throw new BlobError( + `pathname cannot contain "${invalidCharacter}", please encode it if needed`, + ); + } + } + if (!options) { throw new BlobError('missing options, see usage'); }