payload 3.49.0 → 3.50.0-canary.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/uploads/checkFileRestrictions.d.ts.map +1 -1
- package/dist/uploads/checkFileRestrictions.js +9 -1
- package/dist/uploads/checkFileRestrictions.js.map +1 -1
- package/dist/uploads/detectSvgFromXml.d.ts +5 -0
- package/dist/uploads/detectSvgFromXml.d.ts.map +1 -0
- package/dist/uploads/detectSvgFromXml.js +43 -0
- package/dist/uploads/detectSvgFromXml.js.map +1 -0
- package/dist/uploads/endpoints/getFile.d.ts.map +1 -1
- package/dist/uploads/endpoints/getFile.js +5 -1
- package/dist/uploads/endpoints/getFile.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkFileRestrictions.d.ts","sourceRoot":"","sources":["../../src/uploads/checkFileRestrictions.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,2BAA2B,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"checkFileRestrictions.d.ts","sourceRoot":"","sources":["../../src/uploads/checkFileRestrictions.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,2BAA2B,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAM5E;;GAEG;AACH,eAAO,MAAM,6BAA6B,EAAE,aAgC3C,CAAA;AAED,eAAO,MAAM,qBAAqB,+BAI/B,2BAA2B,KAAG,OAAO,CAAC,IAAI,CA0D5C,CAAA"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { fileTypeFromBuffer } from 'file-type';
|
|
2
2
|
import { ValidationError } from '../errors/index.js';
|
|
3
3
|
import { validateMimeType } from '../utilities/validateMimeType.js';
|
|
4
|
+
import { detectSvgFromXml } from './detectSvgFromXml.js';
|
|
4
5
|
/**
|
|
5
6
|
* Restricted file types and their extensions.
|
|
6
7
|
*/ export const RESTRICTED_FILE_EXT_AND_TYPES = [
|
|
@@ -225,7 +226,14 @@ export const checkFileRestrictions = async ({ collection, file, req })=>{
|
|
|
225
226
|
}
|
|
226
227
|
// Secondary mimetype check to assess file type from buffer
|
|
227
228
|
if (configMimeTypes.length > 0) {
|
|
228
|
-
|
|
229
|
+
let detected = await fileTypeFromBuffer(file.data);
|
|
230
|
+
// Handle SVG files that are detected as XML due to <?xml declarations
|
|
231
|
+
if (detected?.mime === 'application/xml' && configMimeTypes.some((type)=>type.includes('svg')) && detectSvgFromXml(file.data)) {
|
|
232
|
+
detected = {
|
|
233
|
+
ext: 'svg',
|
|
234
|
+
mime: 'image/svg+xml'
|
|
235
|
+
};
|
|
236
|
+
}
|
|
229
237
|
const passesMimeTypeCheck = detected?.mime && validateMimeType(detected.mime, configMimeTypes);
|
|
230
238
|
if (detected && !passesMimeTypeCheck) {
|
|
231
239
|
errors.push(`Invalid MIME type: ${detected.mime}.`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/uploads/checkFileRestrictions.ts"],"sourcesContent":["import { fileTypeFromBuffer } from 'file-type'\n\nimport type { checkFileRestrictionsParams, FileAllowList } from './types.js'\n\nimport { ValidationError } from '../errors/index.js'\nimport { validateMimeType } from '../utilities/validateMimeType.js'\n\n/**\n * Restricted file types and their extensions.\n */\nexport const RESTRICTED_FILE_EXT_AND_TYPES: FileAllowList = [\n { extensions: ['exe', 'dll'], mimeType: 'application/x-msdownload' },\n { extensions: ['exe', 'com', 'app', 'action'], mimeType: 'application/x-executable' },\n { extensions: ['bat', 'cmd'], mimeType: 'application/x-msdos-program' },\n { extensions: ['exe', 'com'], mimeType: 'application/x-ms-dos-executable' },\n { extensions: ['dmg'], mimeType: 'application/x-apple-diskimage' },\n { extensions: ['deb'], mimeType: 'application/x-debian-package' },\n { extensions: ['rpm'], mimeType: 'application/x-redhat-package-manager' },\n { extensions: ['exe', 'dll'], mimeType: 'application/vnd.microsoft.portable-executable' },\n { extensions: ['msi'], mimeType: 'application/x-msi' },\n { extensions: ['jar', 'ear', 'war'], mimeType: 'application/java-archive' },\n { extensions: ['desktop'], mimeType: 'application/x-desktop' },\n { extensions: ['cpl'], mimeType: 'application/x-cpl' },\n { extensions: ['lnk'], mimeType: 'application/x-ms-shortcut' },\n { extensions: ['pkg'], mimeType: 'application/x-apple-installer' },\n { extensions: ['htm', 'html', 'shtml', 'xhtml'], mimeType: 'text/html' },\n { extensions: ['php', 'phtml'], mimeType: 'application/x-httpd-php' },\n { extensions: ['js', 'jse'], mimeType: 'text/javascript' },\n { extensions: ['jsp'], mimeType: 'application/x-jsp' },\n { extensions: ['py'], mimeType: 'text/x-python' },\n { extensions: ['rb'], mimeType: 'text/x-ruby' },\n { extensions: ['pl'], mimeType: 'text/x-perl' },\n { extensions: ['ps1', 'psc1', 'psd1', 'psh', 'psm1'], mimeType: 'application/x-powershell' },\n { extensions: ['vbe', 'vbs'], mimeType: 'application/x-vbscript' },\n { extensions: ['ws', 'wsc', 'wsf', 'wsh'], mimeType: 'application/x-ms-wsh' },\n { extensions: ['scr'], mimeType: 'application/x-msdownload' },\n { extensions: ['asp', 'aspx'], mimeType: 'application/x-asp' },\n { extensions: ['hta'], mimeType: 'application/x-hta' },\n { extensions: ['reg'], mimeType: 'application/x-registry' },\n { extensions: ['url'], mimeType: 'application/x-url' },\n { extensions: ['workflow'], mimeType: 'application/x-workflow' },\n { extensions: ['command'], mimeType: 'application/x-command' },\n]\n\nexport const checkFileRestrictions = async ({\n collection,\n file,\n req,\n}: checkFileRestrictionsParams): Promise<void> => {\n const errors: string[] = []\n const { upload: uploadConfig } = collection\n const configMimeTypes =\n uploadConfig &&\n typeof uploadConfig === 'object' &&\n 'mimeTypes' in uploadConfig &&\n Array.isArray(uploadConfig.mimeTypes)\n ? uploadConfig.mimeTypes\n : []\n\n const allowRestrictedFileTypes =\n uploadConfig && typeof uploadConfig === 'object' && 'allowRestrictedFileTypes' in uploadConfig\n ? (uploadConfig as { allowRestrictedFileTypes?: boolean }).allowRestrictedFileTypes\n : false\n\n // Skip validation if `allowRestrictedFileTypes` is true\n if (allowRestrictedFileTypes) {\n return\n }\n\n // Secondary mimetype check to assess file type from buffer\n if (configMimeTypes.length > 0) {\n
|
|
1
|
+
{"version":3,"sources":["../../src/uploads/checkFileRestrictions.ts"],"sourcesContent":["import { fileTypeFromBuffer } from 'file-type'\n\nimport type { checkFileRestrictionsParams, FileAllowList } from './types.js'\n\nimport { ValidationError } from '../errors/index.js'\nimport { validateMimeType } from '../utilities/validateMimeType.js'\nimport { detectSvgFromXml } from './detectSvgFromXml.js'\n\n/**\n * Restricted file types and their extensions.\n */\nexport const RESTRICTED_FILE_EXT_AND_TYPES: FileAllowList = [\n { extensions: ['exe', 'dll'], mimeType: 'application/x-msdownload' },\n { extensions: ['exe', 'com', 'app', 'action'], mimeType: 'application/x-executable' },\n { extensions: ['bat', 'cmd'], mimeType: 'application/x-msdos-program' },\n { extensions: ['exe', 'com'], mimeType: 'application/x-ms-dos-executable' },\n { extensions: ['dmg'], mimeType: 'application/x-apple-diskimage' },\n { extensions: ['deb'], mimeType: 'application/x-debian-package' },\n { extensions: ['rpm'], mimeType: 'application/x-redhat-package-manager' },\n { extensions: ['exe', 'dll'], mimeType: 'application/vnd.microsoft.portable-executable' },\n { extensions: ['msi'], mimeType: 'application/x-msi' },\n { extensions: ['jar', 'ear', 'war'], mimeType: 'application/java-archive' },\n { extensions: ['desktop'], mimeType: 'application/x-desktop' },\n { extensions: ['cpl'], mimeType: 'application/x-cpl' },\n { extensions: ['lnk'], mimeType: 'application/x-ms-shortcut' },\n { extensions: ['pkg'], mimeType: 'application/x-apple-installer' },\n { extensions: ['htm', 'html', 'shtml', 'xhtml'], mimeType: 'text/html' },\n { extensions: ['php', 'phtml'], mimeType: 'application/x-httpd-php' },\n { extensions: ['js', 'jse'], mimeType: 'text/javascript' },\n { extensions: ['jsp'], mimeType: 'application/x-jsp' },\n { extensions: ['py'], mimeType: 'text/x-python' },\n { extensions: ['rb'], mimeType: 'text/x-ruby' },\n { extensions: ['pl'], mimeType: 'text/x-perl' },\n { extensions: ['ps1', 'psc1', 'psd1', 'psh', 'psm1'], mimeType: 'application/x-powershell' },\n { extensions: ['vbe', 'vbs'], mimeType: 'application/x-vbscript' },\n { extensions: ['ws', 'wsc', 'wsf', 'wsh'], mimeType: 'application/x-ms-wsh' },\n { extensions: ['scr'], mimeType: 'application/x-msdownload' },\n { extensions: ['asp', 'aspx'], mimeType: 'application/x-asp' },\n { extensions: ['hta'], mimeType: 'application/x-hta' },\n { extensions: ['reg'], mimeType: 'application/x-registry' },\n { extensions: ['url'], mimeType: 'application/x-url' },\n { extensions: ['workflow'], mimeType: 'application/x-workflow' },\n { extensions: ['command'], mimeType: 'application/x-command' },\n]\n\nexport const checkFileRestrictions = async ({\n collection,\n file,\n req,\n}: checkFileRestrictionsParams): Promise<void> => {\n const errors: string[] = []\n const { upload: uploadConfig } = collection\n const configMimeTypes =\n uploadConfig &&\n typeof uploadConfig === 'object' &&\n 'mimeTypes' in uploadConfig &&\n Array.isArray(uploadConfig.mimeTypes)\n ? uploadConfig.mimeTypes\n : []\n\n const allowRestrictedFileTypes =\n uploadConfig && typeof uploadConfig === 'object' && 'allowRestrictedFileTypes' in uploadConfig\n ? (uploadConfig as { allowRestrictedFileTypes?: boolean }).allowRestrictedFileTypes\n : false\n\n // Skip validation if `allowRestrictedFileTypes` is true\n if (allowRestrictedFileTypes) {\n return\n }\n\n // Secondary mimetype check to assess file type from buffer\n if (configMimeTypes.length > 0) {\n let detected = await fileTypeFromBuffer(file.data)\n\n // Handle SVG files that are detected as XML due to <?xml declarations\n if (\n detected?.mime === 'application/xml' &&\n configMimeTypes.some((type) => type.includes('svg')) &&\n detectSvgFromXml(file.data)\n ) {\n detected = { ext: 'svg' as any, mime: 'image/svg+xml' as any }\n }\n\n const passesMimeTypeCheck = detected?.mime && validateMimeType(detected.mime, configMimeTypes)\n\n if (detected && !passesMimeTypeCheck) {\n errors.push(`Invalid MIME type: ${detected.mime}.`)\n }\n } else {\n const isRestricted = RESTRICTED_FILE_EXT_AND_TYPES.some((type) => {\n const hasRestrictedExt = type.extensions.some((ext) => file.name.toLowerCase().endsWith(ext))\n const hasRestrictedMime = type.mimeType === file.mimetype\n return hasRestrictedExt || hasRestrictedMime\n })\n if (isRestricted) {\n errors.push(\n `File type '${file.mimetype}' not allowed ${file.name}: Restricted file type detected -- set 'allowRestrictedFileTypes' to true to skip this check for this Collection.`,\n )\n }\n }\n\n if (errors.length > 0) {\n req.payload.logger.error(errors.join(', '))\n throw new ValidationError({\n errors: [{ message: errors.join(', '), path: 'file' }],\n })\n }\n}\n"],"names":["fileTypeFromBuffer","ValidationError","validateMimeType","detectSvgFromXml","RESTRICTED_FILE_EXT_AND_TYPES","extensions","mimeType","checkFileRestrictions","collection","file","req","errors","upload","uploadConfig","configMimeTypes","Array","isArray","mimeTypes","allowRestrictedFileTypes","length","detected","data","mime","some","type","includes","ext","passesMimeTypeCheck","push","isRestricted","hasRestrictedExt","name","toLowerCase","endsWith","hasRestrictedMime","mimetype","payload","logger","error","join","message","path"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detectSvgFromXml.d.ts","sourceRoot":"","sources":["../../src/uploads/detectSvgFromXml.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CA6CxD"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Securely detect if an XML buffer contains a valid SVG document
|
|
3
|
+
*/ export function detectSvgFromXml(buffer) {
|
|
4
|
+
try {
|
|
5
|
+
// Limit buffer size to prevent processing large malicious files
|
|
6
|
+
const maxSize = 2048;
|
|
7
|
+
const content = buffer.toString('utf8', 0, Math.min(buffer.length, maxSize));
|
|
8
|
+
// Check for XML declaration and extract encoding if present
|
|
9
|
+
const xmlDeclMatch = content.match(/^<\?xml[^>]*encoding=["']([^"']+)["']/i);
|
|
10
|
+
const declaredEncoding = xmlDeclMatch?.[1]?.toLowerCase();
|
|
11
|
+
// Only support safe encodings
|
|
12
|
+
if (declaredEncoding && ![
|
|
13
|
+
'ascii',
|
|
14
|
+
'utf-8',
|
|
15
|
+
'utf8'
|
|
16
|
+
].includes(declaredEncoding)) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
// Remove XML declarations, comments, and processing instructions
|
|
20
|
+
const cleanContent = content.replace(/<\?xml[^>]*\?>/gi, '').replace(/<!--[\s\S]*?-->/g, '').replace(/<\?[^>]*\?>/g, '').trim();
|
|
21
|
+
// Find the first actual element (root element)
|
|
22
|
+
const rootElementMatch = cleanContent.match(/^<(\w+)(?:\s|>)/);
|
|
23
|
+
if (!rootElementMatch || rootElementMatch[1] !== 'svg') {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
// Validate SVG namespace - must be present for valid SVG
|
|
27
|
+
const svgNamespaceRegex = /xmlns=["']http:\/\/www\.w3\.org\/2000\/svg["']/;
|
|
28
|
+
if (!svgNamespaceRegex.test(content)) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
// Additional validation: ensure it's not malformed
|
|
32
|
+
const svgOpenTag = content.match(/<svg[\s>]/);
|
|
33
|
+
if (!svgOpenTag) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
return true;
|
|
37
|
+
} catch (_error) {
|
|
38
|
+
// If any error occurs during parsing, treat as not SVG
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
//# sourceMappingURL=detectSvgFromXml.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/uploads/detectSvgFromXml.ts"],"sourcesContent":["/**\n * Securely detect if an XML buffer contains a valid SVG document\n */\nexport function detectSvgFromXml(buffer: Buffer): boolean {\n try {\n // Limit buffer size to prevent processing large malicious files\n const maxSize = 2048\n const content = buffer.toString('utf8', 0, Math.min(buffer.length, maxSize))\n\n // Check for XML declaration and extract encoding if present\n const xmlDeclMatch = content.match(/^<\\?xml[^>]*encoding=[\"']([^\"']+)[\"']/i)\n const declaredEncoding = xmlDeclMatch?.[1]?.toLowerCase()\n\n // Only support safe encodings\n if (declaredEncoding && !['ascii', 'utf-8', 'utf8'].includes(declaredEncoding)) {\n return false\n }\n\n // Remove XML declarations, comments, and processing instructions\n const cleanContent = content\n .replace(/<\\?xml[^>]*\\?>/gi, '')\n .replace(/<!--[\\s\\S]*?-->/g, '')\n .replace(/<\\?[^>]*\\?>/g, '')\n .trim()\n\n // Find the first actual element (root element)\n const rootElementMatch = cleanContent.match(/^<(\\w+)(?:\\s|>)/)\n if (!rootElementMatch || rootElementMatch[1] !== 'svg') {\n return false\n }\n\n // Validate SVG namespace - must be present for valid SVG\n const svgNamespaceRegex = /xmlns=[\"']http:\\/\\/www\\.w3\\.org\\/2000\\/svg[\"']/\n if (!svgNamespaceRegex.test(content)) {\n return false\n }\n\n // Additional validation: ensure it's not malformed\n const svgOpenTag = content.match(/<svg[\\s>]/)\n if (!svgOpenTag) {\n return false\n }\n\n return true\n } catch (_error) {\n // If any error occurs during parsing, treat as not SVG\n return false\n }\n}\n"],"names":["detectSvgFromXml","buffer","maxSize","content","toString","Math","min","length","xmlDeclMatch","match","declaredEncoding","toLowerCase","includes","cleanContent","replace","trim","rootElementMatch","svgNamespaceRegex","test","svgOpenTag","_error"],"mappings":"AAAA;;CAEC,GACD,OAAO,SAASA,iBAAiBC,MAAc;IAC7C,IAAI;QACF,gEAAgE;QAChE,MAAMC,UAAU;QAChB,MAAMC,UAAUF,OAAOG,QAAQ,CAAC,QAAQ,GAAGC,KAAKC,GAAG,CAACL,OAAOM,MAAM,EAAEL;QAEnE,4DAA4D;QAC5D,MAAMM,eAAeL,QAAQM,KAAK,CAAC;QACnC,MAAMC,mBAAmBF,cAAc,CAAC,EAAE,EAAEG;QAE5C,8BAA8B;QAC9B,IAAID,oBAAoB,CAAC;YAAC;YAAS;YAAS;SAAO,CAACE,QAAQ,CAACF,mBAAmB;YAC9E,OAAO;QACT;QAEA,iEAAiE;QACjE,MAAMG,eAAeV,QAClBW,OAAO,CAAC,oBAAoB,IAC5BA,OAAO,CAAC,oBAAoB,IAC5BA,OAAO,CAAC,gBAAgB,IACxBC,IAAI;QAEP,+CAA+C;QAC/C,MAAMC,mBAAmBH,aAAaJ,KAAK,CAAC;QAC5C,IAAI,CAACO,oBAAoBA,gBAAgB,CAAC,EAAE,KAAK,OAAO;YACtD,OAAO;QACT;QAEA,yDAAyD;QACzD,MAAMC,oBAAoB;QAC1B,IAAI,CAACA,kBAAkBC,IAAI,CAACf,UAAU;YACpC,OAAO;QACT;QAEA,mDAAmD;QACnD,MAAMgB,aAAahB,QAAQM,KAAK,CAAC;QACjC,IAAI,CAACU,YAAY;YACf,OAAO;QACT;QAEA,OAAO;IACT,EAAE,OAAOC,QAAQ;QACf,uDAAuD;QACvD,OAAO;IACT;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getFile.d.ts","sourceRoot":"","sources":["../../../src/uploads/endpoints/getFile.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAS3D,eAAO,MAAM,cAAc,EAAE,
|
|
1
|
+
{"version":3,"file":"getFile.d.ts","sourceRoot":"","sources":["../../../src/uploads/endpoints/getFile.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAS3D,eAAO,MAAM,cAAc,EAAE,cAmG5B,CAAA"}
|
|
@@ -67,8 +67,12 @@ export const getFileHandler = async (req)=>{
|
|
|
67
67
|
}
|
|
68
68
|
const data = streamFile(filePath);
|
|
69
69
|
const fileTypeResult = await fileTypeFromFile(filePath) || getFileTypeFallback(filePath);
|
|
70
|
+
let mimeType = fileTypeResult.mime;
|
|
71
|
+
if (filePath.endsWith('.svg') && fileTypeResult.mime === 'application/xml') {
|
|
72
|
+
mimeType = 'image/svg+xml';
|
|
73
|
+
}
|
|
70
74
|
let headers = new Headers();
|
|
71
|
-
headers.set('Content-Type',
|
|
75
|
+
headers.set('Content-Type', mimeType);
|
|
72
76
|
headers.set('Content-Length', stats.size + '');
|
|
73
77
|
headers = collection.config.upload?.modifyResponseHeaders ? collection.config.upload.modifyResponseHeaders({
|
|
74
78
|
headers
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/uploads/endpoints/getFile.ts"],"sourcesContent":["import type { Stats } from 'fs'\n\nimport { fileTypeFromFile } from 'file-type'\nimport fsPromises from 'fs/promises'\nimport { status as httpStatus } from 'http-status'\nimport path from 'path'\n\nimport type { PayloadHandler } from '../../config/types.js'\n\nimport { APIError } from '../../errors/APIError.js'\nimport { checkFileAccess } from '../../uploads/checkFileAccess.js'\nimport { streamFile } from '../../uploads/fetchAPI-stream-file/index.js'\nimport { getFileTypeFallback } from '../../uploads/getFileTypeFallback.js'\nimport { getRequestCollection } from '../../utilities/getRequestEntity.js'\nimport { headersWithCors } from '../../utilities/headersWithCors.js'\n\nexport const getFileHandler: PayloadHandler = async (req) => {\n const collection = getRequestCollection(req)\n\n const filename = req.routeParams?.filename as string\n\n if (!collection.config.upload) {\n throw new APIError(\n `This collection is not an upload collection: ${collection.config.slug}`,\n httpStatus.BAD_REQUEST,\n )\n }\n\n const accessResult = (await checkFileAccess({\n collection,\n filename,\n req,\n }))!\n\n if (accessResult instanceof Response) {\n return accessResult\n }\n\n if (collection.config.upload.handlers?.length) {\n let customResponse: null | Response | void = null\n const headers = new Headers()\n\n for (const handler of collection.config.upload.handlers) {\n customResponse = await handler(req, {\n doc: accessResult,\n headers,\n params: {\n collection: collection.config.slug,\n filename,\n },\n })\n }\n\n if (customResponse instanceof Response) {\n return customResponse\n }\n }\n\n const fileDir = collection.config.upload?.staticDir || collection.config.slug\n const filePath = path.resolve(`${fileDir}/${filename}`)\n let stats: Stats\n\n try {\n stats = await fsPromises.stat(filePath)\n } catch (err) {\n if ((err as { code?: string }).code === 'ENOENT') {\n req.payload.logger.error(\n `File ${filename} for collection ${collection.config.slug} is missing on the disk. Expected path: ${filePath}`,\n )\n\n // Omit going to the routeError handler by returning response instead of\n // throwing an error to cut down log noise. The response still matches what you get with APIError to not leak details to the user.\n return Response.json(\n {\n errors: [\n {\n message: 'Something went wrong.',\n },\n ],\n },\n {\n headers: headersWithCors({\n headers: new Headers(),\n req,\n }),\n status: 500,\n },\n )\n }\n\n throw err\n }\n\n const data = streamFile(filePath)\n const fileTypeResult = (await fileTypeFromFile(filePath)) || getFileTypeFallback(filePath)\n\n let headers = new Headers()\n headers.set('Content-Type',
|
|
1
|
+
{"version":3,"sources":["../../../src/uploads/endpoints/getFile.ts"],"sourcesContent":["import type { Stats } from 'fs'\n\nimport { fileTypeFromFile } from 'file-type'\nimport fsPromises from 'fs/promises'\nimport { status as httpStatus } from 'http-status'\nimport path from 'path'\n\nimport type { PayloadHandler } from '../../config/types.js'\n\nimport { APIError } from '../../errors/APIError.js'\nimport { checkFileAccess } from '../../uploads/checkFileAccess.js'\nimport { streamFile } from '../../uploads/fetchAPI-stream-file/index.js'\nimport { getFileTypeFallback } from '../../uploads/getFileTypeFallback.js'\nimport { getRequestCollection } from '../../utilities/getRequestEntity.js'\nimport { headersWithCors } from '../../utilities/headersWithCors.js'\n\nexport const getFileHandler: PayloadHandler = async (req) => {\n const collection = getRequestCollection(req)\n\n const filename = req.routeParams?.filename as string\n\n if (!collection.config.upload) {\n throw new APIError(\n `This collection is not an upload collection: ${collection.config.slug}`,\n httpStatus.BAD_REQUEST,\n )\n }\n\n const accessResult = (await checkFileAccess({\n collection,\n filename,\n req,\n }))!\n\n if (accessResult instanceof Response) {\n return accessResult\n }\n\n if (collection.config.upload.handlers?.length) {\n let customResponse: null | Response | void = null\n const headers = new Headers()\n\n for (const handler of collection.config.upload.handlers) {\n customResponse = await handler(req, {\n doc: accessResult,\n headers,\n params: {\n collection: collection.config.slug,\n filename,\n },\n })\n }\n\n if (customResponse instanceof Response) {\n return customResponse\n }\n }\n\n const fileDir = collection.config.upload?.staticDir || collection.config.slug\n const filePath = path.resolve(`${fileDir}/${filename}`)\n let stats: Stats\n\n try {\n stats = await fsPromises.stat(filePath)\n } catch (err) {\n if ((err as { code?: string }).code === 'ENOENT') {\n req.payload.logger.error(\n `File ${filename} for collection ${collection.config.slug} is missing on the disk. Expected path: ${filePath}`,\n )\n\n // Omit going to the routeError handler by returning response instead of\n // throwing an error to cut down log noise. The response still matches what you get with APIError to not leak details to the user.\n return Response.json(\n {\n errors: [\n {\n message: 'Something went wrong.',\n },\n ],\n },\n {\n headers: headersWithCors({\n headers: new Headers(),\n req,\n }),\n status: 500,\n },\n )\n }\n\n throw err\n }\n\n const data = streamFile(filePath)\n const fileTypeResult = (await fileTypeFromFile(filePath)) || getFileTypeFallback(filePath)\n let mimeType = fileTypeResult.mime\n\n if (filePath.endsWith('.svg') && fileTypeResult.mime === 'application/xml') {\n mimeType = 'image/svg+xml'\n }\n\n let headers = new Headers()\n headers.set('Content-Type', mimeType)\n headers.set('Content-Length', stats.size + '')\n headers = collection.config.upload?.modifyResponseHeaders\n ? collection.config.upload.modifyResponseHeaders({ headers }) || headers\n : headers\n\n return new Response(data, {\n headers: headersWithCors({\n headers,\n req,\n }),\n status: httpStatus.OK,\n })\n}\n"],"names":["fileTypeFromFile","fsPromises","status","httpStatus","path","APIError","checkFileAccess","streamFile","getFileTypeFallback","getRequestCollection","headersWithCors","getFileHandler","req","collection","filename","routeParams","config","upload","slug","BAD_REQUEST","accessResult","Response","handlers","length","customResponse","headers","Headers","handler","doc","params","fileDir","staticDir","filePath","resolve","stats","stat","err","code","payload","logger","error","json","errors","message","data","fileTypeResult","mimeType","mime","endsWith","set","size","modifyResponseHeaders","OK"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payload",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.50.0-canary.0",
|
|
4
4
|
"description": "Node, React, Headless CMS and Application Framework built on Next.js",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"admin panel",
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
"undici": "7.10.0",
|
|
102
102
|
"uuid": "10.0.0",
|
|
103
103
|
"ws": "^8.16.0",
|
|
104
|
-
"@payloadcms/translations": "3.
|
|
104
|
+
"@payloadcms/translations": "3.50.0-canary.0"
|
|
105
105
|
},
|
|
106
106
|
"devDependencies": {
|
|
107
107
|
"@hyrious/esbuild-plugin-commonjs": "0.2.6",
|