Files
klz-cables.com/.pnpm-store/v10/files/7d/3f5febb7ce54a26799a9aeacb0d4307fa49158db33444c57d3b7d2bfec19c3005a6b978770b34c7854ae46819f4120a48c3dc88c4753a391ada6bc2982092a
Marc Mintel 5397309103
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 20s
Build & Deploy / 🧪 QA (push) Failing after 34s
Build & Deploy / 🏗️ Build (push) Has started running
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Smoke Test (push) Has been cancelled
Build & Deploy / ⚡ Lighthouse (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
fix(products): fix breadcrumbs and product filtering (backport from main)
2026-02-24 16:04:21 +01:00

162 lines
6.3 KiB
Plaintext

import Busboy from 'busboy';
import { status as httpStatus } from 'http-status';
import { APIError } from '../../errors/APIError.js';
import { fileFactory } from './fileFactory.js';
import { memHandler, tempFileHandler } from './handlers.js';
import { processNested } from './processNested.js';
import { createUploadTimer } from './uploadTimer.js';
import { buildFields, debugLog, isFunc, parseFileName } from './utilities.js';
const waitFlushProperty = Symbol('wait flush property symbol');
export const processMultipart = async ({ options, request })=>{
let parsingRequest = true;
let shouldAbortProccessing = false;
let fileCount = 0;
let filesCompleted = 0;
let allFilesHaveResolved;
let failedResolvingFiles;
const allFilesComplete = new Promise((res, rej)=>{
allFilesHaveResolved = res;
failedResolvingFiles = rej;
});
const result = {
fields: undefined,
files: undefined
};
const headersObject = {};
request.headers.forEach((value, name)=>{
headersObject[name] = value;
});
const reader = request.body?.getReader();
const busboy = Busboy({
...options,
headers: headersObject
});
function abortAndDestroyFile(file, err) {
file.destroy();
shouldAbortProccessing = true;
failedResolvingFiles(err);
}
// Build multipart req.body fields
busboy.on('field', (field, val)=>{
result.fields = buildFields(result.fields, field, val);
});
// Build req.files fields
busboy.on('file', (field, file, info)=>{
fileCount += 1;
// Parse file name(cutting huge names, decoding, etc..).
const { encoding, filename: name, mimeType: mime } = info;
const filename = parseFileName(options, name);
const inferredMimeType = filename && filename.endsWith('.glb') && 'model/gltf-binary' || filename && filename.endsWith('.gltf') && 'model/gltf+json' || mime;
// Define methods and handlers for upload process.
const { cleanup, complete, dataHandler, getFilePath, getFileSize, getHash, getWritePromise } = options.useTempFiles ? tempFileHandler(options, field, filename) // Upload into temporary file.
: memHandler(options, field, filename) // Upload into RAM.
;
const writePromise = options.useTempFiles ? getWritePromise().catch(()=>{
busboy.end();
cleanup();
}) : getWritePromise();
const uploadTimer = createUploadTimer(options.uploadTimeout, ()=>{
return abortAndDestroyFile(file, new APIError(`Upload timeout for ${field}->${filename}, bytes:${getFileSize()}`));
});
file.on('limit', ()=>{
debugLog(options, `Size limit reached for ${field}->${filename}, bytes:${getFileSize()}`);
uploadTimer.clear();
if (isFunc(options.limitHandler)) {
options.limitHandler({
request,
size: getFileSize()
});
}
// Return error and cleanup files if abortOnLimit set.
if (options.abortOnLimit) {
debugLog(options, `Upload file size limit reached ${field}->${filename}.`);
cleanup();
abortAndDestroyFile(file, new APIError(options.responseOnLimit, httpStatus.REQUEST_ENTITY_TOO_LARGE, {
size: getFileSize()
}));
}
});
file.on('data', (data)=>{
uploadTimer.set();
dataHandler(data);
});
file.on('end', ()=>{
const size = getFileSize();
debugLog(options, `Upload finished ${field}->${filename}, bytes:${size}`);
uploadTimer.clear();
if (!name && size === 0) {
fileCount -= 1;
if (options.useTempFiles) {
cleanup();
debugLog(options, `Removing the empty file ${field}->${filename}`);
}
return debugLog(options, `Don't add file instance if original name and size are empty`);
}
filesCompleted += 1;
result.files = buildFields(result.files, field, fileFactory({
name: filename,
buffer: complete(),
encoding,
hash: getHash(),
mimetype: inferredMimeType,
size,
tempFilePath: getFilePath(),
truncated: Boolean('truncated' in file && file.truncated) || false
}, options));
if (!request[waitFlushProperty]) {
request[waitFlushProperty] = [];
}
request[waitFlushProperty].push(writePromise);
if (filesCompleted === fileCount) {
allFilesHaveResolved();
}
});
file.on('error', (err)=>{
uploadTimer.clear();
debugLog(options, `File Error: ${err.message}`);
cleanup();
failedResolvingFiles(err);
});
// Start upload process.
debugLog(options, `New upload started ${field}->${filename}, bytes:${getFileSize()}`);
uploadTimer.set();
});
busboy.on('finish', async ()=>{
debugLog(options, `Busboy finished parsing request.`);
if (options.parseNested) {
result.fields = processNested(result.fields);
result.files = processNested(result.files);
}
if (request[waitFlushProperty]) {
try {
await Promise.all(request[waitFlushProperty]).then(()=>{
delete request[waitFlushProperty];
});
} catch (err) {
debugLog(options, `Error waiting for file write promises: ${err}`);
}
}
return result;
});
busboy.on('error', (err = new APIError('Busboy error parsing multipart request', httpStatus.BAD_REQUEST))=>{
debugLog(options, `Busboy error`);
throw err;
});
while(parsingRequest){
const { done, value } = await reader.read();
if (done) {
parsingRequest = false;
}
if (value && !shouldAbortProccessing) {
busboy.write(value);
}
}
if (fileCount !== 0) {
await allFilesComplete.catch((e)=>{
throw e;
});
}
return result;
};
//# sourceMappingURL=processMultipart.js.map