此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.greasyfork.org/scripts/517244/1483543/parse-multipart-data-umd.js
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.parseMultipartData = {}));
})(this, (function (exports) { 'use strict';
function process(part) {
// will transform this object:
// { header: 'Content-Disposition: form-data; name="uploads[]"; filename="A.txt"',
// info: 'Content-Type: text/plain',
// part: 'AAAABBBB' }
// into this one:
// { filename: 'A.txt', type: 'text/plain', data: <Buffer 41 41 41 41 42 42 42 42> }
const obj = function (str) {
const k = str.split('=');
const a = k[0].trim();
const b = JSON.parse(k[1].trim());
const o = {};
Object.defineProperty(o, a, {
value: b,
writable: true,
enumerable: true,
configurable: true
});
return o;
};
const header = part.contentDispositionHeader.split(';');
const filenameData = header[2];
let input = {};
if (filenameData) {
input = obj(filenameData);
const contentType = part.contentTypeHeader.split(':')[1].trim();
Object.defineProperty(input, 'type', {
value: contentType,
writable: true,
enumerable: true,
configurable: true
});
}
// always process the name field
Object.defineProperty(input, 'name', {
value: header[1].split('=')[1].replace(/"/g, ''),
writable: true,
enumerable: true,
configurable: true
});
Object.defineProperty(input, 'data', {
value: new Uint8Array(part.part),
writable: true,
enumerable: true,
configurable: true
});
return input;
}
const ParsingState = (ParsingState => {
ParsingState[(ParsingState.INIT = 0)] = 'INIT';
ParsingState[(ParsingState.READING_HEADERS = 1)] = 'READING_HEADERS';
ParsingState[(ParsingState.READING_DATA = 2)] = 'READING_DATA';
ParsingState[(ParsingState.READING_PART_SEPARATOR = 3)] = 'READING_PART_SEPARATOR';
return ParsingState;
})({});
function parse(multipartBodyBuffer, boundary) {
let lastline = '';
let contentDispositionHeader = '';
let contentTypeHeader = '';
let state = ParsingState.INIT;
let buffer = [];
const allParts = [];
let currentPartHeaders = [];
for (let i = 0; i < multipartBodyBuffer.length; i++) {
const oneByte = multipartBodyBuffer[i];
const prevByte = i > 0 ? multipartBodyBuffer[i - 1] : null;
// 0x0a => \n
// 0x0d => \r
const newLineDetected = oneByte === 0x0a && prevByte === 0x0d;
const newLineChar = oneByte === 0x0a || oneByte === 0x0d;
if (!newLineChar) lastline += String.fromCharCode(oneByte);
if (ParsingState.INIT === state && newLineDetected) {
// searching for boundary
if ('--' + boundary === lastline) {
state = ParsingState.READING_HEADERS; // found boundary. start reading headers
}
lastline = '';
} else if (ParsingState.READING_HEADERS === state && newLineDetected) {
// parsing headers. Headers are separated by an empty line from the content. Stop reading headers when the line is empty
if (lastline.length) {
currentPartHeaders.push(lastline);
} else {
// found empty line. search for the headers we want and set the values
for (const h of currentPartHeaders) {
if (h.toLowerCase().startsWith('content-disposition:')) {
contentDispositionHeader = h;
} else if (h.toLowerCase().startsWith('content-type:')) {
contentTypeHeader = h;
}
}
state = ParsingState.READING_DATA;
buffer = [];
}
lastline = '';
} else if (ParsingState.READING_DATA === state) {
// parsing data
if (lastline.length > boundary.length + 4) {
lastline = ''; // mem save
}
if ('--' + boundary === lastline) {
const j = buffer.length - lastline.length;
const part = buffer.slice(0, j - 1);
allParts.push(process({
contentDispositionHeader,
contentTypeHeader,
part
}));
buffer = [];
currentPartHeaders = [];
lastline = '';
state = ParsingState.READING_PART_SEPARATOR;
contentDispositionHeader = '';
contentTypeHeader = '';
} else {
buffer.push(oneByte);
}
if (newLineDetected) {
lastline = '';
}
} else if (ParsingState.READING_PART_SEPARATOR === state) {
if (newLineDetected) {
state = ParsingState.READING_HEADERS;
}
}
}
return allParts;
}
// read the boundary from the content-type header sent by the http client
// this value may be similar to:
// 'multipart/form-data; boundary=----WebKitFormBoundaryvm5A9tzU1ONaGP5B',
function getBoundary(header) {
const items = header.split(';');
if (items) {
for (let i = 0; i < items.length; i++) {
const item = new String(items[i]).trim();
if (item.indexOf('boundary') >= 0) {
const k = item.split('=');
return new String(k[1]).trim().replace(/^["']|["']$/g, '');
}
}
}
return '';
}
exports.getBoundary = getBoundary;
exports.parse = parse;
}));