"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.handleMultipartUpload = handleMultipartUpload;
const busboy_1 = __importDefault(require("busboy"));
const events_1 = require("events");
const vfs_1 = require("./vfs");
const path_1 = require("path");
const upload_1 = require("./upload");
const cross_const_1 = require("./cross-const");
const first_1 = require("./first");
const cross_1 = require("./cross");
async function handleMultipartUpload(ctx, node) {
    if (ctx.request.type !== 'multipart/form-data')
        return ctx.status = cross_const_1.HTTP_BAD_REQUEST;
    ctx.state.uploads = [];
    const locks = [];
    const fileJobs = [];
    const errors = [];
    const bb = (0, cross_1.try_)(() => (0, busboy_1.default)({ headers: ctx.req.headers, preservePath: true }), e => {
        ctx.body = String(e); // busboy validates multipart headers at construction time, so malformed requests must stop here as 400
        ctx.status = cross_const_1.HTTP_BAD_REQUEST;
    });
    if (!bb)
        return;
    bb.on('field', (name) => {
        if (name === 'upload')
            errors.push('empty filename');
    });
    bb.on('file', (_field, file, info) => {
        const fn = info.filename || '';
        if (!fn) {
            errors.push('empty filename');
            fileJobs.push(drainStream(file));
            return;
        }
        ctx.state.uploadPath = decodeURI(ctx.path) + fn;
        ctx.state.uploads.push(fn);
        file.pause();
        fileJobs.push(handleFile(file, fn));
    });
    bb.on('error', (err) => {
        console.warn("Couldn't parse POST requests:", String(err));
        ctx.status = cross_const_1.HTTP_BAD_REQUEST;
    });
    ctx.req.pipe(bb);
    await (0, events_1.once)(bb, 'finish');
    await Promise.all(fileJobs);
    if (!ctx.state.uploads?.length) {
        if (!errors.length)
            errors.push('no files');
        ctx.status = cross_const_1.HTTP_BAD_REQUEST;
    }
    const uris = await Promise.all(locks);
    ctx.body = errors.length ? { uris, errors } : { uris };
    return;
    async function handleFile(file, fn) {
        try { // it is still possible to allow upload in a folder and block in a subfolder, so check for it
            const ret = !await subfolderBlocksUpload(fn) && (0, upload_1.uploadWriter)(node, ctx.path, fn, ctx);
            if (!ret)
                return drainStream(file);
            locks.push(ret.lockMiddleware);
            file.pipe(ret);
            file.resume();
        }
        catch (e) {
            console.warn("Couldn't handle uploaded file:", String(e));
            file.resume();
        }
    }
    function drainStream(stream) {
        stream.resume();
        return new Promise(res => (0, first_1.onFirstEvent)(stream, ['end', 'close', 'error'], res));
    }
    async function subfolderBlocksUpload(fn) {
        const prefix = (0, path_1.dirname)(fn.replaceAll('\\', '/'));
        if (prefix === '.') // no subdir
            return false;
        const subfolderNode = await (0, vfs_1.urlToNode)(prefix + '/', ctx, node, true); // final slash = explicitly a folder even if it doesn't exist on disk
        return subfolderNode && !(0, vfs_1.hasPermission)(subfolderNode, 'can_upload', ctx);
    }
}
