import { isBlob } from './is' import { streamToIterator } from './utils' const carriage = '\r\n' const dashes = '--' const carriageLength = 2 function escape(str: string) { return str.replace(/"/g, '\\"') } function getFooter(boundary: string) { return `${dashes}${boundary}${dashes}${carriage.repeat(2)}` } function getHeader(boundary: string, name: string, field: FormDataEntryValue) { let header = '' header += `${dashes}${boundary}${carriage}` header += `Content-Disposition: form-data; name="${escape(name)}"` if (isBlob(field)) { header += `; filename="${escape(field.name)}"${carriage}` header += `Content-Type: ${field.type || 'application/octet-stream'}` } return `${header}${carriage.repeat(2)}` } export function getBoundary() { const array = new Uint8Array(32) crypto.getRandomValues(array) let str = '' for (var i = 0; i < array.length; i++) { str += array[i].toString(16).padStart(2, '0') } return str } export async function* formDataIterator( form: FormData, boundary: string ): AsyncIterableIterator { const encoder = new TextEncoder() for (const [name, value] of form) { yield encoder.encode(getHeader(boundary, name, value)) if (isBlob(value)) { const stream: ReadableStream = value.stream() yield* streamToIterator(stream) } else { yield encoder.encode(value) } yield encoder.encode(carriage) } yield encoder.encode(getFooter(boundary)) } export function getFormDataLength(form: FormData, boundary: string) { let length = 0 for (const [name, value] of form) { length += Buffer.byteLength(getHeader(boundary, name, value)) length += isBlob(value) ? value.size : Buffer.byteLength(String(value)) length += carriageLength } length += Buffer.byteLength(getFooter(boundary)) return length }