Files
pedwfrontend/pages/api/file/uploadsinglefile.js

187 lines
5.8 KiB
JavaScript

import { uploadSingleFile } from "../../../actions/azurestorage";
import nextConnect from "next-connect";
import middleware from "../middleware/middleware";
import { consoleLogger } from "../../../actions/core/logger";
import { hashAPIPath } from "../../../actions/core/hash";
import { respondError, respondSuccess } from "../middleware/apiResponse";
import { fileTypeFromBuffer } from "file-type";
import fs from "fs";
import path from "path";
const FILENAME_ALLOWED = /^[A-Za-z0-9 ._\-:()—']+$/;
const MAX_FILES_PER_UPLOAD_BATCH = Math.max(
1,
Number.parseInt(process.env.UPLOAD_BATCH_COUNT || "5", 10) || 5
);
function validateFilenameServer(originalFilename) {
const name = path.basename(originalFilename || "");
if (!name) return { ok: false, reason: "Empty filename" };
if (name.includes("#")) return { ok: false, reason: "Filename contains #" };
if (/[<>"/\\|?*]/.test(name))
return { ok: false, reason: "Filename contains reserved characters" };
if (!FILENAME_ALLOWED.test(name))
return { ok: false, reason: "Filename contains invalid characters" };
if (name === "." || name === "..")
return { ok: false, reason: "Invalid filename" };
return { ok: true, name };
}
const ApiProxy = nextConnect();
ApiProxy.use(middleware);
ApiProxy.post(async (req, res) => {
const checkHash = req.query.hash;
if (typeof checkHash === "undefined" || checkHash.length === 0) {
return respondError(res, {
status: 400,
code: "HASH_REQUIRED",
message: "hash is required"
});
}
const hashCandidatePaths = [
"/api/file/uploadsinglefile",
"/api/file/uploadsinglefile?"
];
const isHashValid = hashCandidatePaths.some(
(candidatePath) => hashAPIPath(candidatePath) == "?hash=" + checkHash
);
if (!isHashValid) {
return respondError(res, {
status: 400,
code: "INVALID_HASH",
message: "Invalid hash"
});
}
const containerID = req.body?.containerID?.[0];
const casefolderID = req.body?.casefolderID?.[0];
if (
typeof containerID === "undefined" ||
containerID.length === 0 ||
typeof casefolderID === "undefined" ||
casefolderID.length === 0
) {
return respondError(res, {
status: 400,
code: "MISSING_REQUIRED_BODY",
message: "containerID and casefolderID are required"
});
}
const uploadedFiles = req.files || {};
// Add other mimetypes here
const allowedMimeTypes = [
"application/pdf",
"application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"image/tiff",
"image/jpeg",
"application/zip",
"image/png",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
];
const allowedFilesFormData = {};
const invalidFiles = []; // To store the names of invalid files
for (const [fileName, fileDetails] of Object.entries(uploadedFiles)) {
const file = fileDetails[0];
const filePath = file.path;
// validate ORIGINAL filename from upload
const fnCheck = validateFilenameServer(file.originalFilename);
if (!fnCheck.ok) {
invalidFiles.push(`${file.originalFilename} - Invalid filename`);
continue; // do not process further
}
// 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
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 {
invalidFiles.push(fileName); // Track invalid file
}
} catch (error) {
consoleLogger(error);
}
} else {
invalidFiles.push(fileName); // Track invalid file
}
}
try {
const allowedFileEntries = Object.entries(allowedFilesFormData);
const data = [];
for (
let index = 0;
index < allowedFileEntries.length;
index += MAX_FILES_PER_UPLOAD_BATCH
) {
const fileChunk = Object.fromEntries(
allowedFileEntries.slice(
index,
index + MAX_FILES_PER_UPLOAD_BATCH
)
);
const uploadChunkResult = await uploadSingleFile(
fileChunk,
containerID,
casefolderID
);
if (Array.isArray(uploadChunkResult)) {
data.push(...uploadChunkResult);
}
}
return respondSuccess(res, { data, invalidFiles });
} catch (error) {
consoleLogger(error);
return respondError(res, {
status: 500,
code: "UPLOAD_SINGLE_FILE_FAILED",
message: "Failed to upload files"
});
}
});
export const config = {
api: {
bodyParser: false
}
};
export default ApiProxy;