[typescript-fetch] name downloaded files from Content-Disposition, BlobApiResponse returns a File - #24957
Conversation
…obApiResponse returns a File
There was a problem hiding this comment.
All reported issues were addressed across 25 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
…e bare Blob where File is not a global - filename parameters matched case-insensitively and only at a parameter boundary - quoted-string values keep their semicolons and escaped quotes - runtimes without a global File (Node.js before 20) still receive the Blob - unit tests in the typescript-fetch default test project
|
Thanks cubic-dev-ai, the 40 findings boil down to 4 distinct points repeated on each generated
|
There was a problem hiding this comment.
10 issues found across 27 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="samples/client/petstore/typescript-fetch/builds/enum/runtime.ts">
<violation number="1" location="samples/client/petstore/typescript-fetch/builds/enum/runtime.ts:519">
P2: When another disposition parameter contains a quoted semicolon, `encoded` parses text inside that quoted value as a real `filename*` parameter. Parse Content-Disposition parameters while tracking quoted-string and escape state before applying `filename*` precedence.</violation>
</file>
<file name="samples/client/petstore/typescript-fetch/builds/default/runtime.ts">
<violation number="1" location="samples/client/petstore/typescript-fetch/builds/default/runtime.ts:519">
P2: When another quoted parameter contains a semicolon followed by `filename*` or `filename`, this parser treats the embedded text as a real parameter and names the returned file incorrectly. Parse parameters while honoring quoted-string boundaries before selecting `filename*` or `filename`.</violation>
<violation number="2" location="samples/client/petstore/typescript-fetch/builds/default/runtime.ts:527">
P2: When a quoted parameter contains `; filename=...`, these regexes treat the semicolon as a parameter boundary and return a filename from inside the quoted value. Parse parameters while honoring quoted-string and escape state before matching `filename` or `filename*`.</violation>
</file>
<file name="samples/client/petstore/typescript-fetch/builds/allOf-readonly/runtime.ts">
<violation number="1" location="samples/client/petstore/typescript-fetch/builds/allOf-readonly/runtime.ts:519">
P2: When a quoted parameter contains `; filename*=` text, `parseContentDispositionFilename` treats that text as a real parameter because the regex does not track quoted strings. Parse parameters while respecting quoted-string boundaries before applying the `filename*` and `filename` precedence.</violation>
</file>
<file name="samples/client/petstore/typescript-fetch/builds/with-string-enums/runtime.ts">
<violation number="1" location="samples/client/petstore/typescript-fetch/builds/with-string-enums/runtime.ts:527">
P2: When another disposition parameter contains a semicolon inside a quoted string, this regex extracts `filename` from that quoted value instead of the actual parameter. Parse parameters while honoring quoted-string escapes before looking for `filename` and `filename*`.</violation>
</file>
<file name="samples/client/petstore/typescript-fetch/builds/kebab-case/runtime.ts">
<violation number="1" location="samples/client/petstore/typescript-fetch/builds/kebab-case/runtime.ts:519">
P2: When a quoted filename contains `; filename*=` text, this regex treats the text inside the quoted string as a real parameter and returns the wrong download name. Parse parameters with quote-aware boundaries before applying `filename*` precedence.</violation>
</file>
<file name="samples/client/petstore/typescript-fetch/builds/snakecase-discriminator/runtime.ts">
<violation number="1" location="samples/client/petstore/typescript-fetch/builds/snakecase-discriminator/runtime.ts:519">
P2: When another disposition parameter contains a quoted semicolon, this regex treats the semicolon inside the quoted value as a parameter boundary and can select an embedded `filename*` instead of the real parameter. Tokenize parameters while respecting quoted-string escapes before applying the filename precedence.</violation>
</file>
<file name="samples/client/petstore/typescript-fetch/builds/allOf-nullable/runtime.ts">
<violation number="1" location="samples/client/petstore/typescript-fetch/builds/allOf-nullable/runtime.ts:519">
P2: When a quoted `filename` contains a semicolon followed by `filename*=`, this regex interprets text inside the quoted value as a separate parameter and assigns the wrong download name. Tokenize parameters while respecting quoted-string and escape boundaries before applying `filename*` precedence.</violation>
</file>
<file name="samples/client/petstore/typescript-fetch/builds/oneOf/runtime.ts">
<violation number="1" location="samples/client/petstore/typescript-fetch/builds/oneOf/runtime.ts:519">
P2: When a quoted parameter or filename contains a semicolon followed by `filename*=` or `filename=`, this regex treats the text inside the quote as a real parameter and returns the wrong download name. Tokenize semicolon-delimited parameters while honoring quoted-string escapes before applying the `filename*`/`filename` precedence.</violation>
</file>
<file name="samples/client/petstore/typescript-fetch/tests/default/test/BlobApiResponse.ts">
<violation number="1" location="samples/client/petstore/typescript-fetch/tests/default/test/BlobApiResponse.ts:60">
P2: When these tests run on Node.js before 20, `BlobApiResponse.value()` intentionally returns a bare `Blob`, so these unconditional `file.name` assertions fail. Guard the filename assertions on `typeof File !== 'undefined'` or provide separate expectations for the Blob fallback.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| if (!value) { | ||
| return undefined; | ||
| } | ||
| const encoded = /(?:^|;)\s*filename\*\s*=\s*utf-8'[^']*'([^;]*)/i.exec(value); |
There was a problem hiding this comment.
P2: When another disposition parameter contains a quoted semicolon, encoded parses text inside that quoted value as a real filename* parameter. Parse Content-Disposition parameters while tracking quoted-string and escape state before applying filename* precedence.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/petstore/typescript-fetch/builds/enum/runtime.ts, line 519:
<comment>When another disposition parameter contains a quoted semicolon, `encoded` parses text inside that quoted value as a real `filename*` parameter. Parse Content-Disposition parameters while tracking quoted-string and escape state before applying `filename*` precedence.</comment>
<file context>
@@ -494,32 +494,41 @@ export class BlobApiResponse {
return undefined;
}
- const encoded = /filename\*=(?:UTF-8|utf-8)''([^;]+)/.exec(value);
+ const encoded = /(?:^|;)\s*filename\*\s*=\s*utf-8'[^']*'([^;]*)/i.exec(value);
if (encoded) {
try {
</file context>
| if (!value) { | ||
| return undefined; | ||
| } | ||
| const encoded = /(?:^|;)\s*filename\*\s*=\s*utf-8'[^']*'([^;]*)/i.exec(value); |
There was a problem hiding this comment.
P2: When another quoted parameter contains a semicolon followed by filename* or filename, this parser treats the embedded text as a real parameter and names the returned file incorrectly. Parse parameters while honoring quoted-string boundaries before selecting filename* or filename.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/petstore/typescript-fetch/builds/default/runtime.ts, line 519:
<comment>When another quoted parameter contains a semicolon followed by `filename*` or `filename`, this parser treats the embedded text as a real parameter and names the returned file incorrectly. Parse parameters while honoring quoted-string boundaries before selecting `filename*` or `filename`.</comment>
<file context>
@@ -494,32 +494,41 @@ export class BlobApiResponse {
return undefined;
}
- const encoded = /filename\*=(?:UTF-8|utf-8)''([^;]+)/.exec(value);
+ const encoded = /(?:^|;)\s*filename\*\s*=\s*utf-8'[^']*'([^;]*)/i.exec(value);
if (encoded) {
try {
</file context>
| if (!value) { | ||
| return undefined; | ||
| } | ||
| const encoded = /(?:^|;)\s*filename\*\s*=\s*utf-8'[^']*'([^;]*)/i.exec(value); |
There was a problem hiding this comment.
P2: When a quoted parameter contains ; filename*= text, parseContentDispositionFilename treats that text as a real parameter because the regex does not track quoted strings. Parse parameters while respecting quoted-string boundaries before applying the filename* and filename precedence.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/petstore/typescript-fetch/builds/allOf-readonly/runtime.ts, line 519:
<comment>When a quoted parameter contains `; filename*=` text, `parseContentDispositionFilename` treats that text as a real parameter because the regex does not track quoted strings. Parse parameters while respecting quoted-string boundaries before applying the `filename*` and `filename` precedence.</comment>
<file context>
@@ -494,32 +494,41 @@ export class BlobApiResponse {
return undefined;
}
- const encoded = /filename\*=(?:UTF-8|utf-8)''([^;]+)/.exec(value);
+ const encoded = /(?:^|;)\s*filename\*\s*=\s*utf-8'[^']*'([^;]*)/i.exec(value);
if (encoded) {
try {
</file context>
| // fall through to the plain form | ||
| } | ||
| } | ||
| const plain = /(?:^|;)\s*filename\s*=\s*(?:"((?:[^"\\]|\\.)*)"|([^;\s]+))/i.exec(value); |
There was a problem hiding this comment.
P2: When another disposition parameter contains a semicolon inside a quoted string, this regex extracts filename from that quoted value instead of the actual parameter. Parse parameters while honoring quoted-string escapes before looking for filename and filename*.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/petstore/typescript-fetch/builds/with-string-enums/runtime.ts, line 527:
<comment>When another disposition parameter contains a semicolon inside a quoted string, this regex extracts `filename` from that quoted value instead of the actual parameter. Parse parameters while honoring quoted-string escapes before looking for `filename` and `filename*`.</comment>
<file context>
@@ -494,32 +494,41 @@ export class BlobApiResponse {
}
- const plain = /filename="?([^";]+)"?/.exec(value);
- return plain ? plain[1].trim() : undefined;
+ const plain = /(?:^|;)\s*filename\s*=\s*(?:"((?:[^"\\]|\\.)*)"|([^;\s]+))/i.exec(value);
+ if (!plain) {
+ return undefined;
</file context>
| if (!value) { | ||
| return undefined; | ||
| } | ||
| const encoded = /(?:^|;)\s*filename\*\s*=\s*utf-8'[^']*'([^;]*)/i.exec(value); |
There was a problem hiding this comment.
P2: When a quoted filename contains ; filename*= text, this regex treats the text inside the quoted string as a real parameter and returns the wrong download name. Parse parameters with quote-aware boundaries before applying filename* precedence.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/petstore/typescript-fetch/builds/kebab-case/runtime.ts, line 519:
<comment>When a quoted filename contains `; filename*=` text, this regex treats the text inside the quoted string as a real parameter and returns the wrong download name. Parse parameters with quote-aware boundaries before applying `filename*` precedence.</comment>
<file context>
@@ -494,32 +494,41 @@ export class BlobApiResponse {
return undefined;
}
- const encoded = /filename\*=(?:UTF-8|utf-8)''([^;]+)/.exec(value);
+ const encoded = /(?:^|;)\s*filename\*\s*=\s*utf-8'[^']*'([^;]*)/i.exec(value);
if (encoded) {
try {
</file context>
| if (!value) { | ||
| return undefined; | ||
| } | ||
| const encoded = /(?:^|;)\s*filename\*\s*=\s*utf-8'[^']*'([^;]*)/i.exec(value); |
There was a problem hiding this comment.
P2: When another disposition parameter contains a quoted semicolon, this regex treats the semicolon inside the quoted value as a parameter boundary and can select an embedded filename* instead of the real parameter. Tokenize parameters while respecting quoted-string escapes before applying the filename precedence.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/petstore/typescript-fetch/builds/snakecase-discriminator/runtime.ts, line 519:
<comment>When another disposition parameter contains a quoted semicolon, this regex treats the semicolon inside the quoted value as a parameter boundary and can select an embedded `filename*` instead of the real parameter. Tokenize parameters while respecting quoted-string escapes before applying the filename precedence.</comment>
<file context>
@@ -494,32 +494,41 @@ export class BlobApiResponse {
return undefined;
}
- const encoded = /filename\*=(?:UTF-8|utf-8)''([^;]+)/.exec(value);
+ const encoded = /(?:^|;)\s*filename\*\s*=\s*utf-8'[^']*'([^;]*)/i.exec(value);
if (encoded) {
try {
</file context>
| if (!value) { | ||
| return undefined; | ||
| } | ||
| const encoded = /(?:^|;)\s*filename\*\s*=\s*utf-8'[^']*'([^;]*)/i.exec(value); |
There was a problem hiding this comment.
P2: When a quoted filename contains a semicolon followed by filename*=, this regex interprets text inside the quoted value as a separate parameter and assigns the wrong download name. Tokenize parameters while respecting quoted-string and escape boundaries before applying filename* precedence.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/petstore/typescript-fetch/builds/allOf-nullable/runtime.ts, line 519:
<comment>When a quoted `filename` contains a semicolon followed by `filename*=`, this regex interprets text inside the quoted value as a separate parameter and assigns the wrong download name. Tokenize parameters while respecting quoted-string and escape boundaries before applying `filename*` precedence.</comment>
<file context>
@@ -494,32 +494,41 @@ export class BlobApiResponse {
return undefined;
}
- const encoded = /filename\*=(?:UTF-8|utf-8)''([^;]+)/.exec(value);
+ const encoded = /(?:^|;)\s*filename\*\s*=\s*utf-8'[^']*'([^;]*)/i.exec(value);
if (encoded) {
try {
</file context>
| if (!value) { | ||
| return undefined; | ||
| } | ||
| const encoded = /(?:^|;)\s*filename\*\s*=\s*utf-8'[^']*'([^;]*)/i.exec(value); |
There was a problem hiding this comment.
P2: When a quoted parameter or filename contains a semicolon followed by filename*= or filename=, this regex treats the text inside the quote as a real parameter and returns the wrong download name. Tokenize semicolon-delimited parameters while honoring quoted-string escapes before applying the filename*/filename precedence.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/petstore/typescript-fetch/builds/oneOf/runtime.ts, line 519:
<comment>When a quoted parameter or filename contains a semicolon followed by `filename*=` or `filename=`, this regex treats the text inside the quote as a real parameter and returns the wrong download name. Tokenize semicolon-delimited parameters while honoring quoted-string escapes before applying the `filename*`/`filename` precedence.</comment>
<file context>
@@ -494,32 +494,41 @@ export class BlobApiResponse {
return undefined;
}
- const encoded = /filename\*=(?:UTF-8|utf-8)''([^;]+)/.exec(value);
+ const encoded = /(?:^|;)\s*filename\*\s*=\s*utf-8'[^']*'([^;]*)/i.exec(value);
if (encoded) {
try {
</file context>
| // fall through to the plain form | ||
| } | ||
| } | ||
| const plain = /(?:^|;)\s*filename\s*=\s*(?:"((?:[^"\\]|\\.)*)"|([^;\s]+))/i.exec(value); |
There was a problem hiding this comment.
P2: When a quoted parameter contains ; filename=..., these regexes treat the semicolon as a parameter boundary and return a filename from inside the quoted value. Parse parameters while honoring quoted-string and escape state before matching filename or filename*.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/petstore/typescript-fetch/builds/default/runtime.ts, line 527:
<comment>When a quoted parameter contains `; filename=...`, these regexes treat the semicolon as a parameter boundary and return a filename from inside the quoted value. Parse parameters while honoring quoted-string and escape state before matching `filename` or `filename*`.</comment>
<file context>
@@ -494,32 +494,41 @@ export class BlobApiResponse {
}
- const plain = /filename="?([^";]+)"?/.exec(value);
- return plain ? plain[1].trim() : undefined;
+ const plain = /(?:^|;)\s*filename\s*=\s*(?:"((?:[^"\\]|\\.)*)"|([^;\s]+))/i.exec(value);
+ if (!plain) {
+ return undefined;
</file context>
| }); | ||
| const file = await new BlobApiResponse(response).value(); | ||
| expect(file).to.be.an.instanceOf(Blob); | ||
| expect(file.name).to.equal('named.txt'); |
There was a problem hiding this comment.
P2: When these tests run on Node.js before 20, BlobApiResponse.value() intentionally returns a bare Blob, so these unconditional file.name assertions fail. Guard the filename assertions on typeof File !== 'undefined' or provide separate expectations for the Blob fallback.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/petstore/typescript-fetch/tests/default/test/BlobApiResponse.ts, line 60:
<comment>When these tests run on Node.js before 20, `BlobApiResponse.value()` intentionally returns a bare `Blob`, so these unconditional `file.name` assertions fail. Guard the filename assertions on `typeof File !== 'undefined'` or provide separate expectations for the Blob fallback.</comment>
<file context>
@@ -0,0 +1,83 @@
+ });
+ const file = await new BlobApiResponse(response).value();
+ expect(file).to.be.an.instanceOf(Blob);
+ expect(file.name).to.equal('named.txt');
+ expect(file.type).to.equal('text/plain');
+ expect(await file.text()).to.equal('content');
</file context>
Implements #24956
Changes
For a file response (
isResponseFile),BlobApiResponse.value()returned an anonymousBlob: the name the serveradvertises in
Content-Dispositionwas dropped, and every client wanting to save a download under its real name hadto call thexxxRaw()variant and parse the header itself. Every other runtime of this repository already names the download for the user (typescriptgenerator'sgetBodyAsFile(), JavaprepareDownloadFile, Python__deserialize_file, C#ClientUtils).BlobApiResponse.value()now returns aFile:Content-Disposition, RFC 5987filename*=UTF-8''…form first, plainfilename=otherwise, empty name when the header is absent;type(the responseContent-Type).The parsing lives in an exported
parseContentDispositionFilename(headers: Headers).Backward compatible:
FileextendsBlob, so the generated methods still satisfy their declaredPromise<Blob>return type and existing callers keep working; callers that want the name readfile.name.Fileis a global in browsers and Node.js 20+ (the CI matrix for these samples runs Node 20).Files:
typescript-fetch/runtime.mustache, and the 24 regeneratedruntime.tssamples (identical diff in each).How to validate
Behaviour, with the regenerated
samples/client/petstore/typescript-fetch/builds/default/runtime.tstranspiled and run on Node 22:parseContentDispositionFilenameonfilename="invoice.pdf",filename=plain.csv,filename*=UTF-8''facture%20%C3%A9t%C3%A9.pdf, both forms together (encoded wins),inlineand no header;BlobApiResponse.value()on aResponsewithContent-Type: application/pdfandContent-Disposition: attachment; filename="doc.pdf"gives aFilenameddoc.pdfof typeapplication/pdfwith the original body, and an unnamed response gives an empty name.tsc --strict --lib dompasses on the regenerated runtime.PR checklist
Commit all changed files.
This is important, as CI jobs will verify all generator outputs of your HEAD commit as it would merge with master.
These must match the expectations made by your contribution.
You may regenerate an individual generator by passing the relevant config(s) as an argument to the script, for example
./bin/generate-samples.sh bin/configs/java*.IMPORTANT: Do NOT purge/delete any folders/files (e.g. tests) when regenerating the samples as manually written tests may be removed.
@TiFu (2017/07) @taxpon (2017/07) @sebastianhaas (2017/07) @kenisteward (2017/07) @Vrolijkx (2017/09) @macjohnny (2018/01) @topce (2018/10) @akehir (2019/07) @petejohansonxo (2019/11) @amakhrov (2020/02) @davidgamero (2022/03) @mkusaka (2022/04) @joscha (2024/10) @KannaKim (2026/07)
Summary by cubic
Makes
BlobApiResponse.value()return aFilenamed from theContent-Dispositionheader instead of an anonymousBlob, so downloaded files keep the server-advertised name. Implements #24956.Backward compatibility
FileextendsBlob, so existing callers keep working and can read the name fromfile.name.File(Node.js before 20) still receive the bareBlob.parseContentDispositionFilename(headers)follows RFC 6266:filename*first, thenfilename, with case-insensitive parameter matching and quoted-string support.BlobApiResponseto the default test project.Written for commit 7537797. Summary will update on new commits.