0

I'm working on a feature to define a limit size of a pdf file to be uploaded. That part is ok, but I also need to define a limit for how many pages a pdf file has, and I couldn't find any answer to that. I am using NestJS, and my code block looks like this so far:

  @UseInterceptors(
FilesInterceptor('files', 10, {
  limits: { fileSize: 10242880 },
  fileFilter: (req, file, callback) => {
    if (!file.originalname.match(/\.(jpg|jpeg|png|pdf)$/)) {
      req.fileValidationError =
        'Invalid file type provided. Valid types: [jpg, jpeg, png, pdf]';
      //TODO: Add validation to check if the file is pdf, and if so, define a limit of pages for the file. (max * pages)
      callback(null, false);
    }

    const fileSize = parseInt(req.headers['content-length']);
    if (fileSize > 10242880) {
      req.fileValidationError = 'File size exceeds the maximum limit [10mb]';
      callback(null, false);
    }
    callback(null, true);
    if (!file.originalname.match(/\.(pdf)$/)) {
      //what to do here??
      
    }
  },
})

The multer options available in the interface, are the following. Maybe any of them can provide me with such an info, but I would really appreciate a hand with this. Thanks

export interface MulterOptions {
dest?: string;
/** The storage engine to use for uploaded files. */
storage?: any;
/**
 * An object specifying the size limits of the following optional properties. This object is passed to busboy
 * directly, and the details of properties can be found on https://github.com/mscdex/busboy#busboy-methods
 */
limits?: {
    /** Max field name size (Default: 100 bytes) */
    fieldNameSize?: number;
    /** Max field value size (Default: 1MB) */
    fieldSize?: number;
    /** Max number of non- file fields (Default: Infinity) */
    fields?: number;
    /** For multipart forms, the max file size (in bytes)(Default: Infinity) */
    fileSize?: number;
    /** For multipart forms, the max number of file fields (Default: Infinity) */
    files?: number;
    /** For multipart forms, the max number of parts (fields + files)(Default: Infinity) */
    parts?: number;
    /** For multipart forms, the max number of header key=> value pairs to parse Default: 2000(same as node's http). */
    headerPairs?: number;
};
/** Keep the full path of files instead of just the base name (Default: false) */
preservePath?: boolean;
fileFilter?(req: any, file: {
    /** Field name specified in the form */
    fieldname: string;
    /** Name of the file on the user's computer */
    originalname: string;
    /** Encoding type of the file */
    encoding: string;
    /** Mime type of the file */
    mimetype: string;
    /** Size of the file in bytes */
    size: number;
    /** The folder to which the file has been saved (DiskStorage) */
    destination: string;
    /** The name of the file within the destination (DiskStorage) */
    filename: string;
    /** Location of the uploaded file (DiskStorage) */
    path: string;
    /** A Buffer of the entire file (MemoryStorage) */
    buffer: Buffer;
}, callback: (error: Error | null, acceptFile: boolean) => void): void;

}

P Purcinelli
  • 109
  • 8

2 Answers2

2

No, multer alone can't count no. of pages in a pdf since its just a "multipart/form-data" parser.

You need to use a PDF parser to count no. of pages like pdf-lib, see example.


Btw I have some suggestions to your multer validation code.

  1. you should be validating mime type of file not its file extension.
  2. Determining file size through content-length header is really bad idea, because it can be manipulated by end-user (using curl or netcat). Instead use file.size provided by fileFilter args. Since you have already set limits.fileSize option validating file size here becomes pointless.
const INVALID_PDF_ERROR_MSG = 'Invalid file type provided. Valid types: [jpg, jpeg, png, pdf]'

FilesInterceptor('files', 10, {
  limits: {
    fileSize: 10 * 1024 * 1024, // 10Mb in bytes
  },
  fileFilter: (req, file, callback) => {
    if (file.mime !== 'application/pdf') {
      req.fileValidationError = INVALID_PDF_ERROR_MSG
      callback(null, false) // ```callback(new Error(INVALID_PDF_ERROR_MSG), false)``` is right way to create errors but I leave it you
      return
    }
    // // point less since limits.fileSize is provided
    // if (file.size > 10242880) callback(new Error('File size exceeds the maximum limit [10mb]'), false)

    // I recommend not counting pages here, instead do inside of route handler.
    callback(null, true)
  },
})


bogdanoff
  • 1,705
  • 1
  • 10
  • 20
0

The best solution I was able to provide was using pdf-lib. I moved the page limit check, outside the FIleFilter from multer, because there is no buffer from a file at that point. The solution was used inside a different function, as suggested by @bogdanoff

try {
  for (const file of files) {
    const isPdf = file.mimetype === 'application/pdf';
    if (isPdf) {
      const loadPdf = PDFDocument.load(file.buffer); 
      //the PDFDocument above, is the pdf-lib imported
      const pdf = await loadPdf;
      const pagesCount = pdf.getPageCount();
      if (pagesCount > 5) {
        throw new BadRequestException({
          statusCode: 400,
          message: 'PDF file must have a maximum of 5 pages.',
        });
      }
    }
  }
} catch (error) {
  this.commonLogger.error({
    error,
    serviceName: this.controllerName,
    method: 'createCredential',
    params: { vendorId, userId, credentialDto },
  });
  throw new BadRequestException({ statusCode: 400, message: error.message });
}

}

P Purcinelli
  • 109
  • 8