@@ -454,15 +454,20 @@ export class FilesRouter {
454454 const type = slashIndex > 0 ? contentType . slice ( 0 , slashIndex ) . trim ( ) : '' ;
455455 const subtype =
456456 slashIndex > 0 ? contentType . slice ( slashIndex + 1 ) . split ( ';' ) [ 0 ] . trim ( ) : '' ;
457- if ( ! type || ! subtype ) {
458- // A Content-Type that does not parse as `type/subtype` with a non-empty
459- // type AND subtype is malformed: there is no valid MIME type without a
460- // subtype (RFC 9110 §8.3.1). Browsers cannot parse it and fall back to
461- // MIME-sniffing the file body, which can render HTML/script markers as
462- // active content on storage adapters that serve the stored Content-Type
463- // (e.g. `image`, `image/`). Surface the precise blocklist message when
464- // the bare token names a blocked extension (e.g. a no-slash `svg`),
465- // otherwise reject the unparseable Content-Type.
457+ // A valid media type is `type/subtype` where both are non-empty `token`s
458+ // (RFC 9110 §5.6.2). Reject anything else.
459+ const token = / ^ [ ! # $ % & ' * + \- . ^ _ ` | ~ A - Z a - z 0 - 9 ] + $ / ;
460+ if ( ! token . test ( type ) || ! token . test ( subtype ) ) {
461+ // A Content-Type that does not parse as `type/subtype` with valid,
462+ // non-empty type AND subtype tokens is malformed: there is no valid MIME
463+ // type without a subtype (RFC 9110 §8.3.1), and malformed tokens such as
464+ // `image//svg+xml` or `text/plain,text/html` are equally unparseable.
465+ // Browsers cannot parse such values and fall back to MIME-sniffing the
466+ // file body, which can render HTML/script markers as active content on
467+ // storage adapters that serve the stored Content-Type (e.g. `image`,
468+ // `image/`). Surface the precise blocklist message when the bare token
469+ // names a blocked extension (e.g. a no-slash `svg`), otherwise reject the
470+ // unparseable Content-Type.
466471 const bareToken = ( slashIndex < 0 ? contentType . split ( ';' ) [ 0 ] : type ) . replace (
467472 / \s + / g,
468473 ''
0 commit comments