From 6133a1d1838fe0d5f15608fad0e76aca3c405f68 Mon Sep 17 00:00:00 2001 From: Dan Ribbens Date: Wed, 30 Apr 2025 11:26:28 -0700 Subject: [PATCH] perf: optimize file access promises (#12275) Improves performance in local strategy uploads by reading the file and metadata info synchronously. This change uses `promise.all` for three separately awaited calls. This improves the perf by making all calls in a non-blocking way. --- .../payload/src/uploads/generateFileData.ts | 6 +++- packages/payload/src/uploads/getFileByPath.ts | 36 +++++++++---------- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/packages/payload/src/uploads/generateFileData.ts b/packages/payload/src/uploads/generateFileData.ts index 860be7271..bf5c86d7f 100644 --- a/packages/payload/src/uploads/generateFileData.ts +++ b/packages/payload/src/uploads/generateFileData.ts @@ -10,7 +10,7 @@ import type { SanitizedConfig } from '../config/types.js' import type { PayloadRequest } from '../types/index.js' import type { FileData, FileToSave, ProbedImageSize, UploadEdits } from './types.js' -import { FileRetrievalError, FileUploadError, MissingFile } from '../errors/index.js' +import { FileRetrievalError, FileUploadError, Forbidden, MissingFile } from '../errors/index.js' import { canResizeImage } from './canResizeImage.js' import { cropImage } from './cropImage.js' import { getExternalFile } from './getExternalFile.js' @@ -85,6 +85,10 @@ export const generateFileData = async ({ if (!file && uploadEdits && incomingFileData) { const { filename, url } = incomingFileData as FileData + if (filename && (filename.includes('../') || filename.includes('..\\'))) { + throw new Forbidden(req.t) + } + try { if (url && url.startsWith('/') && !disableLocalStorage) { const filePath = `${staticPath}/${filename}` diff --git a/packages/payload/src/uploads/getFileByPath.ts b/packages/payload/src/uploads/getFileByPath.ts index 53ce79119..2f0a1f152 100644 --- a/packages/payload/src/uploads/getFileByPath.ts +++ b/packages/payload/src/uploads/getFileByPath.ts @@ -5,28 +5,28 @@ import path from 'path' import type { PayloadRequest } from '../types/index.js' -const mimeTypeEstimate = { +const mimeTypeEstimate: Record = { svg: 'image/svg+xml', } export const getFileByPath = async (filePath: string): Promise => { - if (typeof filePath === 'string') { - const data = await fs.readFile(filePath) - const mimetype = fileTypeFromFile(filePath) - const { size } = await fs.stat(filePath) - - const name = path.basename(filePath) - const ext = path.extname(filePath).slice(1) - - const mime = (await mimetype)?.mime || mimeTypeEstimate[ext] - - return { - name, - data, - mimetype: mime, - size, - } + if (typeof filePath !== 'string') { + return undefined } - return undefined + const name = path.basename(filePath) + const ext = path.extname(filePath).slice(1) + + const [data, stat, type] = await Promise.all([ + fs.readFile(filePath), + fs.stat(filePath), + fileTypeFromFile(filePath), + ]) + + return { + name, + data, + mimetype: type?.mime || mimeTypeEstimate[ext], + size: stat.size, + } }