123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111 |
- import util from 'util';
- import {Readable} from 'stream';
- import utils from "../utils.js";
- import readBlob from "./readBlob.js";
- const BOUNDARY_ALPHABET = utils.ALPHABET.ALPHA_DIGIT + '-_';
- const textEncoder = typeof TextEncoder === 'function' ? new TextEncoder() : new util.TextEncoder();
- const CRLF = '\r\n';
- const CRLF_BYTES = textEncoder.encode(CRLF);
- const CRLF_BYTES_COUNT = 2;
- class FormDataPart {
- constructor(name, value) {
- const {escapeName} = this.constructor;
- const isStringValue = utils.isString(value);
- let headers = `Content-Disposition: form-data; name="${escapeName(name)}"${
- !isStringValue && value.name ? `; filename="${escapeName(value.name)}"` : ''
- }${CRLF}`;
- if (isStringValue) {
- value = textEncoder.encode(String(value).replace(/\r?\n|\r\n?/g, CRLF));
- } else {
- headers += `Content-Type: ${value.type || "application/octet-stream"}${CRLF}`
- }
- this.headers = textEncoder.encode(headers + CRLF);
- this.contentLength = isStringValue ? value.byteLength : value.size;
- this.size = this.headers.byteLength + this.contentLength + CRLF_BYTES_COUNT;
- this.name = name;
- this.value = value;
- }
- async *encode(){
- yield this.headers;
- const {value} = this;
- if(utils.isTypedArray(value)) {
- yield value;
- } else {
- yield* readBlob(value);
- }
- yield CRLF_BYTES;
- }
- static escapeName(name) {
- return String(name).replace(/[\r\n"]/g, (match) => ({
- '\r' : '%0D',
- '\n' : '%0A',
- '"' : '%22',
- }[match]));
- }
- }
- const formDataToStream = (form, headersHandler, options) => {
- const {
- tag = 'form-data-boundary',
- size = 25,
- boundary = tag + '-' + utils.generateString(size, BOUNDARY_ALPHABET)
- } = options || {};
- if(!utils.isFormData(form)) {
- throw TypeError('FormData instance required');
- }
- if (boundary.length < 1 || boundary.length > 70) {
- throw Error('boundary must be 10-70 characters long')
- }
- const boundaryBytes = textEncoder.encode('--' + boundary + CRLF);
- const footerBytes = textEncoder.encode('--' + boundary + '--' + CRLF + CRLF);
- let contentLength = footerBytes.byteLength;
- const parts = Array.from(form.entries()).map(([name, value]) => {
- const part = new FormDataPart(name, value);
- contentLength += part.size;
- return part;
- });
- contentLength += boundaryBytes.byteLength * parts.length;
- contentLength = utils.toFiniteNumber(contentLength);
- const computedHeaders = {
- 'Content-Type': `multipart/form-data; boundary=${boundary}`
- }
- if (Number.isFinite(contentLength)) {
- computedHeaders['Content-Length'] = contentLength;
- }
- headersHandler && headersHandler(computedHeaders);
- return Readable.from((async function *() {
- for(const part of parts) {
- yield boundaryBytes;
- yield* part.encode();
- }
- yield footerBytes;
- })());
- };
- export default formDataToStream;
|