import { a as supportMagicFileTypes, i as getFileType, n as computeFileTypeFromName, r as formBodyNormalizers, t as MultipartFile } from "./file-CX_Xk_Sw.js";
import { extname, isAbsolute, join } from "node:path";
import string from "@poppinss/utils/string";
import { Exception, RuntimeException } from "@poppinss/utils/exception";
import { unlink } from "node:fs/promises";
import { tmpdir } from "node:os";
import { debuglog } from "node:util";
import multiparty from "@poppinss/multiparty";
import lodash from "@poppinss/utils/lodash";
import { createWriteStream } from "node:fs";
import { pipeline } from "node:stream/promises";
import inflate from "inflation";
import raw from "raw-body";
import { safeParse } from "@poppinss/utils/json";
import qs from "@poppinss/qs";
import { HttpRequest } from "@adonisjs/http-server";
//#region src/debug.ts
/**
* Debug logger instance for the bodyparser package. Use the DEBUG=adonisjs:bodyparser
* environment variable to enable debug logging.
*
* @example
* ```sh
* DEBUG=adonisjs:bodyparser node ace serve
* ```
*/
var debug_default = debuglog("adonisjs:bodyparser");
//#endregion
//#region src/form_fields.ts
const PROTOTYPE_POLLUTING_KEYS = new Set([
	"__proto__",
	"prototype",
	"constructor"
]);
/**
* A collection of form fields that stores form data while handling
* arrays gracefully
*/
var FormFields = class {
	/**
	* Internal storage for form fields
	*/
	#fields = Object.create(null);
	#normalizer;
	/**
	* Creates a new FormFields instance for collecting form data.
	*
	* @param normalizer - Optional normalizer function to process string values
	*/
	constructor(normalizer) {
		this.#normalizer = normalizer;
	}
	/**
	* Add a new key/value pair. The keys with array-like
	* expressions are handled properly.
	*
	* @param key - The field name, can include array notation
	* @param value - The field value
	*
	* @example
	* ```
	* formfields.add('username', 'virk')
	*
	* // array
	* formfields.add('username[]', 'virk')
	* formfields.add('username[]', 'nikk')
	*
	* // Indexed keys are ordered properly
	* formfields.add('username[1]', 'virk')
	* formfields.add('username[0]', 'nikk')
	* ```
	*/
	add(key, value) {
		let isArray = false;
		/**
		* Convert empty strings to null
		*/
		if (this.#normalizer && typeof value === "string") value = this.#normalizer(value);
		/**
		* Drop `[]` without indexes, since lodash `_.set` and
		* `_.get` methods needs the index or plain key.
		*/
		key = key.replace(/\[]$/, () => {
			isArray = true;
			return "";
		});
		const keyPath = lodash.toPath(key);
		if (keyPath.some((segment) => PROTOTYPE_POLLUTING_KEYS.has(segment))) return;
		/**
		* Check to see if value exists or set it (if missing)
		*/
		const existingValue = lodash.get(this.#fields, keyPath);
		if (!existingValue) {
			lodash.set(this.#fields, keyPath, isArray ? [value] : value);
			return;
		}
		/**
		* Mutate existing value if it's an array
		*/
		if (Array.isArray(existingValue)) {
			existingValue.push(value);
			return;
		}
		/**
		* Set new value + existing value
		*/
		lodash.set(this.#fields, keyPath, [existingValue, value]);
	}
	/**
	* Returns the collected form fields as an object.
	*
	* @example
	* ```ts
	* const fields = new FormFields()
	* fields.add('username', 'virk')
	* fields.add('tags[]', 'node')
	* fields.add('tags[]', 'typescript')
	*
	* console.log(fields.get())
	* // { username: 'virk', tags: ['node', 'typescript'] }
	* ```
	*/
	get() {
		return this.#fields;
	}
};
//#endregion
//#region src/multipart/part_handler.ts
/**
* Part handler handles the progress of a stream and also internally validates
* its size and extension.
*
* This class offloads the task of validating a file stream, regardless of how
* the stream is consumed. For example:
*
* In classic scenario, we will process the file stream and write files to the
* tmp directory and in more advanced cases, the end user can handle the
* stream by themselves and report each chunk to this class.
*/
var PartHandler = class {
	/**
	* The multipart stream part being handled
	*/
	#part;
	/**
	* Configuration options for file validation and processing
	*/
	#options;
	/**
	* The stream buffer reported by the stream consumer. We hold the buffer until are
	* able to detect the file extension and then buff memory is released
	*/
	#buff;
	/**
	* A boolean to know, if we have emitted the error event after one or
	* more validation errors. We need this flag, since the race conditions
	* between `data` and `error` events will trigger multiple `error`
	* emit.
	*/
	#emittedValidationError = false;
	/**
	* A boolean to know if we can use the magic number to detect the file type. This is how it
	* works.
	*
	* - We begin by extracting the file extension from the file name
	* - If the file has no extension, we try to inspect the buffer
	* - If the extension is something we support via magic numbers, then we ignore the extension
	* 	 and inspect the buffer
	* - Otherwise, we have no other option than to trust the extension
	*
	* Think of this as using the optimal way for validating the file type
	*/
	get #canFileTypeBeDetected() {
		const fileExtension = extname(this.#part.filename).replace(/^\./, "");
		return fileExtension ? supportMagicFileTypes.has(fileExtension) : true;
	}
	/**
	* Creating a new file object for each part inside the multipart
	* form data
	*/
	file;
	/**
	* Creates a new part handler instance for processing multipart stream parts
	*
	* @param part - The multipart stream to handle
	* @param options - Validation options and configuration
	*/
	constructor(part, options) {
		this.#part = part;
		this.#options = options;
		this.file = new MultipartFile({
			clientName: part.filename,
			fieldName: part.name,
			headers: part.headers
		}, {
			size: options.size,
			extnames: options.extnames
		});
	}
	/**
	* Detects the file type and extension and also validates it when validations
	* are not deferred.
	*/
	async #detectFileTypeAndExtension() {
		if (!this.#buff) return;
		let fileType = this.#canFileTypeBeDetected ? await getFileType(this.#buff) : computeFileTypeFromName(this.file.clientName, this.file.headers);
		/**
		* If magic number detection failed after enough bytes have been
		* buffered, fall back to detecting the file type from the
		* filename and headers to prevent unbounded memory growth.
		*/
		if (!fileType && this.#buff.length >= 4100) fileType = computeFileTypeFromName(this.file.clientName, this.file.headers);
		if (fileType) {
			this.file.extname = fileType.ext;
			this.file.type = fileType.type;
			this.file.subtype = fileType.subtype;
		}
	}
	/**
	* Skip the stream or end it forcefully. This is invoked when the
	* streaming consumer reports an error
	*/
	#skipEndStream() {
		this.#part.emit("close");
	}
	/**
	* Finish the process of listening for any more events and mark the
	* file state as consumed.
	*/
	#finish() {
		this.file.state = "consumed";
		if (!this.#options.deferValidations) this.file.validate();
	}
	/**
	* Marks the file as being in streaming mode. Should be called before
	* processing the stream.
	*/
	begin() {
		this.file.state = "streaming";
	}
	/**
	* Handles the file upload progress by validating the file size and
	* extension.
	*
	* @param line - Buffer chunk from the stream
	* @param bufferLength - Length of the buffer chunk in bytes
	*/
	async reportProgress(line, bufferLength) {
		/**
		* Do not consume stream data when file state is not `streaming`. Stream
		* events race conditions may emit the `data` event after the `error`
		* event in some cases, so we have to restrict it here.
		*/
		if (this.file.state !== "streaming") return;
		/**
		* Detect the file type and extension when extname is null, otherwise
		* empty out the buffer. We only need the buffer to find the
		* file extension from it's content.
		*/
		if (this.file.extname === void 0) {
			this.#buff = this.#buff ? Buffer.concat([this.#buff, line]) : line;
			await this.#detectFileTypeAndExtension();
		} else this.#buff = void 0;
		/**
		* The length of stream buffer
		*/
		this.file.size = this.file.size + bufferLength;
		/**
		* Validate the file on every chunk, unless validations have been deferred.
		*/
		if (this.#options.deferValidations) return;
		/**
		* Attempt to validate the file after every chunk and report error
		* when it has one or more failures. After this the consumer must
		* call `reportError`.
		*/
		this.file.validate();
		if (!this.file.isValid && !this.#emittedValidationError) {
			this.#emittedValidationError = true;
			this.#part.emit("error", new Exception("one or more validations failed", {
				code: "E_STREAM_VALIDATION_FAILURE",
				status: 400
			}));
		}
	}
	/**
	* Report errors encountered while processing the stream. These can be errors
	* apart from the one reported by this class. For example: The `s3` failure
	* due to some bad credentials.
	*
	* @param error - The error encountered during stream processing
	*/
	async reportError(error) {
		if (this.file.state !== "streaming") return;
		this.#skipEndStream();
		this.#finish();
		if (error.code === "E_STREAM_VALIDATION_FAILURE") return;
		/**
		* Push to the array of file errors
		*/
		this.file.errors.push({
			fieldName: this.file.fieldName,
			clientName: this.file.clientName,
			type: "fatal",
			message: error.message
		});
	}
	/**
	* Report success data about the file.
	*
	* @param data - Success data containing file paths and metadata
	*/
	async reportSuccess(data) {
		if (this.file.state !== "streaming") return;
		/**
		* Re-attempt to detect the file extension after we are done
		* consuming the stream
		*/
		if (this.file.extname === void 0) await this.#detectFileTypeAndExtension();
		if (data) {
			const { filePath, tmpPath, ...meta } = data;
			if (filePath) this.file.filePath = filePath;
			if (tmpPath) this.file.tmpPath = tmpPath;
			this.file.meta = meta || {};
		}
		this.#finish();
	}
};
//#endregion
//#region src/multipart/main.ts
/**
* Multipart class offers a low level API to interact with the incoming
* HTTP request data as a stream. This makes it super easy to
* write files to S3 without saving them to the disk first.
*/
var Multipart = class {
	/**
	* The HTTP context for the current request
	*/
	#ctx;
	/**
	* Configuration options for multipart processing
	*/
	#config;
	/**
	* The registered handlers to handle the file uploads
	*/
	#handlers = {};
	/**
	* Collected fields from the multipart stream
	*/
	#fields;
	/**
	* Collected files from the multipart stream. Files are only collected
	* when there is an attached listener for a given file.
	*/
	#files;
	/**
	* We track the finishing of `this.onFile` async handlers
	* to make sure that `process` promise resolves for all
	* handlers to finish.
	*/
	#pendingHandlers = 0;
	/**
	* The reference to underlying multiparty form
	*/
	#form;
	/**
	* Total size limit of the multipart stream. If it goes beyond
	* the limit, then an exception will be raised.
	*/
	#upperLimit;
	/**
	* A track of total number of file bytes processed so far
	*/
	#processedBytes = 0;
	/**
	* The current state of the multipart form handler
	*/
	state = "idle";
	/**
	* Creates a new Multipart instance for processing multipart form data
	*
	* @param ctx - The HTTP context
	* @param config - Configuration options for multipart processing
	* @param _featureFlags - Feature flags (unused)
	*/
	constructor(ctx, config = {}, _featureFlags = {}) {
		this.#ctx = ctx;
		this.#config = config;
		this.#fields = new FormFields(config.normalizer);
		this.#files = new FormFields(config.normalizer);
		this.#upperLimit = config.limit;
	}
	/**
	* Returns a boolean telling whether all streams have been
	* consumed along with all handlers execution
	*/
	#isClosed() {
		return this.#form["flushing"] <= 0 && this.#pendingHandlers <= 0;
	}
	/**
	* Removes array like expression from the part name to
	* find the handler
	*/
	#getHandlerName(name) {
		return name.replace(/\[\d*\]/, "");
	}
	/**
	* Validates and returns an error when upper limit is defined and
	* processed bytes is over the upper limit
	*/
	#validateProcessedBytes(chunkLength) {
		if (!this.#upperLimit) return;
		this.#processedBytes += chunkLength;
		if (this.#processedBytes > this.#upperLimit) return new Exception("request entity too large", {
			code: "E_REQUEST_ENTITY_TOO_LARGE",
			status: 413
		});
	}
	/**
	* Handles a given part by invoking it's handler or
	* by resuming the part, if there is no defined
	* handler
	*/
	async #handlePart(part) {
		/**
		* Skip parts with empty name or empty filenames. The empty
		* filenames takes place when user doesn't upload a file
		* and empty name is more of a bad client scanerio.
		*/
		if (!part.name || !part.filename) {
			part.resume();
			return;
		}
		const name = this.#getHandlerName(part.name);
		/**
		* Skip, if their is no handler to consume the part.
		*/
		const handler = this.#handlers[name] || this.#handlers["*"];
		if (!handler) {
			debug_default("skipping multipart part as there are no handlers \"%s\"", name);
			part.resume();
			return;
		}
		debug_default("processing multipart part \"%s\"", name);
		this.#pendingHandlers++;
		/**
		* Instantiate the part handler
		*/
		const partHandler = new PartHandler(part, handler.options);
		partHandler.begin();
		/**
		* Track the file instance created by the part handler. The end user
		* must be able to access these files.
		*/
		this.#files.add(partHandler.file.fieldName, partHandler.file);
		this.#fields.add(partHandler.file.fieldName, partHandler.file);
		part.file = partHandler.file;
		try {
			const response = await handler.handler(part, async (line) => {
				if (this.state !== "processing") return;
				const lineLength = line.length;
				/**
				* Keeping an eye on total bytes processed so far and shortcircuit
				* request when more than expected bytes have been received.
				*/
				const error = this.#validateProcessedBytes(lineLength);
				if (error) {
					part.emit("error", error);
					this.abort(error);
					return;
				}
				try {
					await partHandler.reportProgress(line, lineLength);
				} catch (err) {
					part.emit("error", err);
					this.abort(err);
				}
			});
			/**
			* Stream consumed successfully
			*/
			await partHandler.reportSuccess(response || {});
		} catch (error) {
			/**
			* The stream handler reported an exception
			*/
			await partHandler.reportError(error);
		}
		this.#pendingHandlers--;
	}
	/**
	* Record the fields inside multipart contract
	*/
	#handleField(key, value) {
		if (!key) return;
		this.#fields.add(key, value);
	}
	/**
	* Mark the process as finished
	*/
	#finish(newState) {
		if (this.state === "idle" || this.state === "processing") {
			this.state = newState;
			this.#ctx.request["__raw_files"] = this.#files.get();
			this.#ctx.request.setInitialBody(this.#fields.get());
		}
	}
	/**
	* Attach handler for a given file. To handle all files, you
	* can attach a wildcard handler.
	*
	* @param name - The field name to handle, or '*' for wildcard
	* @param options - Validation options and configuration
	* @param handler - The handler function to process the file
	*
	* @example
	* ```ts
	* multipart.onFile('package', {}, async (stream) => {
	* })
	*
	* multipart.onFile('*', {}, async (stream) => {
	* })
	* ```
	*/
	onFile(name, options, handler) {
		this.#handlers[name] = {
			handler,
			options
		};
		return this;
	}
	/**
	* Aborts the multipart stream processing by emitting an error event.
	* This will stop all file processing and reject the process promise.
	*
	* @param error - The error that caused the abort
	*
	* @example
	* ```ts
	* multipart.onFile('*', {}, async (part) => {
	*   if (part.file.size > MAX_SIZE) {
	*     multipart.abort(new Error('File too large'))
	*   }
	* })
	* ```
	*/
	abort(error) {
		this.#form.emit("error", error);
	}
	/**
	* Processes the multipart request by parsing all file and field streams.
	* Must be called after registering all file handlers with onFile.
	*
	* @param config - Optional configuration overrides for this specific request
	*
	* @example
	* ```ts
	* const multipart = ctx.request.multipart
	*
	* multipart.onFile('avatar', {}, async (part, reportChunk) => {
	*   await streamFile(part, '/tmp/avatar.jpg', reportChunk)
	* })
	*
	* await multipart.process()
	* ```
	*/
	process(config) {
		return new Promise((resolve, reject) => {
			if (this.state !== "idle") {
				reject(new Exception("multipart stream has already been consumed", { code: "E_RUNTIME_EXCEPTION" }));
				return;
			}
			this.state = "processing";
			/**
			* Use local upperlimit
			*/
			if (config && config.limit) this.#upperLimit = string.bytes.parse(config.limit);
			this.#form = new multiparty.Form({
				maxFields: config?.maxFields ?? this.#config.maxFields,
				maxFieldsSize: this.#config.fieldsLimit
			});
			debug_default("processing multipart body");
			/**
			* Raise error when form encounters an
			* error
			*/
			this.#form.on("error", (error) => {
				this.#finish("error");
				process.nextTick(() => {
					if (this.#ctx.request.request.readable) this.#ctx.request.request.resume();
					if (error.message.match(/stream ended unexpectedly/)) reject(new Exception("Invalid multipart request", {
						status: 400,
						code: "E_INVALID_MULTIPART_REQUEST"
					}));
					else if (error.message.match(/maxFields [0-9]+ exceeded/)) reject(new Exception("Fields length limit exceeded", {
						status: 413,
						code: "E_REQUEST_ENTITY_TOO_LARGE"
					}));
					else if (error.message.match(/maxFieldsSize [0-9]+ exceeded/)) reject(new Exception("Fields size in bytes exceeded", {
						status: 413,
						code: "E_REQUEST_ENTITY_TOO_LARGE"
					}));
					else reject(error);
				});
			});
			/**
			* Process each part at a time and also resolve the
			* promise when all parts are consumed and processed
			* by their handlers
			*/
			this.#form.on("part", async (part) => {
				await this.#handlePart(part);
				/**
				* When a stream finishes before the handler, the close `event`
				* will not resolve the current Promise. So in that case, we
				* check and resolve from here
				*/
				if (this.#isClosed()) {
					this.#finish("success");
					resolve();
				}
			});
			/**
			* Listen for fields
			*/
			this.#form.on("field", (key, value) => {
				try {
					this.#handleField(key, value);
				} catch (error) {
					this.abort(error);
				}
			});
			/**
			* Resolve promise on close, when all internal
			* file handlers are done processing files
			*/
			this.#form.on("close", () => {
				if (this.#isClosed()) {
					this.#finish("success");
					resolve();
				}
			});
			this.#form.parse(this.#ctx.request.request);
		});
	}
};
//#endregion
//#region src/multipart/stream_file.ts
/**
* Streams a file from a readable stream to a file system location. Automatically
* cleans up on errors and optionally reports data chunks to a listener.
*
* @param readStream - The source readable stream
* @param location - The destination file path
* @param dataListener - Optional callback to receive data chunks
*
* @example
* ```ts
* await streamFile(part, '/tmp/upload.jpg', (chunk) => {
*   console.log('Received', chunk.length, 'bytes')
* })
* ```
*/
async function streamFile(readStream, location, dataListener) {
	if (typeof dataListener === "function") {
		readStream.pause();
		readStream.on("data", dataListener);
	}
	const writeStream = createWriteStream(location);
	try {
		await pipeline(readStream, writeStream);
	} catch (error) {
		unlink(writeStream.path).catch(() => {});
		throw error;
	}
}
//#endregion
//#region src/parsers/text.ts
/**
* Prepares parser options for raw text body parsing by applying defaults
* for encoding and size limits.
*
* @param options - Raw body parser configuration
*/
function prepareTextParserOptions(options) {
	return {
		encoding: options.encoding ?? "utf8",
		limit: options.limit ?? "56kb"
	};
}
/**
* Parses and inflates the raw request body as text. Automatically handles
* compressed request bodies (gzip, deflate, etc.).
*
* @param req - The incoming HTTP request
* @param options - Parser options including encoding and size limits
*/
function parseText(req, options) {
	/**
	* Mimicing behavior of
	* https://github.com/poppinss/co-body/blob/master/lib/text.js#L30
	*/
	const contentLength = req.headers["content-length"];
	const encoding = req.headers["content-encoding"] || "identity";
	if (contentLength && encoding === "identity") options = {
		...options,
		length: ~~contentLength
	};
	return raw(inflate(req), options);
}
//#endregion
//#region src/parsers/json.ts
/**
* Allowed whitespace is defined in RFC 7159
* http://www.rfc-editor.org/rfc/rfc7159.txt
*/
const strictJSONReg = /^[\x20\x09\x0a\x0d]*(\[|\{)/;
/**
* Prepares parser options for JSON body parsing by configuring strict mode
* and value normalization through a reviver function.
*
* @param options - JSON body parser configuration
*/
function prepareJSONParserOptions(options) {
	let normalizer;
	if (options.convertEmptyStringsToNull && options.trimWhitespaces) normalizer = formBodyNormalizers.trimWhitespacesAndConvertToNull;
	else if (options.convertEmptyStringsToNull) normalizer = formBodyNormalizers.convertToNull;
	else if (options.trimWhitespaces) normalizer = formBodyNormalizers.trimWhitespaces;
	return {
		...prepareTextParserOptions(options),
		strict: options.strict !== false,
		reviver: normalizer ? function JSONReviver(key, value) {
			if (key === "") return value;
			return typeof value === "string" ? normalizer(value) : value;
		} : void 0
	};
}
/**
* Parses JSON request body with optional strict mode that enforces only
* objects and arrays as valid JSON. Returns both parsed and raw representations.
*
* @param req - The incoming HTTP request
* @param options - Parser options including encoding, limits, and strict mode
*
* @example
* ```ts
* const { parsed, raw } = await parseJSON(request, options)
* // parsed: { username: 'virk', age: 28 }
* // raw: '{"username":"virk","age":28}'
* ```
*/
async function parseJSON(req, options) {
	const requestBody = await parseText(req, options);
	/**
	* Do not parse body when request body is empty
	*/
	if (!requestBody) return options.strict ? {
		parsed: {},
		raw: requestBody
	} : {
		parsed: requestBody,
		raw: requestBody
	};
	/**
	* Test JSON body to ensure it is valid JSON in strict mode
	*/
	if (options.strict && !strictJSONReg.test(requestBody)) throw new Exception("Invalid JSON, only supports object and array", { status: 422 });
	try {
		return {
			parsed: safeParse(requestBody, options.reviver),
			raw: requestBody
		};
	} catch (error) {
		error.status = 400;
		error.body = requestBody;
		throw error;
	}
}
//#endregion
//#region src/parsers/form.ts
/**
* Prepares parser options for URL-encoded form data by configuring
* query string parsing and value normalization.
*
* @param options - Form body parser configuration
*/
function prepareFormParserOptions(options) {
	/**
	* Shallow clone query string options
	*/
	const queryStringOptions = { ...options.queryString };
	if (queryStringOptions.allowDots === void 0) queryStringOptions.allowDots = true;
	let normalizer;
	if (options.convertEmptyStringsToNull && options.trimWhitespaces) normalizer = formBodyNormalizers.trimWhitespacesAndConvertToNull;
	else if (options.convertEmptyStringsToNull) normalizer = formBodyNormalizers.convertToNull;
	else if (options.trimWhitespaces) normalizer = formBodyNormalizers.trimWhitespaces;
	/**
	* Convert empty strings to null
	*/
	if (normalizer) queryStringOptions.decoder = function(str, defaultDecoder, charset, type) {
		let value = defaultDecoder(str, defaultDecoder, charset);
		if (type === "value") return normalizer(value);
		return value;
	};
	/**
	* Shallow clone of provided options
	*/
	return {
		...prepareTextParserOptions(options),
		qs: queryStringOptions
	};
}
/**
* Parses URL-encoded form data (application/x-www-form-urlencoded) from
* the request body and returns both parsed and raw representations.
*
* @param req - The incoming HTTP request
* @param options - Parser options including encoding, limits, and query string config
*
* @example
* ```ts
* const { parsed, raw } = await parseForm(request, options)
* // parsed: { username: 'virk', tags: ['node', 'typescript'] }
* // raw: 'username=virk&tags=node&tags=typescript'
* ```
*/
async function parseForm(req, options) {
	const requestBody = await parseText(req, options);
	return {
		parsed: qs.parse(requestBody, options.qs),
		raw: requestBody
	};
}
//#endregion
//#region src/bindings/request.ts
/**
* Updates the validation options on a file instance if they haven't been set already.
*
* @param file - The multipart file instance to update
* @param options - Validation options including size limit and allowed extensions
*/
function setFileOptions(file, options) {
	if (file.sizeLimit === void 0 && options && options.size) file.sizeLimit = options.size;
	if (file.allowedExtensions === void 0 && options && options.extnames) file.allowedExtensions = options.extnames;
}
/**
* Type guard to check if a value is an instance of MultipartFile.
*
* @param file - The value to check
*/
function isInstanceOfFile(file) {
	return file && file instanceof MultipartFile;
}
debug_default("extending request class with \"file\", \"files\" and \"allFiles\" macros");
/**
* Extends the Request class toJSON method to serialize files alongside
* the rest of the request data.
*/
HttpRequest.macro("toJSON", function() {
	return {
		...this.serialize(),
		files: this["__raw_files"] || {}
	};
});
/**
* Extends the Request class with a method to fetch a single uploaded file.
* When an array of files exists for the key, returns the first file.
*
* @example
* ```ts
* const avatar = request.file('avatar', {
*   size: '2mb',
*   extnames: ['jpg', 'png', 'jpeg']
* })
*
* if (avatar && avatar.isValid) {
*   await avatar.move(app.publicPath('uploads'))
* }
* ```
*/
HttpRequest.macro("file", function getFile(key, options) {
	let file = lodash.get(this.allFiles(), key);
	file = Array.isArray(file) ? file[0] : file;
	if (!isInstanceOfFile(file)) return null;
	setFileOptions(file, options);
	file.validate();
	return file;
});
/**
* Extends the Request class with a method to fetch all uploaded files for
* a given field name. Always returns an array, even if a single file was uploaded.
*
* @example
* ```ts
* const documents = request.files('documents', {
*   size: '5mb',
*   extnames: ['pdf', 'doc', 'docx']
* })
*
* for (const doc of documents) {
*   if (doc.isValid) {
*     await doc.move(app.publicPath('uploads'))
*   }
* }
* ```
*/
HttpRequest.macro("files", function getFiles(key, options) {
	let files = lodash.get(this.allFiles(), key);
	files = Array.isArray(files) ? files : files ? [files] : [];
	return files.filter(isInstanceOfFile).map((file) => {
		setFileOptions(file, options);
		file.validate();
		return file;
	});
});
/**
* Extends the Request class with a method to fetch all uploaded files
* from the request. Throws an error if the bodyparser middleware is not registered.
*
* @example
* ```ts
* const allFiles = request.allFiles()
* // { avatar: MultipartFile, documents: [MultipartFile, MultipartFile] }
* ```
*/
HttpRequest.macro("allFiles", function allFiles() {
	if (!this.__raw_files) throw new RuntimeException("Cannot read files. Make sure the bodyparser middleware is registered");
	return this["__raw_files"];
});
//#endregion
//#region src/parsers/multipart.ts
/**
* Prepares configuration for multipart form data parsing by converting
* size limits from strings to bytes and configuring value normalization.
*
* @param config - Multipart body parser configuration
*/
function prepareMultipartConfig(config) {
	let normalizer;
	if (config.convertEmptyStringsToNull && config.trimWhitespaces) normalizer = formBodyNormalizers.trimWhitespacesAndConvertToNull;
	else if (config.convertEmptyStringsToNull) normalizer = formBodyNormalizers.convertToNull;
	else if (config.trimWhitespaces) normalizer = formBodyNormalizers.trimWhitespaces;
	return {
		limit: config.limit ? string.bytes.parse(config.limit) : void 0,
		fieldsLimit: config.fieldsLimit ? string.bytes.parse(config.fieldsLimit) : void 0,
		maxFields: config.maxFields,
		normalizer
	};
}
//#endregion
//#region src/bodyparser_middleware.ts
/**
* Bindings to extend request
*/
/**
* BodyParser middleware parses the incoming request body and sets it as
* request body to be read later in the request lifecycle.
*/
var BodyParserMiddleware = class {
	/**
	* Body parser configuration
	*/
	#config;
	#parsersConfig;
	/**
	* Creates a new BodyParserMiddleware instance
	*
	* @param config - The body parser configuration
	* @param _featureFlags - Feature flags (unused)
	*/
	constructor(config, _featureFlags) {
		this.#config = config;
		this.#parsersConfig = {
			raw: prepareTextParserOptions(this.#config.raw),
			form: prepareFormParserOptions(this.#config.form),
			json: prepareJSONParserOptions(this.#config.json),
			multipart: prepareMultipartConfig(this.#config.multipart)
		};
		debug_default("using config %O", this.#config);
	}
	/**
	* Checks if the request content-type header matches any of the expected types.
	*
	* @param request - The HTTP request object
	* @param types - Array of MIME types to check against
	*/
	#isType(request, types) {
		return !!(types && types.length && request.is(types));
	}
	/**
	* Converts raw-body errors into AdonisJS exceptions with appropriate
	* status codes and error codes.
	*
	* @param error - The raw-body error object
	*/
	#getExceptionFor(error) {
		switch (error.type) {
			case "encoding.unsupported": return new Exception(error.message, {
				status: error.status,
				code: "E_ENCODING_UNSUPPORTED"
			});
			case "entity.too.large": return new Exception(error.message, {
				status: error.status,
				code: "E_REQUEST_ENTITY_TOO_LARGE"
			});
			case "request.aborted": return new Exception(error.message, {
				status: error.status,
				code: "E_REQUEST_ABORTED"
			});
			default: return error;
		}
	}
	/**
	* Generates a temporary file path for storing uploaded files. Uses the
	* configured tmpFileName function if provided, otherwise generates a UUID.
	*
	* @param config - The multipart configuration
	*/
	#getTmpPath(config) {
		if (typeof config.tmpFileName === "function") {
			const tmpPath = config.tmpFileName();
			return isAbsolute(tmpPath) ? tmpPath : join(tmpdir(), tmpPath);
		}
		return join(tmpdir(), string.uuid());
	}
	/**
	* Handle HTTP request body by parsing it according to the user
	* configuration
	*
	* @param ctx - The HTTP context
	* @param next - The next middleware function
	*/
	async handle(ctx, next) {
		/**
		* Initiating the `__raw_files` property as an object
		*/
		ctx.request["__raw_files"] = {};
		const requestUrl = ctx.request.url();
		const requestMethod = ctx.request.method();
		/**
		* Only process for whitelisted nodes
		*/
		if (!this.#config.allowedMethods.includes(requestMethod)) {
			debug_default("skipping HTTP request \"%s:%s\"", requestMethod, requestUrl);
			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()) {
			debug_default("skipping as request has no body \"%s:%s\"", requestMethod, requestUrl);
			return next();
		}
		/**
		* Handle multipart form
		*/
		const multipartConfig = this.#config["multipart"];
		if (this.#isType(ctx.request, multipartConfig.types)) {
			debug_default("detected multipart request \"%s:%s\"", requestMethod, requestUrl);
			ctx.request.multipart = new Multipart(ctx, this.#parsersConfig.multipart, {});
			ctx.request.bodyType = "multipart";
			/**
			* Skip parsing when `autoProcess` is disabled
			*/
			if (multipartConfig.autoProcess === false) {
				debug_default("skipping auto processing of multipart request \"%s:%s\"", requestMethod, requestUrl);
				return next();
			}
			/**
			* Skip parsing when the current route matches one of the defined
			* processManually route patterns.
			*/
			if (ctx.route && multipartConfig.processManually.includes(ctx.route.pattern)) {
				debug_default("skipping auto processing of multipart request \"%s:%s\"", requestMethod, requestUrl);
				return next();
			}
			/**
			* Skip parsing when the current route matches one of the "autoProcess"
			* patterns
			*/
			if (ctx.route && Array.isArray(multipartConfig.autoProcess) && !multipartConfig.autoProcess.includes(ctx.route.pattern)) {
				debug_default("skipping auto processing of multipart request \"%s:%s\"", requestMethod, requestUrl);
				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.
			*/
			debug_default("auto processing multipart request \"%s:%s\"", requestMethod, requestUrl);
			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 streamFile(part, tmpPath, reporter);
					return { tmpPath };
				} catch (error) {
					ctx.request.multipart.abort(error);
				}
			});
			try {
				await ctx.request.multipart.process();
				return next();
			} catch (error) {
				throw error;
			}
		}
		/**
		* Handle url-encoded form data
		*/
		const formConfig = this.#config["form"];
		if (this.#isType(ctx.request, formConfig.types)) {
			debug_default("detected urlencoded request \"%s:%s\"", requestMethod, requestUrl);
			try {
				const { parsed, raw } = await parseForm(ctx.request.request, this.#parsersConfig.form);
				ctx.request.setInitialBody(parsed);
				ctx.request.updateRawBody(raw);
				ctx.request.bodyType = "urlencoded";
				return next();
			} catch (error) {
				throw this.#getExceptionFor(error);
			}
		}
		/**
		* Handle content with JSON types
		*/
		const jsonConfig = this.#config["json"];
		if (this.#isType(ctx.request, jsonConfig.types)) {
			debug_default("detected JSON request \"%s:%s\"", requestMethod, requestUrl);
			try {
				const { parsed, raw } = await parseJSON(ctx.request.request, this.#parsersConfig.json);
				ctx.request.setInitialBody(parsed);
				ctx.request.updateRawBody(raw);
				ctx.request.bodyType = "json";
				return next();
			} catch (error) {
				throw this.#getExceptionFor(error);
			}
		}
		/**
		* Handles raw request body
		*/
		const rawConfig = this.#config["raw"];
		if (this.#isType(ctx.request, rawConfig.types)) {
			debug_default("parsing raw body \"%s:%s\"", requestMethod, requestUrl);
			try {
				ctx.request.setInitialBody({});
				ctx.request.updateRawBody(await parseText(ctx.request.request, this.#parsersConfig.raw));
				ctx.request.bodyType = "raw";
				return next();
			} catch (error) {
				throw this.#getExceptionFor(error);
			}
		}
		ctx.request.bodyType = "unknown";
		await next();
	}
};
//#endregion
export { BodyParserMiddleware as t };
