happy-dom
Version:
Happy DOM is a JavaScript implementation of a web browser without its graphical user interface. It includes many web standards from WHATWG DOM and HTML.
172 lines • 6.63 kB
JavaScript
import DOMException from '../../exception/DOMException.js';
import DOMExceptionNameEnum from '../../exception/DOMExceptionNameEnum.js';
import File from '../../file/File.js';
import FormData from '../../form-data/FormData.js';
var MultiparParserStateEnum;
(function (MultiparParserStateEnum) {
MultiparParserStateEnum[MultiparParserStateEnum["boundary"] = 0] = "boundary";
MultiparParserStateEnum[MultiparParserStateEnum["headerStart"] = 2] = "headerStart";
MultiparParserStateEnum[MultiparParserStateEnum["header"] = 3] = "header";
MultiparParserStateEnum[MultiparParserStateEnum["data"] = 5] = "data";
})(MultiparParserStateEnum || (MultiparParserStateEnum = {}));
const CHARACTER_CODE = {
lf: 10,
cr: 13
};
/**
* Multipart reader.
*
* Based on:
* https://github.com/node-fetch/node-fetch/blob/main/src/utils/multipart-parser.js (MIT)
*/
export default class MultipartReader {
/**
* Constructor.
*
* @param formData Form data.
* @param boundary Boundary.
*/
constructor(boundary) {
this.formData = new FormData();
this.boundaryIndex = 0;
this.state = MultiparParserStateEnum.boundary;
this.data = {
contentDisposition: null,
value: [],
contentType: null,
header: ''
};
const boundaryHeader = `--${boundary}`;
this.boundary = new Uint8Array(boundaryHeader.length);
for (let i = 0, max = boundaryHeader.length; i < max; i++) {
this.boundary[i] = boundaryHeader.charCodeAt(i);
}
}
/**
* Appends data.
*
* @param data Data.
*/
write(data) {
let char;
let nextChar;
for (let i = 0, max = data.length; i < max; i++) {
char = data[i];
nextChar = data[i + 1];
switch (this.state) {
case MultiparParserStateEnum.boundary:
if (char === this.boundary[this.boundaryIndex]) {
this.boundaryIndex++;
}
else {
this.boundaryIndex = 0;
}
if (this.boundaryIndex === this.boundary.length) {
this.state = MultiparParserStateEnum.headerStart;
this.boundaryIndex = 0;
}
break;
case MultiparParserStateEnum.headerStart:
if (nextChar !== CHARACTER_CODE.cr && nextChar !== CHARACTER_CODE.lf) {
this.data.header = '';
this.state =
data[i - 2] === CHARACTER_CODE.lf
? MultiparParserStateEnum.data
: MultiparParserStateEnum.header;
}
break;
case MultiparParserStateEnum.header:
if (char === CHARACTER_CODE.cr) {
if (this.data.header) {
const headerParts = this.data.header.split(':');
const headerName = headerParts[0].toLowerCase();
const headerValue = headerParts[1].trim();
switch (headerName) {
case 'content-disposition':
this.data.contentDisposition = this.getContentDisposition(headerValue);
break;
case 'content-type':
this.data.contentType = headerValue;
break;
}
}
this.state = MultiparParserStateEnum.headerStart;
}
else {
this.data.header += String.fromCharCode(char);
}
break;
case MultiparParserStateEnum.data:
if (char === this.boundary[this.boundaryIndex]) {
this.boundaryIndex++;
}
else {
this.boundaryIndex = 0;
}
if (this.boundaryIndex === this.boundary.length) {
this.state = MultiparParserStateEnum.headerStart;
if (this.data.value.length) {
this.appendFormData(this.data.contentDisposition.name, Buffer.from(this.data.value.slice(0, -(this.boundary.length + 1))), this.data.contentDisposition.filename, this.data.contentType);
this.data.value = [];
this.data.contentDisposition = null;
this.data.contentType = null;
}
this.boundaryIndex = 0;
}
else {
this.data.value.push(char);
}
break;
}
}
}
/**
* Ends the stream.
*
* @returns Form data.
*/
end() {
if (this.state !== MultiparParserStateEnum.data) {
throw new DOMException(`Unexpected end of multipart stream. Expected state to be "${MultiparParserStateEnum.data}" but got "${this.state}".`, DOMExceptionNameEnum.invalidStateError);
}
this.appendFormData(this.data.contentDisposition.name, Buffer.from(this.data.value.slice(0, -2)), this.data.contentDisposition.filename, this.data.contentType);
return this.formData;
}
/**
* Appends data.
*
* @param key Key.
* @param value value.
* @param filename Filename.
* @param type Type.
*/
appendFormData(key, value, filename, type) {
if (!value.length) {
return;
}
if (filename) {
this.formData.append(key, new File([value], filename, {
type
}));
}
else {
this.formData.append(key, value.toString());
}
}
/**
* Returns content disposition.
*
* @param headerValue Header value.
* @returns Content disposition.
*/
getContentDisposition(headerValue) {
const regex = /([a-z]+) *= *"([^"]+)"/g;
const contentDisposition = {};
let match;
while ((match = regex.exec(headerValue))) {
contentDisposition[match[1]] = match[2];
}
return contentDisposition;
}
}
//# sourceMappingURL=MultipartReader.js.map