Multipart Form-Data Body Builder
Build multipart/form-data request bodies with custom boundaries, text fields and binary file parts without a browser.
Code
const encoder = new TextEncoder();
export interface MultipartFile {
field: string;
filename: string;
contentType: string;
data: Uint8Array;
}
export interface MultipartBody {
body: Uint8Array;
boundary: string;
contentType: string;
}
export class MultipartFormDataBuilder {
private readonly boundary: string;
private readonly chunks: Uint8Array[] = [];
constructor(boundary?: string) {
this.boundary = boundary ?? "----MultipartBoundary"
+ Math.random().toString(36).slice(2)
+ Date.now().toString(36);
if (this.boundary.length < 1 || this.boundary.length > 70) {
throw new RangeError("boundary length must be between 1 and 70");
}
if (/[\r\n"]/.test(this.boundary)) {
throw new TypeError("boundary must not contain quotes or newlines");
}
}
field(name: string, value: string): this {
this.chunks.push(encoder.encode(
"--" + this.boundary + "\r\n"
+ "Content-Disposition: form-data; name=\"" + escapeName(name) + "\"\r\n\r\n"
+ value + "\r\n",
));
return this;
}
file(file: MultipartFile): this {
if (!/^[a-z0-9!#$&^_.+-]+\/[a-z0-9!#$&^_.+*-]+$/i.test(file.contentType)) {
throw new TypeError("invalid file contentType");
}
this.chunks.push(encoder.encode(
"--" + this.boundary + "\r\n"
+ "Content-Disposition: form-data; name=\"" + escapeName(file.field)
+ "\"; filename=\"" + escapeName(file.filename) + "\"\r\n"
+ "Content-Type: " + file.contentType + "\r\n\r\n",
));
this.chunks.push(file.data);
this.chunks.push(encoder.encode("\r\n"));
return this;
}
build(): MultipartBody {
const closing = encoder.encode("--" + this.boundary + "--\r\n");
const length = this.chunks.reduce((total, chunk) => total + chunk.length, closing.length);
const body = new Uint8Array(length);
let offset = 0;
for (const chunk of this.chunks) {
body.set(chunk, offset);
offset += chunk.length;
}
body.set(closing, offset);
return {
body,
boundary: this.boundary,
contentType: "multipart/form-data; boundary=" + this.boundary,
};
}
}
function escapeName(value: string): string {
return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/[\r\n]/g, " ");
}Report a problem with this exampleOpens a pre-filled issue on GitHub.