"use strict";
/*
 * @adonisjs/bodyparser
 *
 * (c) Harminder Virk <virk@adonisjs.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.BodyParserMiddleware = void 0;
/// <reference path="../../adonis-typings/bodyparser.ts" />
const os_1 = require("os");
const co_body_1 = __importDefault(require("@poppinss/co-body"));
const path_1 = require("path");
const utils_1 = require("@poppinss/utils");
const application_1 = require("@adonisjs/application");
const helpers_1 = require("@poppinss/utils/build/helpers");
const Multipart_1 = require("../Multipart");
const streamFile_1 = require("../Multipart/streamFile");
/**
 * BodyParser middleware parses the incoming request body and set it as
 * request body to be read later in the request lifecycle.
 */
let BodyParserMiddleware = class BodyParserMiddleware {
    constructor(Config, drive) {
        this.drive = drive;
        this.config = Config.get('bodyparser', {});
    }
    /**
     * Returns config for a given type
     */
    getConfigFor(type) {
        const config = this.config[type];
        config['returnRawBody'] = true;
        return config;
    }
    /**
     * Ensures that types exists and have length
     */
    ensureTypes(types) {
        return !!(types && types.length);
    }
    /**
     * Returns a boolean telling if request `content-type` header
     * matches the expected types or not
     */
    isType(request, types) {
        return !!(this.ensureTypes(types) && request.is(types));
    }
    /**
     * Returns a proper Adonis style exception for popular error codes
     * returned by https://github.com/stream-utils/raw-body#readme.
     */
    getExceptionFor(error) {
        switch (error.type) {
            case 'encoding.unsupported':
                return new utils_1.Exception(error.message, error.status, 'E_ENCODING_UNSUPPORTED');
            case 'entity.too.large':
                return new utils_1.Exception(error.message, error.status, 'E_REQUEST_ENTITY_TOO_LARGE');
            case 'request.aborted':
                return new utils_1.Exception(error.message, error.status, 'E_REQUEST_ABORTED');
            default:
                return error;
        }
    }
    /**
     * Returns the tmp path for storing the files temporarly
     */
    getTmpPath(config) {
        if (typeof config.tmpFileName === 'function') {
            const tmpPath = config.tmpFileName();
            return (0, path_1.isAbsolute)(tmpPath) ? tmpPath : (0, path_1.join)((0, os_1.tmpdir)(), tmpPath);
        }
        return (0, path_1.join)((0, os_1.tmpdir)(), (0, helpers_1.cuid)());
    }
    /**
     * Handle HTTP request body by parsing it as per the user
     * config
     */
    async handle(ctx, next) {
        /**
         * Initiating the `__raw_files` private property as an object
         */
        ctx.request['__raw_files'] = {};
        const requestMethod = ctx.request.method();
        /**
         * Only process for whitelisted nodes
         */
        if (!this.config.whitelistedMethods.includes(requestMethod)) {
            ctx.logger.trace(`bodyparser skipping method ${requestMethod}`);
            return next();
        }
        /**
         * Return early when request body is empty. Many clients set the `Content-length = 0`
         * when request doesn't have any body, which is not handled by the below method.
         *
         * The main point of `hasBody` is to early return requests with empty body created by
         * clients with missing headers.
         */
        if (!ctx.request.hasBody()) {
            ctx.logger.trace('bodyparser skipping empty body');
            return next();
        }
        /**
         * Handle multipart form
         */
        const multipartConfig = this.getConfigFor('multipart');
        if (this.isType(ctx.request, multipartConfig.types)) {
            ctx.logger.trace('bodyparser parsing as multipart body');
            ctx.request.multipart = new Multipart_1.Multipart(ctx, {
                maxFields: multipartConfig.maxFields,
                limit: multipartConfig.limit,
                fieldsLimit: multipartConfig.fieldsLimit,
                convertEmptyStringsToNull: multipartConfig.convertEmptyStringsToNull,
            }, this.drive);
            /**
             * Skip parsing when `autoProcess` is disabled or route matches one
             * of the defined processManually route patterns.
             */
            if (!multipartConfig.autoProcess ||
                multipartConfig.processManually.indexOf(ctx.route.pattern) > -1) {
                return next();
            }
            /**
             * Make sure we are not running any validations on the uploaded files. They are
             * deferred for the end user when they will access file using `request.file`
             * method.
             */
            ctx.request.multipart.onFile('*', { deferValidations: true }, async (part, reporter) => {
                /**
                 * We need to abort the main request when we are unable to process any
                 * file. Otherwise the error will endup on the file object, which
                 * is incorrect.
                 */
                try {
                    const tmpPath = this.getTmpPath(multipartConfig);
                    await (0, streamFile_1.streamFile)(part, tmpPath, reporter);
                    return { tmpPath };
                }
                catch (error) {
                    ctx.request.multipart.abort(error);
                }
            });
            const action = ctx.profiler.profile('bodyparser:multipart');
            try {
                await ctx.request.multipart.process();
                action.end();
                return next();
            }
            catch (error) {
                action.end({ error });
                throw error;
            }
        }
        /**
         * Handle url-encoded form data
         */
        const formConfig = this.getConfigFor('form');
        if (this.isType(ctx.request, formConfig.types)) {
            ctx.logger.trace('bodyparser parsing as form request');
            const action = ctx.profiler.profile('bodyparser:urlencoded');
            try {
                const { parsed, raw } = await co_body_1.default.form(ctx.request.request, formConfig);
                ctx.request.setInitialBody(parsed);
                ctx.request.updateRawBody(raw);
                action.end();
                return next();
            }
            catch (error) {
                action.end({ error });
                throw this.getExceptionFor(error);
            }
        }
        /**
         * Handle content with JSON types
         */
        const jsonConfig = this.getConfigFor('json');
        if (this.isType(ctx.request, jsonConfig.types)) {
            ctx.logger.trace('bodyparser parsing as json body');
            const action = ctx.profiler.profile('bodyparser:json');
            try {
                const { parsed, raw } = await co_body_1.default.json(ctx.request.request, jsonConfig);
                ctx.request.setInitialBody(parsed);
                ctx.request.updateRawBody(raw);
                action.end();
                return next();
            }
            catch (error) {
                action.end({ error });
                throw this.getExceptionFor(error);
            }
        }
        /**
         * Handles raw request body
         */
        const rawConfig = this.getConfigFor('raw');
        if (this.isType(ctx.request, rawConfig.types)) {
            ctx.logger.trace('bodyparser parsing as raw body');
            const action = ctx.profiler.profile('bodyparser:raw');
            try {
                const { raw } = await co_body_1.default.text(ctx.request.request, rawConfig);
                ctx.request.setInitialBody({});
                ctx.request.updateRawBody(raw);
                action.end();
                return next();
            }
            catch (error) {
                action.end({ error });
                throw this.getExceptionFor(error);
            }
        }
        await next();
    }
};
BodyParserMiddleware = __decorate([
    (0, application_1.inject)(['Adonis/Core/Config', 'Adonis/Core/Drive']),
    __metadata("design:paramtypes", [Object, Object])
], BodyParserMiddleware);
exports.BodyParserMiddleware = BodyParserMiddleware;
