Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: fix
packages:
- "@typespec/http-server-js"
---

Fixed an issue with handling of `bytes` response bodies with content-types other than "application/json" that would cause http-server-js to emit an invalid attempt to call `Uint8Array.toJsonObject`.
7 changes: 7 additions & 0 deletions .chronus/changes/hsjs-bytes-body-payload-2026-4-26-12-51-4.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: feature
packages:
- "@typespec/http-server-js"
---

Added support for `Http.File` response bodies. File bodies are treated as _raw_ bytes, and the `filename` is represented in the `Content-Disposition` header.
24 changes: 24 additions & 0 deletions packages/http-server-js/generated-defs/helpers/header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@ const lines = [
" params: { [k: string]: string };",
"}",
"",
"function replaceInvalidHttpHeaderCharacters(value: string): string {",
" let sanitized = \"\";",
"",
" for (const char of value) {",
" const codePoint = char.codePointAt(0)!;",
" sanitized += codePoint < 0x20 || codePoint === 0x7f ? \" \" : char;",
" }",
"",
" return sanitized;",
"}",
"",
"/**",
" * Parses a header value that may contain additional parameters (e.g. `text/html; charset=utf-8`).",
" * @param headerValueText - the text of the header value to parse",
Expand Down Expand Up @@ -63,6 +74,19 @@ const lines = [
" };",
"}",
"",
"/**",
" * Formats an attachment Content-Disposition header value for a filename.",
" *",
" * Control characters are replaced so the header value stays valid for Node's",
" * response.setHeader validation, and quotes/backslashes are escaped inside the",
" * quoted-string filename parameter.",
" */",
"export function formatContentDispositionAttachment(filename: string): string {",
" const sanitized = replaceInvalidHttpHeaderCharacters(filename);",
" const escaped = sanitized.replace(/[\"\\\\]/g, \"\\\\$&\");",
" return `attachment; filename=\"${escaped}\"`;",
"}",
"",
];

export async function createModule(parent: Module): Promise<Module> {
Expand Down
24 changes: 24 additions & 0 deletions packages/http-server-js/src/helpers/header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ export interface HeaderValueParameters {
params: { [k: string]: string };
}

function replaceInvalidHttpHeaderCharacters(value: string): string {
let sanitized = "";

for (const char of value) {
const codePoint = char.codePointAt(0)!;
sanitized += codePoint < 0x20 || codePoint === 0x7f ? " " : char;
}

return sanitized;
}

/**
* Parses a header value that may contain additional parameters (e.g. `text/html; charset=utf-8`).
* @param headerValueText - the text of the header value to parse
Expand Down Expand Up @@ -53,3 +64,16 @@ export function parseHeaderValueParameters<Header extends string | undefined>(
params,
};
}

/**
* Formats an attachment Content-Disposition header value for a filename.
*
* Control characters are replaced so the header value stays valid for Node's
* response.setHeader validation, and quotes/backslashes are escaped inside the
* quoted-string filename parameter.
*/
export function formatContentDispositionAttachment(filename: string): string {
const sanitized = replaceInvalidHttpHeaderCharacters(filename);
const escaped = sanitized.replace(/["\\]/g, "\\$&");
return `attachment; filename="${escaped}"`;
}
Loading
Loading