78 lines
2.4 KiB
TypeScript
78 lines
2.4 KiB
TypeScript
|
|
import { mkdir } from 'fs/promises';
|
||
|
|
import { basename, join, resolve } from 'path';
|
||
|
|
import { FILE } from './constants';
|
||
|
|
import settingsCache from './settings';
|
||
|
|
|
||
|
|
/** Upload directory path */
|
||
|
|
export const UPLOAD_DIR = resolve(process.cwd(), 'uploads');
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Sanitizes a filename by removing path traversal sequences and directory separators.
|
||
|
|
* Returns only the base filename to prevent directory escape attacks.
|
||
|
|
*/
|
||
|
|
export function sanitizeFilename(filename: string): string {
|
||
|
|
// Get only the base filename, stripping any directory components
|
||
|
|
const base = basename(filename);
|
||
|
|
// Remove any remaining null bytes or other dangerous characters
|
||
|
|
return base.replace(/[\x00-\x1f]/g, '');
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Validates that a file path is safely within the upload directory.
|
||
|
|
* Prevents path traversal attacks by checking the resolved absolute path.
|
||
|
|
*/
|
||
|
|
export function isPathSafe(filePath: string): boolean {
|
||
|
|
const resolvedPath = resolve(filePath);
|
||
|
|
return resolvedPath.startsWith(UPLOAD_DIR + '/') || resolvedPath === UPLOAD_DIR;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Gets max file size from instance settings (in KB), converted to bytes.
|
||
|
|
* Defaults to 10MB if not configured.
|
||
|
|
*/
|
||
|
|
export function getMaxFileSize(): number {
|
||
|
|
const settings = settingsCache.get('instanceSettings');
|
||
|
|
const maxSecretSizeKB = settings?.maxSecretSize ?? FILE.DEFAULT_MAX_SIZE_KB;
|
||
|
|
return maxSecretSizeKB * 1024; // Convert KB to bytes
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Ensures the upload directory exists, creating it if necessary.
|
||
|
|
*/
|
||
|
|
export async function ensureUploadDir(): Promise<void> {
|
||
|
|
try {
|
||
|
|
await mkdir(UPLOAD_DIR, { recursive: true });
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Failed to create upload directory:', error);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Generates a safe file path within the upload directory.
|
||
|
|
* @param id - Unique identifier for the file
|
||
|
|
* @param originalFilename - Original filename to sanitize
|
||
|
|
* @returns Object with sanitized filename and full path, or null if invalid
|
||
|
|
*/
|
||
|
|
export function generateSafeFilePath(
|
||
|
|
id: string,
|
||
|
|
originalFilename: string
|
||
|
|
): { filename: string; path: string } | null {
|
||
|
|
const safeFilename = sanitizeFilename(originalFilename);
|
||
|
|
if (!safeFilename) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
const filename = `${id}-${safeFilename}`;
|
||
|
|
const path = join(UPLOAD_DIR, filename);
|
||
|
|
|
||
|
|
// Verify path is safe
|
||
|
|
if (!isPathSafe(path)) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
return { filename, path };
|
||
|
|
}
|
||
|
|
|
||
|
|
// Initialize upload directory on module load
|
||
|
|
ensureUploadDir();
|