payload 3.86.0 → 3.87.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/collections/operations/count.d.ts.map +1 -1
- package/dist/collections/operations/count.js +1 -0
- package/dist/collections/operations/count.js.map +1 -1
- package/dist/collections/operations/updateByID.d.ts.map +1 -1
- package/dist/collections/operations/updateByID.js +1 -0
- package/dist/collections/operations/updateByID.js.map +1 -1
- package/dist/fields/config/types.js +1 -1
- package/dist/fields/config/types.js.map +1 -1
- package/dist/fields/hooks/beforeValidate/promise.d.ts.map +1 -1
- package/dist/fields/hooks/beforeValidate/promise.js +5 -3
- package/dist/fields/hooks/beforeValidate/promise.js.map +1 -1
- package/dist/fields/hooks/beforeValidate/stripNullRows.d.ts +10 -0
- package/dist/fields/hooks/beforeValidate/stripNullRows.d.ts.map +1 -0
- package/dist/fields/hooks/beforeValidate/stripNullRows.js +18 -0
- package/dist/fields/hooks/beforeValidate/stripNullRows.js.map +1 -0
- package/dist/fields/hooks/beforeValidate/traverseFields.d.ts.map +1 -1
- package/dist/fields/hooks/beforeValidate/traverseFields.js +2 -0
- package/dist/fields/hooks/beforeValidate/traverseFields.js.map +1 -1
- package/dist/folders/utils/getFoldersAndDocumentsFromJoin.d.ts.map +1 -1
- package/dist/folders/utils/getFoldersAndDocumentsFromJoin.js +1 -0
- package/dist/folders/utils/getFoldersAndDocumentsFromJoin.js.map +1 -1
- package/dist/globals/operations/countGlobalVersions.d.ts.map +1 -1
- package/dist/globals/operations/countGlobalVersions.js +1 -0
- package/dist/globals/operations/countGlobalVersions.js.map +1 -1
- package/dist/index.bundled.d.ts +71 -15
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/uploads/generateFileData.js +4 -1
- package/dist/uploads/generateFileData.js.map +1 -1
- package/dist/uploads/getImageSize.d.ts +12 -1
- package/dist/uploads/getImageSize.d.ts.map +1 -1
- package/dist/uploads/getImageSize.js +20 -18
- package/dist/uploads/getImageSize.js.map +1 -1
- package/dist/uploads/getImageSize.spec.js +298 -0
- package/dist/uploads/getImageSize.spec.js.map +1 -0
- package/dist/uploads/probeImageSize.d.ts +39 -0
- package/dist/uploads/probeImageSize.d.ts.map +1 -0
- package/dist/uploads/probeImageSize.js +374 -0
- package/dist/uploads/probeImageSize.js.map +1 -0
- package/dist/uploads/probeImageSize.spec.js +366 -0
- package/dist/uploads/probeImageSize.spec.js.map +1 -0
- package/dist/utilities/parseParams/index.d.ts +2 -3
- package/dist/utilities/parseParams/index.d.ts.map +1 -1
- package/dist/utilities/parseParams/index.js.map +1 -1
- package/dist/utilities/traverseFields.d.ts.map +1 -1
- package/dist/utilities/traverseFields.js +20 -22
- package/dist/utilities/traverseFields.js.map +1 -1
- package/dist/utilities/traverseFields.spec.js +112 -0
- package/dist/utilities/traverseFields.spec.js.map +1 -1
- package/dist/utilities/unflattenData.d.ts +3 -0
- package/dist/utilities/unflattenData.d.ts.map +1 -0
- package/dist/utilities/unflattenData.js +14 -0
- package/dist/utilities/unflattenData.js.map +1 -0
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/uploads/generateFileData.ts"],"sourcesContent":["import type { OutputInfo, Sharp, SharpOptions } from 'sharp'\n\nimport { fileTypeFromBuffer } from 'file-type'\nimport fs from 'fs/promises'\nimport sanitize from 'sanitize-filename'\n\nimport type { Collection } from '../collections/config/types.js'\nimport type { SanitizedConfig } from '../config/types.js'\nimport type { Document, PayloadRequest } from '../types/index.js'\nimport type { FileData, FileToSave, ProbedImageSize, UploadEdits } from './types.js'\n\nimport { FileRetrievalError, FileUploadError, Forbidden, MissingFile } from '../errors/index.js'\nimport { isNumber } from '../utilities/isNumber.js'\nimport { canResizeImage } from './canResizeImage.js'\nimport { checkFileRestrictions } from './checkFileRestrictions.js'\nimport { cropImage } from './cropImage.js'\nimport { getExternalFile } from './getExternalFile.js'\nimport { getFileByPath } from './getFileByPath.js'\nimport { getImageSize } from './getImageSize.js'\nimport { getSafeFileName } from './getSafeFilename.js'\nimport { createImageSizes } from './image-resizing/createImageSizes.js'\nimport { isImage } from './isImage.js'\nimport { optionallyAppendMetadata } from './optionallyAppendMetadata.js'\ntype Args<T> = {\n collection: Collection\n config: SanitizedConfig\n data: T\n draft?: boolean\n isDuplicating?: boolean\n operation: 'create' | 'update'\n originalDoc?: T\n overwriteExistingFiles?: boolean\n req: PayloadRequest\n throwOnMissingFile?: boolean\n}\n\ntype Result<T> = Promise<{\n data: T\n files: FileToSave[]\n}>\n\nconst shouldReupload = (\n uploadEdits: UploadEdits,\n fileData: Record<string, unknown> | undefined,\n) => {\n if (!fileData) {\n return false\n }\n\n if (uploadEdits.crop || uploadEdits.heightInPixels || uploadEdits.widthInPixels) {\n return true\n }\n\n // Since uploadEdits always has focalPoint, compare to the value in the data if it was changed\n if (uploadEdits.focalPoint) {\n const incomingFocalX = uploadEdits.focalPoint.x\n const incomingFocalY = uploadEdits.focalPoint.y\n\n const currentFocalX = 'focalX' in fileData && fileData.focalX\n const currentFocalY = 'focalY' in fileData && fileData.focalY\n\n const isEqual = incomingFocalX === currentFocalX && incomingFocalY === currentFocalY\n return !isEqual\n }\n\n return false\n}\n\nexport const generateFileData = async <T>({\n collection: { config: collectionConfig },\n data,\n draft,\n isDuplicating,\n operation,\n originalDoc,\n overwriteExistingFiles,\n req,\n throwOnMissingFile,\n}: Args<T>): Result<T> => {\n if (!collectionConfig.upload) {\n return {\n data,\n files: [],\n }\n }\n\n const { serverURL, sharp } = req.payload.config\n\n let file = isDuplicating ? undefined : req.file\n\n const uploadEdits = parseUploadEditsFromReqOrIncomingData({\n data,\n isDuplicating,\n operation,\n originalDoc,\n req,\n })\n\n const {\n constructorOptions,\n disableLocalStorage,\n focalPoint: focalPointEnabled = true,\n formatOptions,\n imageSizes,\n resizeOptions,\n staticDir,\n trimOptions,\n withMetadata,\n } = collectionConfig.upload\n\n const staticPath = staticDir\n\n const incomingFileData: Document = isDuplicating ? originalDoc : data\n let isLocalFile = false\n\n if (\n !file &&\n (isDuplicating || shouldReupload(uploadEdits, incomingFileData as Record<string, unknown>))\n ) {\n const { filename, url } = incomingFileData as unknown as FileData\n if (filename && (filename.includes('../') || filename.includes('..\\\\'))) {\n throw new Forbidden(req.t)\n }\n\n if ((serverURL && url?.startsWith(serverURL)) || url?.startsWith('/')) {\n isLocalFile = true\n }\n\n try {\n if (!disableLocalStorage && isLocalFile) {\n // File is stored locally\n const filePath = `${staticPath}/${filename}`\n const response = await getFileByPath(filePath)\n file = response\n overwriteExistingFiles = true\n } else if (filename && url) {\n // File is remote\n file = await getExternalFile({\n data: incomingFileData as unknown as FileData,\n req,\n uploadConfig: collectionConfig.upload,\n })\n overwriteExistingFiles = true\n }\n } catch (err: unknown) {\n throw new FileRetrievalError(req.t, err instanceof Error ? err.message : undefined)\n }\n }\n\n if (isDuplicating) {\n overwriteExistingFiles = false\n }\n\n if (!file) {\n if (throwOnMissingFile) {\n throw new MissingFile(req.t)\n }\n\n return {\n data: incomingFileData!,\n files: [],\n }\n }\n\n await checkFileRestrictions({\n collection: collectionConfig,\n file,\n req,\n })\n\n if (!disableLocalStorage) {\n await fs.mkdir(staticPath!, { recursive: true })\n }\n\n let newData = incomingFileData as T\n const filesToSave: FileToSave[] = []\n const fileData: Partial<FileData> = {}\n const fileIsAnimatedType = ['image/avif', 'image/gif', 'image/webp'].includes(file.mimetype)\n const cropData =\n typeof uploadEdits === 'object' && 'crop' in uploadEdits ? uploadEdits.crop : undefined\n\n try {\n const fileSupportsResize = canResizeImage(file.mimetype)\n let fsSafeName: string\n let sharpFile: Sharp | undefined\n let dimensions: ProbedImageSize | undefined\n let fileBuffer!: { data: Buffer; info: OutputInfo }\n let ext\n let mime: string\n const fileHasAdjustments =\n fileSupportsResize &&\n Boolean(\n resizeOptions || formatOptions || trimOptions || constructorOptions || file.tempFilePath,\n )\n\n const sharpOptions: SharpOptions = { ...constructorOptions }\n\n if (fileIsAnimatedType) {\n sharpOptions.animated = true\n }\n\n if (sharp && (fileIsAnimatedType || fileHasAdjustments)) {\n if (file.tempFilePath) {\n sharpFile = sharp(file.tempFilePath, sharpOptions).rotate() // pass rotate() to auto-rotate based on EXIF data. https://github.com/payloadcms/payload/pull/3081\n } else {\n sharpFile = sharp(file.data, sharpOptions).rotate() // pass rotate() to auto-rotate based on EXIF data. https://github.com/payloadcms/payload/pull/3081\n }\n\n if (fileHasAdjustments) {\n if (resizeOptions) {\n sharpFile = sharpFile.resize(resizeOptions)\n }\n if (formatOptions) {\n sharpFile = sharpFile.toFormat(formatOptions.format, formatOptions.options)\n }\n if (trimOptions) {\n sharpFile = sharpFile.trim(trimOptions)\n }\n }\n }\n\n if (fileSupportsResize || isImage(file.mimetype)) {\n dimensions = await getImageSize(file)\n fileData.width = dimensions.width\n fileData.height = dimensions.height\n }\n\n if (sharpFile) {\n const metadata = await sharpFile.metadata()\n sharpFile = await optionallyAppendMetadata({\n req,\n sharpFile,\n withMetadata: withMetadata!,\n })\n fileBuffer = await sharpFile.toBuffer({ resolveWithObject: true })\n ;({ ext, mime } = (await fileTypeFromBuffer(fileBuffer.data))!) // This is getting an incorrect gif height back.\n fileData.width = fileBuffer.info.width\n fileData.height = fileBuffer.info.height\n fileData.filesize = fileBuffer.info.size\n\n // Animated GIFs + WebP aggregate the height from every frame, so we need to use divide by number of pages\n if (metadata.pages) {\n fileData.height = fileBuffer.info.height / metadata.pages\n fileData.filesize = fileBuffer.data.length\n }\n } else {\n mime = file.mimetype\n fileData.filesize = file.size\n\n if (file.name.includes('.')) {\n ext = file.name.split('.').pop()?.split('?')[0]\n } else {\n ext = ''\n }\n }\n\n // Adjust SVG mime type. fromBuffer modifies it.\n if (mime === 'application/xml' && ext === 'svg') {\n mime = 'image/svg+xml'\n }\n fileData.mimeType = mime\n\n const baseFilename = sanitize(file.name.substring(0, file.name.lastIndexOf('.')) || file.name)\n fsSafeName = `${baseFilename}${ext ? `.${ext}` : ''}`\n\n if (!overwriteExistingFiles) {\n // Extract prefix if present (added by plugin-cloud-storage)\n const prefix = (data as Record<string, unknown>)?.prefix as string | undefined\n fsSafeName = await getSafeFileName({\n collectionSlug: collectionConfig.slug,\n desiredFilename: fsSafeName,\n prefix,\n req,\n staticPath: staticPath!,\n })\n }\n\n fileData.filename = fsSafeName\n\n let fileForResize = file\n\n if (cropData && sharp) {\n const { data: croppedImage, info } = await cropImage({\n cropData,\n dimensions: dimensions!,\n file,\n heightInPixels: uploadEdits.heightInPixels!,\n req,\n sharp,\n widthInPixels: uploadEdits.widthInPixels!,\n withMetadata,\n })\n\n // Apply resize after cropping to ensure it conforms to resizeOptions\n if (resizeOptions && !resizeOptions.withoutEnlargement) {\n const resizedAfterCrop = await sharp(croppedImage)\n .resize({\n fit: resizeOptions?.fit || 'cover',\n height: resizeOptions?.height,\n position: resizeOptions?.position || 'center',\n width: resizeOptions?.width,\n })\n .toBuffer({ resolveWithObject: true })\n\n filesToSave.push({\n buffer: resizedAfterCrop.data,\n path: `${staticPath}/${fsSafeName}`,\n })\n\n fileForResize = {\n ...fileForResize,\n data: resizedAfterCrop.data,\n size: resizedAfterCrop.info.size,\n }\n\n fileData.width = resizedAfterCrop.info.width\n fileData.height = resizedAfterCrop.info.height\n if (fileIsAnimatedType) {\n const metadata = await sharpFile!.metadata()\n fileData.height = metadata.pages\n ? resizedAfterCrop.info.height / metadata.pages\n : resizedAfterCrop.info.height\n }\n fileData.filesize = resizedAfterCrop.info.size\n } else {\n // If resizeOptions is not present, just save the cropped image\n filesToSave.push({\n buffer: croppedImage,\n path: `${staticPath}/${fsSafeName}`,\n })\n\n fileForResize = {\n ...file,\n data: croppedImage,\n size: info.size,\n }\n\n fileData.width = info.width\n fileData.height = info.height\n if (fileIsAnimatedType) {\n const metadata = await sharpFile!.metadata()\n fileData.height = metadata.pages ? info.height / metadata.pages : info.height\n }\n fileData.filesize = info.size\n }\n\n if (file.tempFilePath) {\n await fs.writeFile(file.tempFilePath, croppedImage) // write fileBuffer to the temp path\n } else {\n req.file = fileForResize\n }\n } else {\n // For non-image files with useTempFiles, read the buffer from the temp file\n // since file.data is empty when using temp files\n let bufferToSave: Buffer\n if (fileBuffer?.data) {\n bufferToSave = fileBuffer.data\n } else if (file.tempFilePath) {\n bufferToSave = await fs.readFile(file.tempFilePath)\n } else {\n bufferToSave = file.data\n }\n\n filesToSave.push({\n buffer: bufferToSave,\n path: `${staticPath}/${fsSafeName}`,\n })\n\n // If using temp files and the image is being resized, write the file to the temp path\n if (fileBuffer?.data || bufferToSave.length > 0) {\n if (file.tempFilePath) {\n await fs.writeFile(file.tempFilePath, fileBuffer?.data || bufferToSave) // write fileBuffer to the temp path\n } else {\n // Assign the _possibly modified_ file to the request object\n req.file = {\n ...file,\n data: fileBuffer?.data || bufferToSave,\n size: fileBuffer?.info.size,\n }\n }\n }\n }\n\n if (fileSupportsResize && (Array.isArray(imageSizes) || focalPointEnabled !== false)) {\n req.payloadUploadSizes = {}\n // Focal point adjustments\n const focalPoint =\n focalPointEnabled && uploadEdits?.focalPoint\n ? {\n x: isNumber(uploadEdits.focalPoint.x) ? Math.round(uploadEdits.focalPoint.x) : 50,\n y: isNumber(uploadEdits.focalPoint.y) ? Math.round(uploadEdits.focalPoint.y) : 50,\n }\n : undefined\n\n const { sizeData, sizesToSave } = await createImageSizes({\n config: collectionConfig,\n dimensions: !cropData\n ? dimensions!\n : {\n ...dimensions,\n height: fileData.height!,\n width: fileData.width!,\n },\n file: fileForResize,\n focalPoint,\n mimeType: fileData.mimeType,\n req,\n savedFilename: fsSafeName || file.name,\n sharp,\n staticPath: staticPath!,\n withMetadata,\n })\n\n fileData.sizes = sizeData\n fileData.focalX = focalPoint?.x\n fileData.focalY = focalPoint?.y\n filesToSave.push(...sizesToSave)\n }\n } catch (err) {\n req.payload.logger.error(err)\n throw new FileUploadError(req.t)\n }\n\n newData = {\n ...newData,\n ...fileData,\n ...(draft ? { _status: 'draft' } : {}),\n }\n\n return {\n data: newData,\n files: filesToSave,\n }\n}\n\n/**\n * Parse upload edits from req or incoming data\n */\nfunction parseUploadEditsFromReqOrIncomingData(args: {\n data: unknown\n isDuplicating?: boolean\n operation: 'create' | 'update'\n originalDoc: unknown\n req: PayloadRequest\n}): UploadEdits {\n const { data, isDuplicating, operation, originalDoc, req } = args\n\n // Get intended focal point change from query string or incoming data\n const uploadEdits =\n req.query?.uploadEdits && typeof req.query.uploadEdits === 'object'\n ? (req.query.uploadEdits as UploadEdits)\n : {}\n\n if (uploadEdits.focalPoint) {\n return uploadEdits\n }\n\n const incomingData = data as FileData\n const origDoc = originalDoc as FileData\n\n if (origDoc && 'focalX' in origDoc && 'focalY' in origDoc) {\n // If no change in focal point, return undefined.\n // This prevents a refocal operation triggered from admin, because it always sends the focal point.\n if (incomingData.focalX === origDoc.focalX && incomingData.focalY === origDoc.focalY) {\n return undefined!\n }\n\n if (isDuplicating) {\n uploadEdits.focalPoint = {\n x: incomingData?.focalX || origDoc.focalX!,\n y: incomingData?.focalY || origDoc.focalY!,\n }\n return uploadEdits\n }\n }\n\n if (incomingData?.focalX && incomingData?.focalY) {\n uploadEdits.focalPoint = {\n x: incomingData.focalX,\n y: incomingData.focalY,\n }\n return uploadEdits\n }\n\n // If no focal point is set, default to center\n if (operation === 'create') {\n uploadEdits.focalPoint = {\n x: 50,\n y: 50,\n }\n }\n\n return uploadEdits\n}\n"],"names":["fileTypeFromBuffer","fs","sanitize","FileRetrievalError","FileUploadError","Forbidden","MissingFile","isNumber","canResizeImage","checkFileRestrictions","cropImage","getExternalFile","getFileByPath","getImageSize","getSafeFileName","createImageSizes","isImage","optionallyAppendMetadata","shouldReupload","uploadEdits","fileData","crop","heightInPixels","widthInPixels","focalPoint","incomingFocalX","x","incomingFocalY","y","currentFocalX","focalX","currentFocalY","focalY","isEqual","generateFileData","collection","config","collectionConfig","data","draft","isDuplicating","operation","originalDoc","overwriteExistingFiles","req","throwOnMissingFile","upload","files","serverURL","sharp","payload","file","undefined","parseUploadEditsFromReqOrIncomingData","constructorOptions","disableLocalStorage","focalPointEnabled","formatOptions","imageSizes","resizeOptions","staticDir","trimOptions","withMetadata","staticPath","incomingFileData","isLocalFile","filename","url","includes","t","startsWith","filePath","response","uploadConfig","err","Error","message","mkdir","recursive","newData","filesToSave","fileIsAnimatedType","mimetype","cropData","fileSupportsResize","fsSafeName","sharpFile","dimensions","fileBuffer","ext","mime","fileHasAdjustments","Boolean","tempFilePath","sharpOptions","animated","rotate","resize","toFormat","format","options","trim","width","height","metadata","toBuffer","resolveWithObject","info","filesize","size","pages","length","name","split","pop","mimeType","baseFilename","substring","lastIndexOf","prefix","collectionSlug","slug","desiredFilename","fileForResize","croppedImage","withoutEnlargement","resizedAfterCrop","fit","position","push","buffer","path","writeFile","bufferToSave","readFile","Array","isArray","payloadUploadSizes","Math","round","sizeData","sizesToSave","savedFilename","sizes","logger","error","_status","args","query","incomingData","origDoc"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/uploads/generateFileData.ts"],"sourcesContent":["import type { OutputInfo, Sharp, SharpOptions } from 'sharp'\n\nimport { fileTypeFromBuffer } from 'file-type'\nimport fs from 'fs/promises'\nimport sanitize from 'sanitize-filename'\n\nimport type { Collection } from '../collections/config/types.js'\nimport type { SanitizedConfig } from '../config/types.js'\nimport type { Document, PayloadRequest } from '../types/index.js'\nimport type { FileData, FileToSave, ProbedImageSize, UploadEdits } from './types.js'\n\nimport { FileRetrievalError, FileUploadError, Forbidden, MissingFile } from '../errors/index.js'\nimport { isNumber } from '../utilities/isNumber.js'\nimport { canResizeImage } from './canResizeImage.js'\nimport { checkFileRestrictions } from './checkFileRestrictions.js'\nimport { cropImage } from './cropImage.js'\nimport { getExternalFile } from './getExternalFile.js'\nimport { getFileByPath } from './getFileByPath.js'\nimport { getImageSize } from './getImageSize.js'\nimport { getSafeFileName } from './getSafeFilename.js'\nimport { createImageSizes } from './image-resizing/createImageSizes.js'\nimport { isImage } from './isImage.js'\nimport { optionallyAppendMetadata } from './optionallyAppendMetadata.js'\ntype Args<T> = {\n collection: Collection\n config: SanitizedConfig\n data: T\n draft?: boolean\n isDuplicating?: boolean\n operation: 'create' | 'update'\n originalDoc?: T\n overwriteExistingFiles?: boolean\n req: PayloadRequest\n throwOnMissingFile?: boolean\n}\n\ntype Result<T> = Promise<{\n data: T\n files: FileToSave[]\n}>\n\nconst shouldReupload = (\n uploadEdits: UploadEdits,\n fileData: Record<string, unknown> | undefined,\n) => {\n if (!fileData) {\n return false\n }\n\n if (uploadEdits.crop || uploadEdits.heightInPixels || uploadEdits.widthInPixels) {\n return true\n }\n\n // Since uploadEdits always has focalPoint, compare to the value in the data if it was changed\n if (uploadEdits.focalPoint) {\n const incomingFocalX = uploadEdits.focalPoint.x\n const incomingFocalY = uploadEdits.focalPoint.y\n\n const currentFocalX = 'focalX' in fileData && fileData.focalX\n const currentFocalY = 'focalY' in fileData && fileData.focalY\n\n const isEqual = incomingFocalX === currentFocalX && incomingFocalY === currentFocalY\n return !isEqual\n }\n\n return false\n}\n\nexport const generateFileData = async <T>({\n collection: { config: collectionConfig },\n data,\n draft,\n isDuplicating,\n operation,\n originalDoc,\n overwriteExistingFiles,\n req,\n throwOnMissingFile,\n}: Args<T>): Result<T> => {\n if (!collectionConfig.upload) {\n return {\n data,\n files: [],\n }\n }\n\n const { serverURL, sharp } = req.payload.config\n\n let file = isDuplicating ? undefined : req.file\n\n const uploadEdits = parseUploadEditsFromReqOrIncomingData({\n data,\n isDuplicating,\n operation,\n originalDoc,\n req,\n })\n\n const {\n constructorOptions,\n disableLocalStorage,\n focalPoint: focalPointEnabled = true,\n formatOptions,\n imageSizes,\n resizeOptions,\n staticDir,\n trimOptions,\n withMetadata,\n } = collectionConfig.upload\n\n const staticPath = staticDir\n\n const incomingFileData: Document = isDuplicating ? originalDoc : data\n let isLocalFile = false\n\n if (\n !file &&\n (isDuplicating || shouldReupload(uploadEdits, incomingFileData as Record<string, unknown>))\n ) {\n const { filename, url } = incomingFileData as unknown as FileData\n if (filename && (filename.includes('../') || filename.includes('..\\\\'))) {\n throw new Forbidden(req.t)\n }\n\n if ((serverURL && url?.startsWith(serverURL)) || url?.startsWith('/')) {\n isLocalFile = true\n }\n\n try {\n if (!disableLocalStorage && isLocalFile) {\n // File is stored locally\n const filePath = `${staticPath}/${filename}`\n const response = await getFileByPath(filePath)\n file = response\n overwriteExistingFiles = true\n } else if (filename && url) {\n // File is remote\n file = await getExternalFile({\n data: incomingFileData as unknown as FileData,\n req,\n uploadConfig: collectionConfig.upload,\n })\n overwriteExistingFiles = true\n }\n } catch (err: unknown) {\n throw new FileRetrievalError(req.t, err instanceof Error ? err.message : undefined)\n }\n }\n\n if (isDuplicating) {\n overwriteExistingFiles = false\n }\n\n if (!file) {\n if (throwOnMissingFile) {\n throw new MissingFile(req.t)\n }\n\n return {\n data: incomingFileData!,\n files: [],\n }\n }\n\n await checkFileRestrictions({\n collection: collectionConfig,\n file,\n req,\n })\n\n if (!disableLocalStorage) {\n await fs.mkdir(staticPath!, { recursive: true })\n }\n\n let newData = incomingFileData as T\n const filesToSave: FileToSave[] = []\n const fileData: Partial<FileData> = {}\n const fileIsAnimatedType = ['image/avif', 'image/gif', 'image/webp'].includes(file.mimetype)\n const cropData =\n typeof uploadEdits === 'object' && 'crop' in uploadEdits ? uploadEdits.crop : undefined\n\n try {\n const fileSupportsResize = canResizeImage(file.mimetype)\n let fsSafeName: string\n let sharpFile: Sharp | undefined\n let dimensions: ProbedImageSize | undefined\n let fileBuffer!: { data: Buffer; info: OutputInfo }\n let ext\n let mime: string\n const fileHasAdjustments =\n fileSupportsResize &&\n Boolean(\n resizeOptions || formatOptions || trimOptions || constructorOptions || file.tempFilePath,\n )\n\n const sharpOptions: SharpOptions = { ...constructorOptions }\n\n if (fileIsAnimatedType) {\n sharpOptions.animated = true\n }\n\n if (sharp && (fileIsAnimatedType || fileHasAdjustments)) {\n if (file.tempFilePath) {\n sharpFile = sharp(file.tempFilePath, sharpOptions).rotate() // pass rotate() to auto-rotate based on EXIF data. https://github.com/payloadcms/payload/pull/3081\n } else {\n sharpFile = sharp(file.data, sharpOptions).rotate() // pass rotate() to auto-rotate based on EXIF data. https://github.com/payloadcms/payload/pull/3081\n }\n\n if (fileHasAdjustments) {\n if (resizeOptions) {\n sharpFile = sharpFile.resize(resizeOptions)\n }\n if (formatOptions) {\n sharpFile = sharpFile.toFormat(formatOptions.format, formatOptions.options)\n }\n if (trimOptions) {\n sharpFile = sharpFile.trim(trimOptions)\n }\n }\n }\n\n if (fileSupportsResize || isImage(file.mimetype)) {\n dimensions = await getImageSize({ file, sharp })\n fileData.width = dimensions.width\n fileData.height = dimensions.height\n }\n\n if (sharpFile) {\n const metadata = await sharpFile.metadata()\n sharpFile = await optionallyAppendMetadata({\n req,\n sharpFile,\n withMetadata: withMetadata!,\n })\n fileBuffer = await sharpFile.toBuffer({ resolveWithObject: true })\n ;({ ext, mime } = (await fileTypeFromBuffer(fileBuffer.data))!) // This is getting an incorrect gif height back.\n fileData.width = fileBuffer.info.width\n fileData.height = fileBuffer.info.height\n fileData.filesize = fileBuffer.info.size\n\n // Animated GIFs + WebP aggregate the height from every frame, so we need to use divide by number of pages\n if (metadata.pages) {\n fileData.height = fileBuffer.info.height / metadata.pages\n fileData.filesize = fileBuffer.data.length\n }\n } else {\n mime = file.mimetype\n fileData.filesize = file.size\n\n if (file.name.includes('.')) {\n ext = file.name.split('.').pop()?.split('?')[0]\n } else {\n ext = ''\n }\n }\n\n // Adjust SVG mime type. fromBuffer modifies it.\n if (mime === 'application/xml' && ext === 'svg') {\n mime = 'image/svg+xml'\n }\n fileData.mimeType = mime\n\n const baseFilename = sanitize(file.name.substring(0, file.name.lastIndexOf('.')) || file.name)\n fsSafeName = `${baseFilename}${ext ? `.${ext}` : ''}`\n\n if (!overwriteExistingFiles) {\n // Extract prefix if present (added by plugin-cloud-storage)\n const prefix = (data as Record<string, unknown>)?.prefix as string | undefined\n fsSafeName = await getSafeFileName({\n collectionSlug: collectionConfig.slug,\n desiredFilename: fsSafeName,\n prefix,\n req,\n staticPath: staticPath!,\n })\n }\n\n fileData.filename = fsSafeName\n\n let fileForResize = file\n\n if (cropData && sharp) {\n const { data: croppedImage, info } = await cropImage({\n cropData,\n dimensions: dimensions!,\n file,\n heightInPixels: uploadEdits.heightInPixels!,\n req,\n sharp,\n widthInPixels: uploadEdits.widthInPixels!,\n withMetadata,\n })\n\n // Apply resize after cropping to ensure it conforms to resizeOptions\n if (resizeOptions && !resizeOptions.withoutEnlargement) {\n const resizedAfterCrop = await sharp(croppedImage)\n .resize({\n fit: resizeOptions?.fit || 'cover',\n height: resizeOptions?.height,\n position: resizeOptions?.position || 'center',\n width: resizeOptions?.width,\n })\n .toBuffer({ resolveWithObject: true })\n\n filesToSave.push({\n buffer: resizedAfterCrop.data,\n path: `${staticPath}/${fsSafeName}`,\n })\n\n fileForResize = {\n ...fileForResize,\n data: resizedAfterCrop.data,\n size: resizedAfterCrop.info.size,\n }\n\n fileData.width = resizedAfterCrop.info.width\n fileData.height = resizedAfterCrop.info.height\n if (fileIsAnimatedType) {\n const metadata = await sharpFile!.metadata()\n fileData.height = metadata.pages\n ? resizedAfterCrop.info.height / metadata.pages\n : resizedAfterCrop.info.height\n }\n fileData.filesize = resizedAfterCrop.info.size\n } else {\n // If resizeOptions is not present, just save the cropped image\n filesToSave.push({\n buffer: croppedImage,\n path: `${staticPath}/${fsSafeName}`,\n })\n\n fileForResize = {\n ...file,\n data: croppedImage,\n size: info.size,\n }\n\n fileData.width = info.width\n fileData.height = info.height\n if (fileIsAnimatedType) {\n const metadata = await sharpFile!.metadata()\n fileData.height = metadata.pages ? info.height / metadata.pages : info.height\n }\n fileData.filesize = info.size\n }\n\n if (file.tempFilePath) {\n await fs.writeFile(file.tempFilePath, croppedImage) // write fileBuffer to the temp path\n } else {\n req.file = fileForResize\n }\n } else {\n // For non-image files with useTempFiles, read the buffer from the temp file\n // since file.data is empty when using temp files\n let bufferToSave: Buffer\n if (fileBuffer?.data) {\n bufferToSave = fileBuffer.data\n } else if (file.tempFilePath) {\n bufferToSave = await fs.readFile(file.tempFilePath)\n } else {\n bufferToSave = file.data\n }\n\n filesToSave.push({\n buffer: bufferToSave,\n path: `${staticPath}/${fsSafeName}`,\n })\n\n // If using temp files and the image is being resized, write the file to the temp path\n if (fileBuffer?.data || bufferToSave.length > 0) {\n if (file.tempFilePath) {\n await fs.writeFile(file.tempFilePath, fileBuffer?.data || bufferToSave) // write fileBuffer to the temp path\n } else {\n // Assign the _possibly modified_ file to the request object\n req.file = {\n ...file,\n data: fileBuffer?.data || bufferToSave,\n size: fileBuffer?.info.size,\n }\n }\n }\n }\n\n if (fileSupportsResize && (Array.isArray(imageSizes) || focalPointEnabled !== false)) {\n req.payloadUploadSizes = {}\n // Focal point adjustments\n const focalPoint =\n focalPointEnabled && uploadEdits?.focalPoint\n ? {\n x: isNumber(uploadEdits.focalPoint.x) ? Math.round(uploadEdits.focalPoint.x) : 50,\n y: isNumber(uploadEdits.focalPoint.y) ? Math.round(uploadEdits.focalPoint.y) : 50,\n }\n : undefined\n\n const { sizeData, sizesToSave } = await createImageSizes({\n config: collectionConfig,\n dimensions: !cropData\n ? dimensions!\n : {\n ...dimensions,\n height: fileData.height!,\n width: fileData.width!,\n },\n file: fileForResize,\n focalPoint,\n mimeType: fileData.mimeType,\n req,\n savedFilename: fsSafeName || file.name,\n sharp,\n staticPath: staticPath!,\n withMetadata,\n })\n\n fileData.sizes = sizeData\n fileData.focalX = focalPoint?.x\n fileData.focalY = focalPoint?.y\n filesToSave.push(...sizesToSave)\n }\n } catch (err) {\n req.payload.logger.error(err)\n throw new FileUploadError(req.t)\n }\n\n newData = {\n ...newData,\n ...fileData,\n ...(draft ? { _status: 'draft' } : {}),\n }\n\n return {\n data: newData,\n files: filesToSave,\n }\n}\n\n/**\n * Parse upload edits from req or incoming data\n */\nfunction parseUploadEditsFromReqOrIncomingData(args: {\n data: unknown\n isDuplicating?: boolean\n operation: 'create' | 'update'\n originalDoc: unknown\n req: PayloadRequest\n}): UploadEdits {\n const { data, isDuplicating, operation, originalDoc, req } = args\n\n // Get intended focal point change from query string or incoming data\n const uploadEdits =\n req.query?.uploadEdits && typeof req.query.uploadEdits === 'object'\n ? (req.query.uploadEdits as UploadEdits)\n : {}\n\n if (uploadEdits.focalPoint) {\n return uploadEdits\n }\n\n const incomingData = data as FileData\n const origDoc = originalDoc as FileData\n\n if (origDoc && 'focalX' in origDoc && 'focalY' in origDoc) {\n // If no change in focal point, return undefined.\n // This prevents a refocal operation triggered from admin, because it always sends the focal point.\n if (incomingData.focalX === origDoc.focalX && incomingData.focalY === origDoc.focalY) {\n return undefined!\n }\n\n if (isDuplicating) {\n uploadEdits.focalPoint = {\n x: incomingData?.focalX || origDoc.focalX!,\n y: incomingData?.focalY || origDoc.focalY!,\n }\n return uploadEdits\n }\n }\n\n if (incomingData?.focalX && incomingData?.focalY) {\n uploadEdits.focalPoint = {\n x: incomingData.focalX,\n y: incomingData.focalY,\n }\n return uploadEdits\n }\n\n // If no focal point is set, default to center\n if (operation === 'create') {\n uploadEdits.focalPoint = {\n x: 50,\n y: 50,\n }\n }\n\n return uploadEdits\n}\n"],"names":["fileTypeFromBuffer","fs","sanitize","FileRetrievalError","FileUploadError","Forbidden","MissingFile","isNumber","canResizeImage","checkFileRestrictions","cropImage","getExternalFile","getFileByPath","getImageSize","getSafeFileName","createImageSizes","isImage","optionallyAppendMetadata","shouldReupload","uploadEdits","fileData","crop","heightInPixels","widthInPixels","focalPoint","incomingFocalX","x","incomingFocalY","y","currentFocalX","focalX","currentFocalY","focalY","isEqual","generateFileData","collection","config","collectionConfig","data","draft","isDuplicating","operation","originalDoc","overwriteExistingFiles","req","throwOnMissingFile","upload","files","serverURL","sharp","payload","file","undefined","parseUploadEditsFromReqOrIncomingData","constructorOptions","disableLocalStorage","focalPointEnabled","formatOptions","imageSizes","resizeOptions","staticDir","trimOptions","withMetadata","staticPath","incomingFileData","isLocalFile","filename","url","includes","t","startsWith","filePath","response","uploadConfig","err","Error","message","mkdir","recursive","newData","filesToSave","fileIsAnimatedType","mimetype","cropData","fileSupportsResize","fsSafeName","sharpFile","dimensions","fileBuffer","ext","mime","fileHasAdjustments","Boolean","tempFilePath","sharpOptions","animated","rotate","resize","toFormat","format","options","trim","width","height","metadata","toBuffer","resolveWithObject","info","filesize","size","pages","length","name","split","pop","mimeType","baseFilename","substring","lastIndexOf","prefix","collectionSlug","slug","desiredFilename","fileForResize","croppedImage","withoutEnlargement","resizedAfterCrop","fit","position","push","buffer","path","writeFile","bufferToSave","readFile","Array","isArray","payloadUploadSizes","Math","round","sizeData","sizesToSave","savedFilename","sizes","logger","error","_status","args","query","incomingData","origDoc"],"mappings":"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"}
|
|
@@ -1,4 +1,15 @@
|
|
|
1
|
+
import type { SharpDependency } from '../config/types.js';
|
|
1
2
|
import type { PayloadRequest } from '../types/index.js';
|
|
2
3
|
import type { ProbedImageSize } from './types.js';
|
|
3
|
-
|
|
4
|
+
type Args = {
|
|
5
|
+
file: PayloadRequest['file'];
|
|
6
|
+
/**
|
|
7
|
+
* The configured `sharp` instance, when available. Preferred for reading
|
|
8
|
+
* dimensions because it covers every format sharp can process. Falls back to
|
|
9
|
+
* the dependency-free probe when sharp is not configured.
|
|
10
|
+
*/
|
|
11
|
+
sharp?: SharpDependency;
|
|
12
|
+
};
|
|
13
|
+
export declare function getImageSize({ file, sharp }: Args): Promise<ProbedImageSize>;
|
|
14
|
+
export {};
|
|
4
15
|
//# sourceMappingURL=getImageSize.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getImageSize.d.ts","sourceRoot":"","sources":["../../src/uploads/getImageSize.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"getImageSize.d.ts","sourceRoot":"","sources":["../../src/uploads/getImageSize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AACzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAIjD,KAAK,IAAI,GAAG;IACV,IAAI,EAAE,cAAc,CAAC,MAAM,CAAC,CAAA;IAC5B;;;;OAIG;IACH,KAAK,CAAC,EAAE,eAAe,CAAA;CACxB,CAAA;AAED,wBAAsB,YAAY,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,IAAI,GAAG,OAAO,CAAC,eAAe,CAAC,CAqBlF"}
|
|
@@ -1,23 +1,25 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import { probeImageSize, probeImageSizeFromPath } from './probeImageSize.js';
|
|
2
|
+
export async function getImageSize({ file, sharp }) {
|
|
3
|
+
// `tempFilePath` may be an empty string when the file is held in memory
|
|
4
|
+
const tempFilePath = file?.tempFilePath || undefined;
|
|
5
|
+
if (sharp) {
|
|
6
|
+
try {
|
|
7
|
+
const { height, width } = await sharp(tempFilePath ?? file.data).metadata();
|
|
8
|
+
if (width && height) {
|
|
9
|
+
return {
|
|
10
|
+
height,
|
|
11
|
+
width
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
} catch {
|
|
15
|
+
// sharp decodes the full image and rejects truncated/header-only files
|
|
16
|
+
// that the byte-level probe can still measure, so fall through to it
|
|
17
|
+
}
|
|
8
18
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if (file?.mimetype === 'image/tiff') {
|
|
12
|
-
const dimensions = await temporaryFileTask(async (filepath)=>{
|
|
13
|
-
await fs.writeFile(filepath, file.data);
|
|
14
|
-
return imageSizeFromFile(filepath);
|
|
15
|
-
}, {
|
|
16
|
-
extension: 'tiff'
|
|
17
|
-
});
|
|
18
|
-
return dimensions;
|
|
19
|
+
if (tempFilePath) {
|
|
20
|
+
return probeImageSizeFromPath(tempFilePath);
|
|
19
21
|
}
|
|
20
|
-
return
|
|
22
|
+
return probeImageSize(file.data);
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
//# sourceMappingURL=getImageSize.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/uploads/getImageSize.ts"],"sourcesContent":["import
|
|
1
|
+
{"version":3,"sources":["../../src/uploads/getImageSize.ts"],"sourcesContent":["import type { SharpDependency } from '../config/types.js'\nimport type { PayloadRequest } from '../types/index.js'\nimport type { ProbedImageSize } from './types.js'\n\nimport { probeImageSize, probeImageSizeFromPath } from './probeImageSize.js'\n\ntype Args = {\n file: PayloadRequest['file']\n /**\n * The configured `sharp` instance, when available. Preferred for reading\n * dimensions because it covers every format sharp can process. Falls back to\n * the dependency-free probe when sharp is not configured.\n */\n sharp?: SharpDependency\n}\n\nexport async function getImageSize({ file, sharp }: Args): Promise<ProbedImageSize> {\n // `tempFilePath` may be an empty string when the file is held in memory\n const tempFilePath = file?.tempFilePath || undefined\n\n if (sharp) {\n try {\n const { height, width } = await sharp(tempFilePath ?? file!.data).metadata()\n if (width && height) {\n return { height, width }\n }\n } catch {\n // sharp decodes the full image and rejects truncated/header-only files\n // that the byte-level probe can still measure, so fall through to it\n }\n }\n\n if (tempFilePath) {\n return probeImageSizeFromPath(tempFilePath)\n }\n\n return probeImageSize(file!.data)\n}\n"],"names":["probeImageSize","probeImageSizeFromPath","getImageSize","file","sharp","tempFilePath","undefined","height","width","data","metadata"],"mappings":"AAIA,SAASA,cAAc,EAAEC,sBAAsB,QAAQ,sBAAqB;AAY5E,OAAO,eAAeC,aAAa,EAAEC,IAAI,EAAEC,KAAK,EAAQ;IACtD,wEAAwE;IACxE,MAAMC,eAAeF,MAAME,gBAAgBC;IAE3C,IAAIF,OAAO;QACT,IAAI;YACF,MAAM,EAAEG,MAAM,EAAEC,KAAK,EAAE,GAAG,MAAMJ,MAAMC,gBAAgBF,KAAMM,IAAI,EAAEC,QAAQ;YAC1E,IAAIF,SAASD,QAAQ;gBACnB,OAAO;oBAAEA;oBAAQC;gBAAM;YACzB;QACF,EAAE,OAAM;QACN,uEAAuE;QACvE,qEAAqE;QACvE;IACF;IAEA,IAAIH,cAAc;QAChB,OAAOJ,uBAAuBI;IAChC;IAEA,OAAOL,eAAeG,KAAMM,IAAI;AAClC"}
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import sharp from 'sharp';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import { afterAll, afterEach, describe, expect, it, vi } from 'vitest';
|
|
7
|
+
import { getImageSize } from './getImageSize.js';
|
|
8
|
+
// Tracks bytes actually read off disk so the streaming test below can assert
|
|
9
|
+
// that a large file's tail is never pulled into memory
|
|
10
|
+
const counters = vi.hoisted(()=>({
|
|
11
|
+
bytesReadFromDisk: 0
|
|
12
|
+
}));
|
|
13
|
+
vi.mock('fs', async (importOriginal)=>{
|
|
14
|
+
const actual = await importOriginal();
|
|
15
|
+
return {
|
|
16
|
+
...actual,
|
|
17
|
+
createReadStream: (...args)=>{
|
|
18
|
+
const stream = actual.createReadStream(...args);
|
|
19
|
+
stream.on('data', (chunk)=>{
|
|
20
|
+
counters.bytesReadFromDisk += chunk.length;
|
|
21
|
+
});
|
|
22
|
+
return stream;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
const dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
27
|
+
const fixturesDir = path.resolve(dirname, '../../../../test/uploads');
|
|
28
|
+
// Minimal JPEG XL bare-codestream encoder (bit-packed, little-endian, LSB
|
|
29
|
+
// first). See probeImageSize.spec.ts for the full JXL parsing test suite;
|
|
30
|
+
// this only needs to produce a valid header for the sharp-fallback tests below
|
|
31
|
+
const encodeJXLCodestream = (width, height)=>{
|
|
32
|
+
const bits = [];
|
|
33
|
+
const pushBits = (value, length)=>{
|
|
34
|
+
for(let i = 0; i < length; i++){
|
|
35
|
+
bits.push(value >> i & 1);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
const pushDimension = (value)=>{
|
|
39
|
+
const n = value - 1;
|
|
40
|
+
const sizeClasses = [
|
|
41
|
+
9,
|
|
42
|
+
13,
|
|
43
|
+
18,
|
|
44
|
+
30
|
|
45
|
+
];
|
|
46
|
+
const classIndex = sizeClasses.findIndex((extraBits)=>n < 2 ** extraBits);
|
|
47
|
+
pushBits(classIndex, 2);
|
|
48
|
+
pushBits(n, sizeClasses[classIndex]);
|
|
49
|
+
};
|
|
50
|
+
pushBits(0, 1); // isSmallImage = false
|
|
51
|
+
pushDimension(height);
|
|
52
|
+
pushBits(0, 3); // widthMode = 0 (explicit dimension)
|
|
53
|
+
pushDimension(width);
|
|
54
|
+
const body = Buffer.alloc(Math.ceil(bits.length / 8));
|
|
55
|
+
bits.forEach((bit, i)=>{
|
|
56
|
+
if (bit) {
|
|
57
|
+
body[Math.floor(i / 8)] |= 1 << i % 8;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
return Buffer.concat([
|
|
61
|
+
Buffer.from([
|
|
62
|
+
0xff,
|
|
63
|
+
0x0a
|
|
64
|
+
]),
|
|
65
|
+
body
|
|
66
|
+
]);
|
|
67
|
+
};
|
|
68
|
+
const fileFor = (name)=>{
|
|
69
|
+
const data = readFileSync(path.join(fixturesDir, name));
|
|
70
|
+
// In-memory uploads carry an empty `tempFilePath`, which must fall back to `data`
|
|
71
|
+
return {
|
|
72
|
+
data,
|
|
73
|
+
mimetype: 'image/test',
|
|
74
|
+
name,
|
|
75
|
+
size: data.length,
|
|
76
|
+
tempFilePath: ''
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
describe('getImageSize', ()=>{
|
|
80
|
+
const cases = [
|
|
81
|
+
{
|
|
82
|
+
file: 'test-image.png',
|
|
83
|
+
height: 800,
|
|
84
|
+
width: 800
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
file: 'test-image.jpg',
|
|
88
|
+
height: 800,
|
|
89
|
+
width: 800
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
file: 'non-animated.webp',
|
|
93
|
+
height: 420,
|
|
94
|
+
width: 900
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
file: 'test-image-avif.avif',
|
|
98
|
+
height: 800,
|
|
99
|
+
width: 800
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
file: 'image.svg',
|
|
103
|
+
height: 260,
|
|
104
|
+
width: 260
|
|
105
|
+
}
|
|
106
|
+
];
|
|
107
|
+
it.each(cases)('should read $file dimensions via sharp', async ({ file, height, width })=>{
|
|
108
|
+
expect(await getImageSize({
|
|
109
|
+
file: fileFor(file),
|
|
110
|
+
sharp
|
|
111
|
+
})).toEqual({
|
|
112
|
+
height,
|
|
113
|
+
width
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
it.each(cases)('should read $file dimensions via the probe fallback', async ({ file, height, width })=>{
|
|
117
|
+
expect(await getImageSize({
|
|
118
|
+
file: fileFor(file)
|
|
119
|
+
})).toEqual({
|
|
120
|
+
height,
|
|
121
|
+
width
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
it('should fall back to the probe when sharp cannot decode a header-only image', async ()=>{
|
|
125
|
+
// A 1x1 PNG truncated after IHDR: the header is readable but there is no
|
|
126
|
+
// pixel data, so sharp throws while the probe still measures it.
|
|
127
|
+
const data = Buffer.from('89504e470d0a1a0a0000000d49484452000000010000000108060000001f15c489', 'hex');
|
|
128
|
+
const file = {
|
|
129
|
+
data,
|
|
130
|
+
mimetype: 'image/png',
|
|
131
|
+
name: 'truncated.png',
|
|
132
|
+
size: data.length,
|
|
133
|
+
tempFilePath: ''
|
|
134
|
+
};
|
|
135
|
+
expect(await getImageSize({
|
|
136
|
+
file,
|
|
137
|
+
sharp
|
|
138
|
+
})).toEqual({
|
|
139
|
+
height: 1,
|
|
140
|
+
width: 1
|
|
141
|
+
});
|
|
142
|
+
expect(await getImageSize({
|
|
143
|
+
file
|
|
144
|
+
})).toEqual({
|
|
145
|
+
height: 1,
|
|
146
|
+
width: 1
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
it('should read TIFF dimensions from a tempFilePath without a temp-file workaround', async ()=>{
|
|
150
|
+
const tempFilePath = path.join(fixturesDir, 'test-image.tiff');
|
|
151
|
+
const file = {
|
|
152
|
+
mimetype: 'image/tiff',
|
|
153
|
+
name: 'test-image.tiff',
|
|
154
|
+
tempFilePath
|
|
155
|
+
};
|
|
156
|
+
expect(await getImageSize({
|
|
157
|
+
file,
|
|
158
|
+
sharp
|
|
159
|
+
})).toEqual({
|
|
160
|
+
height: 100,
|
|
161
|
+
width: 200
|
|
162
|
+
});
|
|
163
|
+
expect(await getImageSize({
|
|
164
|
+
file
|
|
165
|
+
})).toEqual({
|
|
166
|
+
height: 100,
|
|
167
|
+
width: 200
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
describe('sharp cannot decode JPEG XL', ()=>{
|
|
171
|
+
const tempDir = mkdtempSync(path.join(os.tmpdir(), 'payload-get-image-size-jxl-'));
|
|
172
|
+
afterAll(()=>{
|
|
173
|
+
rmSync(tempDir, {
|
|
174
|
+
recursive: true,
|
|
175
|
+
force: true
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
it('should fall back to the probe for an in-memory JPEG XL buffer', async ()=>{
|
|
179
|
+
// `isImage('image/jxl')` is true, so `generateFileData` always calls
|
|
180
|
+
// `getImageSize` with `sharp` for JXL uploads; sharp rejects the format
|
|
181
|
+
// (libvips has no JXL codec) and the probe must take over
|
|
182
|
+
const data = encodeJXLCodestream(1920, 1080);
|
|
183
|
+
const file = {
|
|
184
|
+
data,
|
|
185
|
+
mimetype: 'image/jxl',
|
|
186
|
+
name: 'test.jxl',
|
|
187
|
+
size: data.length,
|
|
188
|
+
tempFilePath: ''
|
|
189
|
+
};
|
|
190
|
+
expect(await getImageSize({
|
|
191
|
+
file,
|
|
192
|
+
sharp
|
|
193
|
+
})).toEqual({
|
|
194
|
+
height: 1080,
|
|
195
|
+
width: 1920
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
it('should fall back to the probe for a JPEG XL tempFilePath', async ()=>{
|
|
199
|
+
const tempFilePath = path.join(tempDir, 'test.jxl');
|
|
200
|
+
writeFileSync(tempFilePath, encodeJXLCodestream(640, 480));
|
|
201
|
+
const file = {
|
|
202
|
+
mimetype: 'image/jxl',
|
|
203
|
+
name: 'test.jxl',
|
|
204
|
+
tempFilePath
|
|
205
|
+
};
|
|
206
|
+
expect(await getImageSize({
|
|
207
|
+
file,
|
|
208
|
+
sharp
|
|
209
|
+
})).toEqual({
|
|
210
|
+
height: 480,
|
|
211
|
+
width: 640
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
describe('streaming from tempFilePath', ()=>{
|
|
216
|
+
const tempDir = mkdtempSync(path.join(os.tmpdir(), 'payload-get-image-size-'));
|
|
217
|
+
afterEach(()=>{
|
|
218
|
+
counters.bytesReadFromDisk = 0;
|
|
219
|
+
});
|
|
220
|
+
afterAll(()=>{
|
|
221
|
+
rmSync(tempDir, {
|
|
222
|
+
recursive: true,
|
|
223
|
+
force: true
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
it('should not read a whole large PNG into memory when probing via tempFilePath', async ()=>{
|
|
227
|
+
// image-dimensions only reads the PNG signature (bytes 0-7) and the
|
|
228
|
+
// width/height fields (bytes 16-23); everything else here is filler
|
|
229
|
+
const header = Buffer.alloc(24);
|
|
230
|
+
header.write('\x89PNG\r\n\x1a\n', 0, 'binary');
|
|
231
|
+
header.writeUInt32BE(1920, 16);
|
|
232
|
+
header.writeUInt32BE(1080, 20);
|
|
233
|
+
const largeFileSize = 3 * 1024 * 1024;
|
|
234
|
+
const largeFile = Buffer.concat([
|
|
235
|
+
header,
|
|
236
|
+
Buffer.alloc(largeFileSize)
|
|
237
|
+
]);
|
|
238
|
+
const tempFilePath = path.join(tempDir, 'large.png');
|
|
239
|
+
writeFileSync(tempFilePath, largeFile);
|
|
240
|
+
const file = {
|
|
241
|
+
mimetype: 'image/png',
|
|
242
|
+
name: 'large.png',
|
|
243
|
+
tempFilePath
|
|
244
|
+
};
|
|
245
|
+
expect(await getImageSize({
|
|
246
|
+
file
|
|
247
|
+
})).toEqual({
|
|
248
|
+
height: 1080,
|
|
249
|
+
width: 1920
|
|
250
|
+
});
|
|
251
|
+
// Confirms the read actually went through `createReadStream` rather
|
|
252
|
+
// than skipping it entirely (which would trivially satisfy "< 500KB")
|
|
253
|
+
expect(counters.bytesReadFromDisk).toBeGreaterThan(0);
|
|
254
|
+
expect(counters.bytesReadFromDisk).toBeLessThan(500 * 1024);
|
|
255
|
+
expect(counters.bytesReadFromDisk).toBeLessThan(largeFile.length);
|
|
256
|
+
});
|
|
257
|
+
it.each(cases.filter(({ file })=>file !== 'image.svg'))('should read $file dimensions via imageDimensionsFromStream when streamed from a tempFilePath', async ({ file, height, width })=>{
|
|
258
|
+
const tempFilePath = path.join(tempDir, file);
|
|
259
|
+
writeFileSync(tempFilePath, readFileSync(path.join(fixturesDir, file)));
|
|
260
|
+
const streamedFile = {
|
|
261
|
+
mimetype: 'image/test',
|
|
262
|
+
name: file,
|
|
263
|
+
tempFilePath
|
|
264
|
+
};
|
|
265
|
+
expect(await getImageSize({
|
|
266
|
+
file: streamedFile
|
|
267
|
+
})).toEqual({
|
|
268
|
+
height,
|
|
269
|
+
width
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
it('should read GIF dimensions when streamed from a tempFilePath', async ()=>{
|
|
273
|
+
// No GIF fixture lives in this directory; `image-dimensions` only needs
|
|
274
|
+
// the 6-byte signature plus the 4-byte width/height fields that follow it
|
|
275
|
+
const header = Buffer.concat([
|
|
276
|
+
Buffer.from('GIF89a', 'ascii'),
|
|
277
|
+
Buffer.alloc(4)
|
|
278
|
+
]);
|
|
279
|
+
header.writeUInt16LE(64, 6);
|
|
280
|
+
header.writeUInt16LE(48, 8);
|
|
281
|
+
const tempFilePath = path.join(tempDir, 'test.gif');
|
|
282
|
+
writeFileSync(tempFilePath, header);
|
|
283
|
+
const file = {
|
|
284
|
+
mimetype: 'image/gif',
|
|
285
|
+
name: 'test.gif',
|
|
286
|
+
tempFilePath
|
|
287
|
+
};
|
|
288
|
+
expect(await getImageSize({
|
|
289
|
+
file
|
|
290
|
+
})).toEqual({
|
|
291
|
+
height: 48,
|
|
292
|
+
width: 64
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
//# sourceMappingURL=getImageSize.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/uploads/getImageSize.spec.ts"],"sourcesContent":["import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'fs'\nimport os from 'os'\nimport path from 'path'\nimport sharp from 'sharp'\nimport { fileURLToPath } from 'url'\nimport { afterAll, afterEach, describe, expect, it, vi } from 'vitest'\n\nimport type { PayloadRequest } from '../types/index.js'\n\nimport { getImageSize } from './getImageSize.js'\n\n// Tracks bytes actually read off disk so the streaming test below can assert\n// that a large file's tail is never pulled into memory\nconst counters = vi.hoisted(() => ({ bytesReadFromDisk: 0 }))\n\nvi.mock('fs', async (importOriginal) => {\n const actual = await importOriginal<typeof import('fs')>()\n return {\n ...actual,\n createReadStream: (...args: Parameters<typeof actual.createReadStream>) => {\n const stream = actual.createReadStream(...args)\n stream.on('data', (chunk: Buffer) => {\n counters.bytesReadFromDisk += chunk.length\n })\n return stream\n },\n }\n})\n\nconst dirname = path.dirname(fileURLToPath(import.meta.url))\nconst fixturesDir = path.resolve(dirname, '../../../../test/uploads')\n\n// Minimal JPEG XL bare-codestream encoder (bit-packed, little-endian, LSB\n// first). See probeImageSize.spec.ts for the full JXL parsing test suite;\n// this only needs to produce a valid header for the sharp-fallback tests below\nconst encodeJXLCodestream = (width: number, height: number): Buffer => {\n const bits: number[] = []\n const pushBits = (value: number, length: number) => {\n for (let i = 0; i < length; i++) {\n bits.push((value >> i) & 1)\n }\n }\n const pushDimension = (value: number) => {\n const n = value - 1\n const sizeClasses = [9, 13, 18, 30]\n const classIndex = sizeClasses.findIndex((extraBits) => n < 2 ** extraBits)\n pushBits(classIndex, 2)\n pushBits(n, sizeClasses[classIndex]!)\n }\n\n pushBits(0, 1) // isSmallImage = false\n pushDimension(height)\n pushBits(0, 3) // widthMode = 0 (explicit dimension)\n pushDimension(width)\n\n const body = Buffer.alloc(Math.ceil(bits.length / 8))\n bits.forEach((bit, i) => {\n if (bit) {\n body[Math.floor(i / 8)]! |= 1 << i % 8\n }\n })\n\n return Buffer.concat([Buffer.from([0xff, 0x0a]), body])\n}\n\nconst fileFor = (name: string): PayloadRequest['file'] => {\n const data = readFileSync(path.join(fixturesDir, name))\n // In-memory uploads carry an empty `tempFilePath`, which must fall back to `data`\n return {\n data,\n mimetype: 'image/test',\n name,\n size: data.length,\n tempFilePath: '',\n } as PayloadRequest['file']\n}\n\ndescribe('getImageSize', () => {\n const cases: Array<{ file: string; height: number; width: number }> = [\n { file: 'test-image.png', height: 800, width: 800 },\n { file: 'test-image.jpg', height: 800, width: 800 },\n { file: 'non-animated.webp', height: 420, width: 900 },\n { file: 'test-image-avif.avif', height: 800, width: 800 },\n { file: 'image.svg', height: 260, width: 260 },\n ]\n\n it.each(cases)('should read $file dimensions via sharp', async ({ file, height, width }) => {\n expect(await getImageSize({ file: fileFor(file), sharp })).toEqual({ height, width })\n })\n\n it.each(cases)(\n 'should read $file dimensions via the probe fallback',\n async ({ file, height, width }) => {\n expect(await getImageSize({ file: fileFor(file) })).toEqual({ height, width })\n },\n )\n\n it('should fall back to the probe when sharp cannot decode a header-only image', async () => {\n // A 1x1 PNG truncated after IHDR: the header is readable but there is no\n // pixel data, so sharp throws while the probe still measures it.\n const data = Buffer.from(\n '89504e470d0a1a0a0000000d49484452000000010000000108060000001f15c489',\n 'hex',\n )\n const file = {\n data,\n mimetype: 'image/png',\n name: 'truncated.png',\n size: data.length,\n tempFilePath: '',\n } as PayloadRequest['file']\n\n expect(await getImageSize({ file, sharp })).toEqual({ height: 1, width: 1 })\n expect(await getImageSize({ file })).toEqual({ height: 1, width: 1 })\n })\n\n it('should read TIFF dimensions from a tempFilePath without a temp-file workaround', async () => {\n const tempFilePath = path.join(fixturesDir, 'test-image.tiff')\n const file = {\n mimetype: 'image/tiff',\n name: 'test-image.tiff',\n tempFilePath,\n } as PayloadRequest['file']\n\n expect(await getImageSize({ file, sharp })).toEqual({ height: 100, width: 200 })\n expect(await getImageSize({ file })).toEqual({ height: 100, width: 200 })\n })\n\n describe('sharp cannot decode JPEG XL', () => {\n const tempDir = mkdtempSync(path.join(os.tmpdir(), 'payload-get-image-size-jxl-'))\n\n afterAll(() => {\n rmSync(tempDir, { recursive: true, force: true })\n })\n\n it('should fall back to the probe for an in-memory JPEG XL buffer', async () => {\n // `isImage('image/jxl')` is true, so `generateFileData` always calls\n // `getImageSize` with `sharp` for JXL uploads; sharp rejects the format\n // (libvips has no JXL codec) and the probe must take over\n const data = encodeJXLCodestream(1920, 1080)\n const file = {\n data,\n mimetype: 'image/jxl',\n name: 'test.jxl',\n size: data.length,\n tempFilePath: '',\n } as PayloadRequest['file']\n\n expect(await getImageSize({ file, sharp })).toEqual({ height: 1080, width: 1920 })\n })\n\n it('should fall back to the probe for a JPEG XL tempFilePath', async () => {\n const tempFilePath = path.join(tempDir, 'test.jxl')\n writeFileSync(tempFilePath, encodeJXLCodestream(640, 480))\n\n const file = {\n mimetype: 'image/jxl',\n name: 'test.jxl',\n tempFilePath,\n } as PayloadRequest['file']\n\n expect(await getImageSize({ file, sharp })).toEqual({ height: 480, width: 640 })\n })\n })\n\n describe('streaming from tempFilePath', () => {\n const tempDir = mkdtempSync(path.join(os.tmpdir(), 'payload-get-image-size-'))\n\n afterEach(() => {\n counters.bytesReadFromDisk = 0\n })\n\n afterAll(() => {\n rmSync(tempDir, { recursive: true, force: true })\n })\n\n it('should not read a whole large PNG into memory when probing via tempFilePath', async () => {\n // image-dimensions only reads the PNG signature (bytes 0-7) and the\n // width/height fields (bytes 16-23); everything else here is filler\n const header = Buffer.alloc(24)\n header.write('\\x89PNG\\r\\n\\x1a\\n', 0, 'binary')\n header.writeUInt32BE(1920, 16)\n header.writeUInt32BE(1080, 20)\n const largeFileSize = 3 * 1024 * 1024\n const largeFile = Buffer.concat([header, Buffer.alloc(largeFileSize)])\n\n const tempFilePath = path.join(tempDir, 'large.png')\n writeFileSync(tempFilePath, largeFile)\n\n const file = {\n mimetype: 'image/png',\n name: 'large.png',\n tempFilePath,\n } as PayloadRequest['file']\n\n expect(await getImageSize({ file })).toEqual({ height: 1080, width: 1920 })\n // Confirms the read actually went through `createReadStream` rather\n // than skipping it entirely (which would trivially satisfy \"< 500KB\")\n expect(counters.bytesReadFromDisk).toBeGreaterThan(0)\n expect(counters.bytesReadFromDisk).toBeLessThan(500 * 1024)\n expect(counters.bytesReadFromDisk).toBeLessThan(largeFile.length)\n })\n\n it.each(cases.filter(({ file }) => file !== 'image.svg'))(\n 'should read $file dimensions via imageDimensionsFromStream when streamed from a tempFilePath',\n async ({ file, height, width }) => {\n const tempFilePath = path.join(tempDir, file)\n writeFileSync(tempFilePath, readFileSync(path.join(fixturesDir, file)))\n\n const streamedFile = {\n mimetype: 'image/test',\n name: file,\n tempFilePath,\n } as PayloadRequest['file']\n\n expect(await getImageSize({ file: streamedFile })).toEqual({ height, width })\n },\n )\n\n it('should read GIF dimensions when streamed from a tempFilePath', async () => {\n // No GIF fixture lives in this directory; `image-dimensions` only needs\n // the 6-byte signature plus the 4-byte width/height fields that follow it\n const header = Buffer.concat([Buffer.from('GIF89a', 'ascii'), Buffer.alloc(4)])\n header.writeUInt16LE(64, 6)\n header.writeUInt16LE(48, 8)\n\n const tempFilePath = path.join(tempDir, 'test.gif')\n writeFileSync(tempFilePath, header)\n\n const file = {\n mimetype: 'image/gif',\n name: 'test.gif',\n tempFilePath,\n } as PayloadRequest['file']\n\n expect(await getImageSize({ file })).toEqual({ height: 48, width: 64 })\n })\n })\n})\n"],"names":["mkdtempSync","readFileSync","rmSync","writeFileSync","os","path","sharp","fileURLToPath","afterAll","afterEach","describe","expect","it","vi","getImageSize","counters","hoisted","bytesReadFromDisk","mock","importOriginal","actual","createReadStream","args","stream","on","chunk","length","dirname","url","fixturesDir","resolve","encodeJXLCodestream","width","height","bits","pushBits","value","i","push","pushDimension","n","sizeClasses","classIndex","findIndex","extraBits","body","Buffer","alloc","Math","ceil","forEach","bit","floor","concat","from","fileFor","name","data","join","mimetype","size","tempFilePath","cases","file","each","toEqual","tempDir","tmpdir","recursive","force","header","write","writeUInt32BE","largeFileSize","largeFile","toBeGreaterThan","toBeLessThan","filter","streamedFile","writeUInt16LE"],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ProbedImageSize } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Image dimension probe used as a fallback when `sharp` is not configured.
|
|
4
|
+
* Reads only the header bytes needed to determine width and height.
|
|
5
|
+
*
|
|
6
|
+
* PNG, JPEG, GIF, WebP, AVIF and HEIF/HEIC are delegated to `image-dimensions`
|
|
7
|
+
* (actively maintained, zero dependencies). BMP, ICO/CUR, SVG, TIFF and JPEG
|
|
8
|
+
* XL have no such maintained equivalent, so they're read here directly; each
|
|
9
|
+
* of those parsers advances by a fixed amount or is bounded by an explicit
|
|
10
|
+
* guard, so malformed input can never block the event loop.
|
|
11
|
+
*
|
|
12
|
+
* Replaces the archived `image-size` package, whose ICNS/HEIF parsers shipped
|
|
13
|
+
* unpatched denial-of-service vulnerabilities (CVE-2025-71330, CVE-2025-71319).
|
|
14
|
+
*
|
|
15
|
+
* Two edge cases changed behavior compared to the previous hand-rolled parser:
|
|
16
|
+
* - JPEG files using arithmetic coding or hierarchical encoding (SOF markers
|
|
17
|
+
* 5-7, 9-11, 13-15) are no longer read; `image-dimensions` only recognizes
|
|
18
|
+
* baseline and progressive JPEG (SOF0-3). These encodings are not produced
|
|
19
|
+
* by browsers or cameras, so this is not expected to affect real uploads.
|
|
20
|
+
* - AVIF/HEIF detection now checks the container's brand list instead of
|
|
21
|
+
* accepting any ISO base media file with a `ftyp` box, so non-image formats
|
|
22
|
+
* that share the same container (for example MP4/MOV) are correctly no
|
|
23
|
+
* longer misidentified as images.
|
|
24
|
+
*
|
|
25
|
+
* @throws if the buffer is not a recognized/parseable image format
|
|
26
|
+
*/
|
|
27
|
+
export declare function probeImageSize(data: Buffer): ProbedImageSize;
|
|
28
|
+
/**
|
|
29
|
+
* Probe used when the file lives on disk via `tempFilePath`. PNG, JPEG, GIF,
|
|
30
|
+
* WebP, AVIF and HEIF/HEIC are read through `imageDimensionsFromStream`,
|
|
31
|
+
* which reads only as many bytes as are needed to find the header, so a
|
|
32
|
+
* large file's data is never pulled into memory. BMP, ICO/CUR, SVG, TIFF and
|
|
33
|
+
* JPEG XL are detected from a small header sniff and, only then, read in
|
|
34
|
+
* full for `probeImageSize` to parse.
|
|
35
|
+
*
|
|
36
|
+
* @throws if the file is not a recognized/parseable image format
|
|
37
|
+
*/
|
|
38
|
+
export declare function probeImageSizeFromPath(tempFilePath: string): Promise<ProbedImageSize>;
|
|
39
|
+
//# sourceMappingURL=probeImageSize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"probeImageSize.d.ts","sourceRoot":"","sources":["../../src/uploads/probeImageSize.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAEjD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAW5D;AAQD;;;;;;;;;GASG;AACH,wBAAsB,sBAAsB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAgB3F"}
|