1 | {"version":3,"sources":["../src/multipart/file.ts","../src/helpers.ts","../src/multipart/validators/size.ts","../src/multipart/validators/extensions.ts"],"sourcesContent":["/*\n * @adonisjs/bodyparser\n *\n * (c) AdonisJS\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nimport { join } from 'node:path'\nimport { Exception } from '@poppinss/utils'\nimport Macroable from '@poppinss/macroable'\n\nimport { moveFile } from '../helpers.js'\nimport { SizeValidator } from './validators/size.js'\nimport { ExtensionValidator } from './validators/extensions.js'\nimport type { FileJSON, FileUploadError, FileValidationOptions } from '../types.js'\n\n/**\n * The file holds the meta/data for an uploaded file, along with\n * an errors occurred during the upload process.\n */\nexport class MultipartFile extends Macroable {\n /**\n * File validators\n */\n #sizeValidator = new SizeValidator(this)\n #extensionValidator = new ExtensionValidator(this)\n\n /**\n * A boolean to know if file is an instance of this class\n * or not\n */\n isMultipartFile: true = true\n\n /**\n * Field name is the name of the field\n */\n fieldName: string\n\n /**\n * Client name is the file name on the user client\n */\n clientName: string\n\n /**\n * The headers sent as part of the multipart request\n */\n headers: Record<string, any>\n\n /**\n * File size in bytes\n */\n size: number = 0\n\n /**\n * The extname for the file.\n */\n extname?: string\n\n /**\n * Upload errors\n */\n errors: FileUploadError[] = []\n\n /**\n * Type and subtype are extracted from the `content-type`\n * header or from the file magic number\n */\n type?: string\n subtype?: string\n\n /**\n * File path is only set after the move operation\n */\n filePath?: string\n\n /**\n * File name is only set after the move operation. It is the relative\n * path of the moved file\n */\n fileName?: string\n\n /**\n * Tmp path, only exists when file is uploaded using the\n * classic mode.\n */\n tmpPath?: string\n\n /**\n * The file meta data\n */\n meta: any = {}\n\n /**\n * The state of the file\n */\n state: 'idle' | 'streaming' | 'consumed' | 'moved' = 'idle'\n\n /**\n * Whether or not the validations have been executed\n */\n get validated(): boolean {\n return this.#sizeValidator.validated && this.#extensionValidator.validated\n }\n\n /**\n * A boolean to know if file has one or more errors\n */\n get isValid() {\n return this.errors.length === 0\n }\n\n /**\n * Opposite of [[this.isValid]]\n */\n get hasErrors() {\n return !this.isValid\n }\n\n /**\n * The maximum file size limit\n */\n get sizeLimit() {\n return this.#sizeValidator.maxLimit\n }\n\n set sizeLimit(limit: number | string | undefined) {\n this.#sizeValidator.maxLimit = limit\n }\n\n /**\n * Extensions allowed\n */\n get allowedExtensions() {\n return this.#extensionValidator.extensions\n }\n\n set allowedExtensions(extensions: string[] | undefined) {\n this.#extensionValidator.extensions = extensions\n }\n\n constructor(\n data: { fieldName: string; clientName: string; headers: any },\n validationOptions: Partial<FileValidationOptions>\n ) {\n super()\n this.sizeLimit = validationOptions.size\n this.allowedExtensions = validationOptions.extnames\n this.fieldName = data.fieldName\n this.clientName = data.clientName\n this.headers = data.headers\n }\n\n /**\n * Validate the file\n */\n validate() {\n this.#extensionValidator.validate()\n this.#sizeValidator.validate()\n }\n\n /**\n * Mark file as moved\n */\n markAsMoved(fileName: string, filePath: string) {\n this.filePath = filePath\n this.fileName = fileName\n this.state = 'moved'\n }\n\n /**\n * Moves the file to a given location. Multiple calls to the `move` method are allowed,\n * incase you want to move a file to multiple locations.\n */\n async move(location: string, options?: { name?: string; overwrite?: boolean }): Promise<void> {\n if (!this.tmpPath) {\n throw new Exception('property \"tmpPath\" must be set on the file before moving it', {\n status: 500,\n code: 'E_MISSING_FILE_TMP_PATH',\n })\n }\n\n options = Object.assign({ name: this.clientName, overwrite: true }, options)\n const filePath = join(location, options.name!)\n\n try {\n await moveFile(this.tmpPath, filePath, { overwrite: options.overwrite! })\n this.markAsMoved(options.name!, filePath)\n } catch (error) {\n if (error.message.includes('destination file already')) {\n throw new Exception(\n `\"${options.name!}\" already exists at \"${location}\". Set \"overwrite = true\" to overwrite it`\n )\n }\n throw error\n }\n }\n\n /**\n * Returns file JSON representation\n */\n toJSON(): FileJSON {\n return {\n fieldName: this.fieldName,\n clientName: this.clientName,\n size: this.size,\n filePath: this.filePath,\n fileName: this.fileName,\n type: this.type,\n extname: this.extname,\n subtype: this.subtype,\n state: this.state,\n isValid: this.isValid,\n validated: this.validated,\n errors: this.errors,\n meta: this.meta,\n }\n }\n}\n","/*\n * @adonisjs/bodyparser\n *\n * (c) AdonisJS\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nimport type { Mode } from 'node:fs'\nimport mediaTyper from 'media-typer'\nimport { dirname, extname } from 'node:path'\nimport { RuntimeException } from '@poppinss/utils'\nimport { fileTypeFromBuffer, supportedExtensions } from 'file-type'\nimport { access, mkdir, copyFile, unlink, rename } from 'node:fs/promises'\n\n/**\n * We can detect file types for these files using the magic\n * number\n */\nexport const supportMagicFileTypes = supportedExtensions\n\n/**\n * Attempts to parse the file mime type using the file magic number\n */\nfunction parseMimeType(mime: string): { type: string; subtype: string } | null {\n try {\n const { type, subtype } = mediaTyper.parse(mime)\n return { type, subtype }\n } catch (error) {\n return null\n }\n}\n\n/**\n * Returns the file `type`, `subtype` and `extension`.\n */\nexport async function getFileType(\n fileContents: Buffer\n): Promise<null | { ext: string; type?: string; subtype?: string }> {\n /**\n * Attempt to detect file type from it's content\n */\n const magicType = await fileTypeFromBuffer(fileContents)\n if (magicType) {\n return Object.assign({ ext: magicType.ext }, parseMimeType(magicType.mime))\n }\n\n return null\n}\n\n/**\n * Computes file name from the file type\n */\nexport function computeFileTypeFromName(\n clientName: string,\n headers: { [key: string]: string }\n): { ext: string; type?: string; subtype?: string } {\n /**\n * Otherwise fallback to file extension from it's client name\n * and pull type/subtype from the headers content type.\n */\n return Object.assign(\n { ext: extname(clientName).replace(/^\\./, '') },\n parseMimeType(headers['content-type'])\n )\n}\n\n/**\n * Check if a file already exists\n */\nexport async function pathExists(filePath: string) {\n try {\n await access(filePath)\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Move file from source to destination with a fallback to move file across\n * paritions and devices.\n */\nexport async function moveFile(\n sourcePath: string,\n destinationPath: string,\n options: { overwrite: boolean; directoryMode?: Mode } = { overwrite: true }\n) {\n if (!sourcePath || !destinationPath) {\n throw new RuntimeException('\"sourcePath\" and \"destinationPath\" required')\n }\n\n if (!options.overwrite && (await pathExists(destinationPath))) {\n throw new RuntimeException(`The destination file already exists: \"${destinationPath}\"`)\n }\n\n await mkdir(dirname(destinationPath), {\n recursive: true,\n mode: options.directoryMode,\n })\n\n try {\n await rename(sourcePath, destinationPath)\n } catch (error) {\n if (error.code === 'EXDEV') {\n await copyFile(sourcePath, destinationPath)\n await unlink(sourcePath)\n } else {\n throw error\n }\n }\n}\n","/*\n * @adonisjs/bodyparser\n *\n * (c) AdonisJS\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nimport bytes from 'bytes'\nimport { MultipartFile } from '../file.js'\n\n/**\n * Size validator validates the file size\n */\nexport class SizeValidator {\n #file: MultipartFile\n #maximumAllowedLimit?: number | string\n #bytesLimit: number = 0\n\n validated: boolean = false\n\n /**\n * Defining the maximum bytes the file can have\n */\n get maxLimit(): number | string | undefined {\n return this.#maximumAllowedLimit\n }\n set maxLimit(limit: number | string | undefined) {\n if (this.#maximumAllowedLimit !== undefined) {\n throw new Error('Cannot reset sizeLimit after file has been validated')\n }\n\n this.validated = false\n this.#maximumAllowedLimit = limit\n\n if (this.#maximumAllowedLimit) {\n this.#bytesLimit =\n typeof this.#maximumAllowedLimit === 'string'\n ? bytes(this.#maximumAllowedLimit)\n : this.#maximumAllowedLimit\n }\n }\n\n constructor(file: MultipartFile) {\n this.#file = file\n }\n\n /**\n * Reporting error to the file\n */\n #reportError() {\n this.#file.errors.push({\n fieldName: this.#file.fieldName,\n clientName: this.#file.clientName,\n message: `File size should be less than ${bytes(this.#bytesLimit)}`,\n type: 'size',\n })\n }\n\n /**\n * Validating file size while it is getting streamed. We only mark\n * the file as `validated` when it's validation fails. Otherwise\n * we keep re-validating the file as we receive more data.\n */\n #validateWhenGettingStreamed() {\n if (this.#file.size > this.#bytesLimit) {\n this.validated = true\n this.#reportError()\n }\n }\n\n /**\n * We have the final file size after the stream has been consumed. At this\n * stage we always mark `validated = true`.\n */\n #validateAfterConsumed() {\n this.validated = true\n if (this.#file.size > this.#bytesLimit) {\n this.#reportError()\n }\n }\n\n /**\n * Validate the file size\n */\n validate() {\n if (this.validated) {\n return\n }\n\n /**\n * Do not attempt to validate when `maximumAllowedLimit` is not\n * defined.\n */\n if (this.#maximumAllowedLimit === undefined) {\n this.validated = true\n return\n }\n\n if (this.#file.state === 'streaming') {\n this.#validateWhenGettingStreamed()\n return\n }\n\n if (this.#file.state === 'consumed') {\n this.#validateAfterConsumed()\n return\n }\n }\n}\n","/*\n * @adonisjs/bodyparser\n *\n * (c) AdonisJS\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nimport { MultipartFile } from '../file.js'\n\n/**\n * Validates the file extension\n */\nexport class ExtensionValidator {\n #file: MultipartFile\n #allowedExtensions?: string[] = []\n\n validated: boolean = false\n\n /**\n * Update the expected file extensions\n */\n get extensions(): string[] | undefined {\n return this.#allowedExtensions\n }\n\n set extensions(extnames: string[] | undefined) {\n if (this.#allowedExtensions && this.#allowedExtensions.length) {\n throw new Error('Cannot update allowed extension names after file has been validated')\n }\n\n this.validated = false\n this.#allowedExtensions = extnames\n }\n\n constructor(file: MultipartFile) {\n this.#file = file\n }\n\n /**\n * Report error to the file\n */\n #reportError() {\n /**\n * File is invalid, so report the error\n */\n const suffix = this.#allowedExtensions!.length === 1 ? 'is' : 'are'\n\n const message = [\n `Invalid file extension ${this.#file.extname}.`,\n `Only ${this.#allowedExtensions!.join(', ')} ${suffix} allowed`,\n ].join(' ')\n\n this.#file.errors.push({\n fieldName: this.#file.fieldName,\n clientName: this.#file.clientName,\n message: message,\n type: 'extname',\n })\n }\n\n /**\n * Validating the file in the streaming mode. During this mode\n * we defer the validation, until we get the file extname.\n */\n #validateWhenGettingStreamed() {\n if (!this.#file.extname) {\n return\n }\n\n this.validated = true\n\n /**\n * Valid extension type\n */\n if (this.#allowedExtensions!.includes(this.#file.extname)) {\n return\n }\n\n this.#reportError()\n }\n\n /**\n * Validate the file extension after it has been streamed\n */\n #validateAfterConsumed() {\n this.validated = true\n\n /**\n * Valid extension type\n */\n if (this.#allowedExtensions!.includes(this.#file.extname || '')) {\n return\n }\n\n this.#reportError()\n }\n\n /**\n * Validate the file\n */\n validate(): void {\n /**\n * Do not validate if already validated\n */\n if (this.validated) {\n return\n }\n\n /**\n * Do not run validations, when constraints on the extension are not set\n */\n if (!Array.isArray(this.#allowedExtensions) || this.#allowedExtensions.length === 0) {\n this.validated = true\n return\n }\n\n if (this.#file.state === 'streaming') {\n this.#validateWhenGettingStreamed()\n return\n }\n\n if (this.#file.state === 'consumed') {\n this.#validateAfterConsumed()\n }\n }\n}\n"],"mappings":"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","names":[]} |