b5f3067a6a
Initial setup of project dependencies including axios, express, dotenv, and other supporting packages. Added configuration files and documentation for project setup and usage.
113 lines
2.9 KiB
JavaScript
113 lines
2.9 KiB
JavaScript
import util from 'util';
|
|
import {Readable} from 'stream';
|
|
import utils from "../utils.js";
|
|
import readBlob from "./readBlob.js";
|
|
import platform from "../platform/index.js";
|
|
|
|
const BOUNDARY_ALPHABET = platform.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 + '-' + platform.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);
|
|
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;
|