From 2c97986b19979c84d2934f2bd2819d54135ffc9b Mon Sep 17 00:00:00 2001 From: rdbsolutions Date: Thu, 27 Mar 2025 12:48:34 +0000 Subject: [PATCH] checking mime types by magic sig, extension and headers --- .../representation/representationElements.js | 53 ++++++----- components/elements/index.js | 55 ++++++----- package.json | 1 + pages/api/file/uploadsinglefile.js | 94 ++++++++++++++++--- 4 files changed, 143 insertions(+), 60 deletions(-) diff --git a/components/case/representation/representationElements.js b/components/case/representation/representationElements.js index 6057b18d..9fade1e2 100644 --- a/components/case/representation/representationElements.js +++ b/components/case/representation/representationElements.js @@ -944,8 +944,10 @@ export const RenderFileUpload = (field) => { return "/assets/images/documenttypes/zip.png"; break; case "image/jpeg": + return "/assets/images/documenttypes/jpg.png"; case "image/png": - return URL.createObjectURL(fileObj); + return "/assets/images/documenttypes/jpg.png"; + //return URL.createObjectURL(fileObj); break; default: return "/assets/images/documenttypes/default.png"; @@ -1074,6 +1076,7 @@ export const RenderFileUpload = (field) => { // Handle accepted files (clear error message when files are accepted) const handleDropAccepted = () => { + setErrorMessage(null); setRejectedFiles([]); // Clear any previously rejected file errorsss }; @@ -1169,6 +1172,12 @@ export const RenderFileUpload = (field) => { // Object.assign(values, { // "fileList": buildAppealFilesArray, // }); + data.invalidFiles.length > 0 && + (setRejectedFiles(data.invalidFiles), + setUploadCountMessage( + renamedAcceptedFiles.length - + data.invalidFiles.length + )); getFilesFromBlobproxy( field.containerID, field.ticketnumber + @@ -1215,32 +1224,16 @@ export const RenderFileUpload = (field) => { ) - - {uploadCountMessage > 0 && - completedUploadFiles == false && ( -
- {t("home:uploading-files-label", { - number: uploadCountMessage, - })} -
- )} - {completedUploadFiles > 0 && - uploadCountMessage > 0 && ( -
- {t( - "home:completed-uploading-files-label", - { - number: uploadCountMessage, - } - )} -
- )} + {/* */} )} - {rejectedFiles.length > 0 && ( -
+ {rejectedFiles.length > 0 && completedUploadFiles == true && ( +
    {rejectedFiles.map((error, index) => (
  • {error}
  • @@ -1248,6 +1241,20 @@ export const RenderFileUpload = (field) => {
)} + {uploadCountMessage > 0 && completedUploadFiles == false && ( +
+ {t("home:uploading-files-label", { + number: uploadCountMessage, + })} +
+ )} + {completedUploadFiles > 0 && uploadCountMessage >= 0 && ( +
+ {t("home:completed-uploading-files-label", { + number: uploadCountMessage, + })} +
+ )} {field.meta.touched && field.meta.error && ( {field.meta.error} )} diff --git a/components/elements/index.js b/components/elements/index.js index dfd8038d..f676b273 100644 --- a/components/elements/index.js +++ b/components/elements/index.js @@ -1851,8 +1851,10 @@ const RenderFileUpload = (field) => { return "/assets/images/documenttypes/zip.png"; break; case "image/jpeg": + return "/assets/images/documenttypes/jpg.png"; case "image/png": - return URL.createObjectURL(fileObj); + return "/assets/images/documenttypes/png.png"; + //return URL.createObjectURL(fileObj); break; default: return "/assets/images/documenttypes/default.png"; @@ -2036,13 +2038,12 @@ const RenderFileUpload = (field) => { ); field.input.onChange(renamedAcceptedFiles); - setUploadCountMessage(renamedAcceptedFiles.length); - - console.log("werwerw"); field.setFileCount( field.uploadCount + renamedAcceptedFiles.length ); + setUploadCountMessage(renamedAcceptedFiles.length); + uploadSingleFile( renamedAcceptedFiles, field.containerID, @@ -2089,6 +2090,12 @@ const RenderFileUpload = (field) => { Object.assign(values, { "filesList": buildAppealFilesArray, }); + data.invalidFiles.length > 0 && + (setRejectedFiles(data.invalidFiles), + setUploadCountMessage( + renamedAcceptedFiles.length - + data.invalidFiles.length + )); getFilesFromBlobproxy( field.containerID, field.ticketnumber @@ -2123,31 +2130,15 @@ const RenderFileUpload = (field) => {
{/* */} - {uploadCountMessage > 0 && - completedUploadFiles == false && ( -
- {t("home:uploading-files-label", { - number: uploadCountMessage, - })} -
- )} - {completedUploadFiles > 0 && - uploadCountMessage > 0 && ( -
- {t( - "home:completed-uploading-files-label", - { - number: uploadCountMessage, - } - )} -
- )} )} - {rejectedFiles.length > 0 && ( -
+ {rejectedFiles.length > 0 && completedUploadFiles == true && ( +
    {rejectedFiles.map((error, index) => (
  • {error}
  • @@ -2155,6 +2146,20 @@ const RenderFileUpload = (field) => {
)} + {uploadCountMessage > 0 && completedUploadFiles == false && ( +
+ {t("home:uploading-files-label", { + number: uploadCountMessage, + })} +
+ )} + {completedUploadFiles > 0 && uploadCountMessage >= 0 && ( +
+ {t("home:completed-uploading-files-label", { + number: uploadCountMessage, + })} +
+ )} {field.meta.touched && field.meta.error && ( {field.meta.error} )} diff --git a/package.json b/package.json index 3bc3b484..04575588 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "date-fns": "^2.30.0", "dom": "^0.0.3", "express": "^4.18.2", + "file-type": "^20.4.1", "fs": "0.0.2", "google-map-react": "^2.2.1", "govuk-frontend": "^5.0.0", diff --git a/pages/api/file/uploadsinglefile.js b/pages/api/file/uploadsinglefile.js index 382c0888..2ec13b78 100644 --- a/pages/api/file/uploadsinglefile.js +++ b/pages/api/file/uploadsinglefile.js @@ -7,6 +7,8 @@ import { import nextConnect from "next-connect"; import middleware from "../middleware/middleware"; import { consoleLogger } from "../../../actions"; +import { fileTypeFromBuffer } from "file-type"; +import fs from "fs"; const ApiProxy = nextConnect(); ApiProxy.use(middleware); @@ -35,31 +37,99 @@ ApiProxy.post(async (req, res) => { ]; var allowedFilesFormData = {}; + var invalidFiles = []; // To store the names of invalid files + + // for (const [fileName, fileDetails] of Object.entries(req.files)) { + // if (allowedMimeTypes.includes(fileDetails[0].headers["content-type"])) { + // allowedFilesFormData[fileName] = fileDetails.map((file) => ({ + // fieldName: file.fieldName, + // originalFilename: file.originalFilename, + // path: file.path, + // size: file.size, + // headers: file.headers, + // contentType: file.headers["content-type"], + // })); + // } + // } for (const [fileName, fileDetails] of Object.entries(req.files)) { - if (allowedMimeTypes.includes(fileDetails[0].headers["content-type"])) { - allowedFilesFormData[fileName] = fileDetails.map((file) => ({ - fieldName: file.fieldName, - originalFilename: file.originalFilename, - path: file.path, - size: file.size, - headers: file.headers, - contentType: file.headers["content-type"], - })); + const file = fileDetails[0]; + const filePath = file.path; + + // Check the MIME type from file headers + const fileMimeType = file.headers["content-type"]; + if (allowedMimeTypes.includes(fileMimeType)) { + // Check file signature (magic number) using file-type + const buffer = fs.readFileSync(filePath); + + try { + const type = await fileTypeFromBuffer(buffer); // Correct usage of fileTypeFromBuffer + console.log("checking mime type ", type); + if (type && allowedMimeTypes.includes(type.mime)) { + // If the MIME type from the file signature matches the allowed list + allowedFilesFormData[fileName] = fileDetails.map( + (file) => ({ + fieldName: file.fieldName, + originalFilename: file.originalFilename, + path: file.path, + size: file.size, + headers: file.headers, + contentType: fileMimeType, + }) + ); + } else { + console.log( + `\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\n\nFile ${fileName} has an invalid MIME type based on its content.\n\n\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ); + invalidFiles.push(fileName); // Track invalid file + + // return res + // .status(400) + // .json({ error: `Invalid file type for ${fileName}` }); + } + } catch (error) { + console.log( + `Error reading file ${fileName} for MIME type validation`, + error + ); + // return res + // .status(500) + // .json({ error: "Failed to validate file type" }); + } + } else { + console.log( + `\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\n\nFile ${fileName} has an unsupported MIME type: ${fileMimeType}\n\n\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ); + invalidFiles.push(fileName); // Track invalid file + + // return res.status(400).json({ + // error: `Unsupported MIME type for ${fileName}`, + // }); } } - //console.log("allowedFilesFormData: ", allowedFilesFormData); + // If no valid files are found + // if (Object.keys(allowedFilesFormData).length === 0) { + // return res.status(400).json({ + // error: "No valid files found", + // invalidFiles: invalidFiles, // Return the list of invalid files + // }); + // } + + invalidFiles = invalidFiles.map((fileName) => { + return `${fileName} - Invalid file`; + }); + try { const data = await uploadSingleFile( allowedFilesFormData, containerID, casefolderID ).then((data) => { - return res.status(200).json({ data }); + return res.status(200).json({ data, invalidFiles }); }); - return res.status(200).json({ data }); + return res.status(200).json({ data, invalidFiles }); } catch (error) { consoleLogger(error); return res.status(500).json({ error: "Failed to upload files" });