Skip to content

[typescript-fetch] name downloaded files from Content-Disposition, BlobApiResponse returns a File - #24957

Open
AntoineDuComptoirDesPharmacies wants to merge 2 commits into
OpenAPITools:masterfrom
LeComptoirDesPharmacies:feature/24956
Open

AntoineDuComptoirDesPharmacies wants to merge 2 commits into
OpenAPITools:masterfrom
LeComptoirDesPharmacies:feature/24956

Conversation

@AntoineDuComptoirDesPharmacies

@AntoineDuComptoirDesPharmacies AntoineDuComptoirDesPharmacies commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Implements #24956

Changes

For a file response (isResponseFile), BlobApiResponse.value() returned an anonymous Blob: the name the server
advertises in Content-Disposition was dropped, and every client wanting to save a download under its real name hadto call the xxxRaw() variant and parse the header itself. Every other runtime of this repository already names the download for the user (typescript generator's getBodyAsFile(), Java prepareDownloadFile, Python __deserialize_file, C# ClientUtils).

BlobApiResponse.value() now returns a File:

  • named after Content-Disposition, RFC 5987 filename*=UTF-8''… form first, plain filename= otherwise, empty name when the header is absent;
  • typed with the blob's type (the response Content-Type).

The parsing lives in an exported parseContentDispositionFilename(headers: Headers).

Backward compatible: File extends Blob, so the generated methods still satisfy their declared Promise<Blob> return type and existing callers keep working; callers that want the name read file.name. File is 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 regenerated runtime.ts samples (identical diff in each).

How to validate

./bin/generate-samples.sh bin/configs/typescript-fetch*.yaml   # no diff beyond the committed runtime.ts files

Behaviour, with the regenerated samples/client/petstore/typescript-fetch/builds/default/runtime.ts transpiled and run on Node 22: parseContentDispositionFilename on filename="invoice.pdf", filename=plain.csv, filename*=UTF-8''facture%20%C3%A9t%C3%A9.pdf, both forms together (encoded wins), inline and no header; BlobApiResponse.value() on a Response with Content-Type: application/pdf and Content-Disposition: attachment; filename="doc.pdf" gives a File named doc.pdf of type application/pdf with the original body, and an unnamed response gives an empty name. tsc --strict --lib dom passes on the regenerated runtime.

PR checklist

  • [ X ] Read the contribution guidelines.
  • [ X ] Run the following to build the project and update samples:
    ./mvnw clean package || exit
    ./bin/generate-samples.sh ./bin/configs/*.yaml || exit
    ./bin/utils/export_docs_generators.sh || exit
    
    (For Windows users, please run the script in WSL)
    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.
  • [ X ] If your PR is targeting a particular programming language, @mention the technical committee members, so they are more likely to review the pull request.

@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 a File named from the Content-Disposition header instead of an anonymous Blob, so downloaded files keep the server-advertised name. Implements #24956.

Backward compatibility

  • File extends Blob, so existing callers keep working and can read the name from file.name.
  • Runtimes without a global File (Node.js before 20) still receive the bare Blob.
  • The exported parseContentDispositionFilename(headers) follows RFC 6266: filename* first, then filename, with case-insensitive parameter matching and quoted-string support.
  • Adds unit tests for the parser and BlobApiResponse to the default test project.

Written for commit 7537797. Summary will update on new commits.

Review in cubic

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 25 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread samples/client/petstore/typescript-fetch/builds/with-interfaces/runtime.ts Outdated
Comment thread samples/client/petstore/typescript-fetch/builds/multiple-parameters/runtime.ts Outdated
Comment thread samples/client/petstore/typescript-fetch/builds/sagas-and-records/src/runtime.ts Outdated
Comment thread samples/client/others/typescript-fetch/self-import-issue/runtime.ts Outdated
…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
@AntoineDuComptoirDesPharmacies

Copy link
Copy Markdown
Contributor Author

Thanks cubic-dev-ai, the 40 findings boil down to 4 distinct points repeated on each generated runtime.ts; all four are addressed in the commit I just did :

  • File missing on Node.js < 20: BlobApiResponse.value() now keeps returning the bare Blob when File is not a global, so those runtimes behave exactly as before the change.
  • Quoted-string filenames: filename="report;2024.pdf" and escaped quotes are parsed as a quoted-string instead of being cut at the first ;.
  • Case-insensitive parameter names: Filename=, FILENAME*=utf-8''… are accepted.
  • Parameter boundary: filename / filename* only match at the start of a parameter, so xfilename= or a filename*= fragment inside another parameter's value are ignored.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant