From abc0f8cb8a3d157e55f454ff7629aa5025cfd44c Mon Sep 17 00:00:00 2001
From: Felix Kaspar <ich@felixkaspar.com>
Date: Thu, 21 Dec 2023 22:52:05 +0100
Subject: [PATCH] Mime type validation for APIs

---
 .../api/dynamic-operations-controller.ts      | 11 +++---
 .../src/routes/api/workflow-controller.ts     |  8 ++++-
 shared-operations/src/wrappers/PdfFileJoi.ts  | 35 +++++++++++--------
 3 files changed, 33 insertions(+), 21 deletions(-)

diff --git a/server-node/src/routes/api/dynamic-operations-controller.ts b/server-node/src/routes/api/dynamic-operations-controller.ts
index 546147f30..ded527c22 100644
--- a/server-node/src/routes/api/dynamic-operations-controller.ts
+++ b/server-node/src/routes/api/dynamic-operations-controller.ts
@@ -8,6 +8,7 @@ import { Operator } from '@stirling-pdf/shared-operations/src/functions';
 import { PdfFile } from '@stirling-pdf/shared-operations/src/wrappers/PdfFile';
 import { respondWithPdfFiles } from 'utils/endpoint-utils';
 import { Action } from '@stirling-pdf/shared-operations/declarations/Action';
+import { JoiPDFFileSchema } from '@stirling-pdf/shared-operations/src/wrappers/PdfFileJoi';
 
 router.post('/:func', upload.array("file"), async function(req: Request, res: Response) {
     handleEndpoint(req, res);
@@ -23,12 +24,12 @@ function handleEndpoint(req: Request, res: Response) {
         return;
     }
 
-    let pdfFiles: PdfFile[] = [];
-    if (Array.isArray(req.files))
-        pdfFiles = PdfFile.fromMulterFiles(req.files);
-    else {
-        pdfFiles = PdfFile.fromMulterFiles(Object.values(req.files).flatMap(va => va));
+    const validationResults = JoiPDFFileSchema.validate(req.files);
+    if(validationResults.error) {
+        res.status(400).json({error: "PDF validation failed", details: validationResults.error.message});
+        return;
     }
+    const pdfFiles: PdfFile[] = validationResults.value;
 
     const operator = getOperatorByName(req.params.func);
     if(operator) {
diff --git a/server-node/src/routes/api/workflow-controller.ts b/server-node/src/routes/api/workflow-controller.ts
index ee805d8bf..676997cab 100644
--- a/server-node/src/routes/api/workflow-controller.ts
+++ b/server-node/src/routes/api/workflow-controller.ts
@@ -6,6 +6,7 @@ const upload = multer();
 import { traverseOperations } from "@stirling-pdf/shared-operations/src/workflow/traverseOperations";
 import { PdfFile, RepresentationType } from '@stirling-pdf/shared-operations/src/wrappers/PdfFile';
 import { respondWithPdfFiles } from '../../utils/endpoint-utils';
+import { JoiPDFFileSchema } from '@stirling-pdf/shared-operations/src/wrappers/PdfFileJoi';
 
 interface Workflow {
     eventStream?: express.Response<any, Record<string, any>>,
@@ -42,7 +43,12 @@ router.post("/:workflowUuid?", [
             }
         }
 
-        const inputs = PdfFile.fromMulterFiles(req.files as Express.Multer.File[]);
+        const validationResults = JoiPDFFileSchema.validate(req.files);
+        if(validationResults.error) {
+            res.status(400).json({error: "PDF validation failed", details: validationResults.error.message});
+            return;
+        }
+        const inputs: PdfFile[] = validationResults.value;
 
         // Allow option to do it synchronously and just make a long request
         if(req.body.async === "false") {
diff --git a/shared-operations/src/wrappers/PdfFileJoi.ts b/shared-operations/src/wrappers/PdfFileJoi.ts
index f06dc9178..d9295fe9d 100644
--- a/shared-operations/src/wrappers/PdfFileJoi.ts
+++ b/shared-operations/src/wrappers/PdfFileJoi.ts
@@ -1,22 +1,27 @@
 import Joi from "joi";
 import { PdfFile } from "./PdfFile";
 
-export const JoiPDFFileSchema = Joi.binary().custom((value: Express.Multer.File[] | PdfFile | PdfFile[], helpers) => {
-    if (value instanceof PdfFile) {
-        return value;
-    }
-    else if (Array.isArray(value)) {
-        if(value.every((e) => e instanceof PdfFile))
+export const JoiPDFFileSchema = Joi.custom((value: Express.Multer.File | Express.Multer.File[] | PdfFile | PdfFile[], helpers) => {
+    if (Array.isArray(value)) {
+        if(isPdfFileArray(value))
             return value;
-        else
-            throw new Error("Some elements in the array are not of type PdfFile");
-    }
-    else {
-        try {
+        else { // File(s)
+            if(value.some(f => f.mimetype != "application/pdf")) 
+                throw new Error("at least one of the files provided doesn't seem to be a PDF.");
+
             return PdfFile.fromMulterFiles(value);
-        } catch (error) {
-            console.error(error);
-            throw new Error('value is not of type PdfFile');
         }
     }
-}, "pdffile validation");
\ No newline at end of file
+    else {
+        if (value instanceof PdfFile) {
+            return value;
+        }
+        else { 
+            throw new Error("an invalid type (unhandeled, non-file-type) was provided to pdf validation process. Please report this to maintainers.");
+        }
+    }
+}, "pdffile validation");
+
+function isPdfFileArray(value: any): value is PdfFile[] {
+    return value.every((e) => e instanceof PdfFile)
+} 
\ No newline at end of file