75 lines
2.6 KiB
TypeScript
75 lines
2.6 KiB
TypeScript
import { ConfigService } from '@nestjs/config';
|
|
import * as path from 'path';
|
|
import * as fs from 'fs';
|
|
import * as crypto from 'crypto';
|
|
import multer from 'multer';
|
|
|
|
export interface UploadLimits {
|
|
fileSize: number;
|
|
}
|
|
|
|
/**
|
|
* Build a multer-style `limits` object from the UPLOAD_SIZE_LIMIT env value.
|
|
* Examples: '50mb', '500kb', '2gb'. Defaults to '50mb'.
|
|
*/
|
|
export function parseUploadSizeLimit(value: string | undefined): number {
|
|
const v = (value ?? '50mb').trim().toLowerCase();
|
|
const m = /^(\d+(?:\.\d+)?)(b|kb|mb|gb)$/.exec(v);
|
|
if (!m) return 50 * 1024 * 1024;
|
|
const n = parseFloat(m[1]);
|
|
const unit = m[2];
|
|
const mult = unit === 'b' ? 1 : unit === 'kb' ? 1024 : unit === 'mb' ? 1024 * 1024 : 1024 * 1024 * 1024;
|
|
return Math.floor(n * mult);
|
|
}
|
|
|
|
export function buildUploadLimits(config: ConfigService): UploadLimits {
|
|
const v = config.get<string>('UPLOAD_SIZE_LIMIT', '50mb');
|
|
return { fileSize: parseUploadSizeLimit(v) };
|
|
}
|
|
|
|
/**
|
|
* Produce a safe, collision-resistant filename from the original upload
|
|
* filename. Strips directory traversal, lowercases, restricts to a safe
|
|
* charset, caps the stem length, and appends a short random suffix so
|
|
* identical names from different uploads never overwrite each other.
|
|
*/
|
|
export function safeFilename(original: string): string {
|
|
const base = path.basename(original || 'file').toLowerCase();
|
|
const cleaned = base
|
|
.replace(/[^a-z0-9._-]+/g, '-')
|
|
.replace(/^-+|-+$/g, '')
|
|
.slice(0, 120);
|
|
const stem = cleaned || 'file';
|
|
const dot = stem.lastIndexOf('.');
|
|
const head = dot > 0 ? stem.slice(0, dot) : stem;
|
|
const ext = dot > 0 ? stem.slice(dot) : '';
|
|
const suffix = crypto.randomBytes(4).toString('hex');
|
|
const safeHead = (head || 'file').slice(0, 100);
|
|
return `${safeHead}-${suffix}${ext}`;
|
|
}
|
|
|
|
/**
|
|
* Build a configured Multer instance with a per-route `fileSize` limit
|
|
* (taken from `UPLOAD_SIZE_LIMIT`) and safe-filename storage at the
|
|
* supplied destination directory.
|
|
*/
|
|
export interface MulterOptions {
|
|
destination?: string;
|
|
fieldName?: string;
|
|
fileSize?: number;
|
|
}
|
|
|
|
export function buildMulter(config: ConfigService, opts: MulterOptions = {}): multer.Multer {
|
|
const limits = opts.fileSize !== undefined
|
|
? { fileSize: opts.fileSize }
|
|
: buildUploadLimits(config);
|
|
const dest = opts.destination ?? path.resolve(config.get<string>('UPLOAD_DIR', '/data/hipctf/uploads'));
|
|
if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
|
|
return multer({
|
|
storage: multer.diskStorage({
|
|
destination: (_req, _file, cb) => cb(null, dest),
|
|
filename: (_req, file, cb) => cb(null, safeFilename(file.originalname)),
|
|
}),
|
|
limits,
|
|
});
|
|
} |