( file: ZipFileMetadata, state: ZipValidationState, )
| 61 | * Validates a single file during zip extraction |
| 62 | */ |
| 63 | export function validateZipFile( |
| 64 | file: ZipFileMetadata, |
| 65 | state: ZipValidationState, |
| 66 | ): FileValidationResult { |
| 67 | state.fileCount++ |
| 68 | |
| 69 | let error: string | undefined |
| 70 | |
| 71 | // Check file count |
| 72 | if (state.fileCount > LIMITS.MAX_FILE_COUNT) { |
| 73 | error = `Archive contains too many files: ${state.fileCount} (max: ${LIMITS.MAX_FILE_COUNT})` |
| 74 | } |
| 75 | |
| 76 | // Validate path safety |
| 77 | if (!isPathSafe(file.name)) { |
| 78 | error = `Unsafe file path detected: "${file.name}". Path traversal or absolute paths are not allowed.` |
| 79 | } |
| 80 | |
| 81 | // Check individual file size |
| 82 | const fileSize = file.originalSize || 0 |
| 83 | if (fileSize > LIMITS.MAX_FILE_SIZE) { |
| 84 | error = `File "${file.name}" is too large: ${Math.round(fileSize / 1024 / 1024)}MB (max: ${Math.round(LIMITS.MAX_FILE_SIZE / 1024 / 1024)}MB)` |
| 85 | } |
| 86 | |
| 87 | // Track total uncompressed size |
| 88 | state.totalUncompressedSize += fileSize |
| 89 | |
| 90 | // Check total size |
| 91 | if (state.totalUncompressedSize > LIMITS.MAX_TOTAL_SIZE) { |
| 92 | error = `Archive total size is too large: ${Math.round(state.totalUncompressedSize / 1024 / 1024)}MB (max: ${Math.round(LIMITS.MAX_TOTAL_SIZE / 1024 / 1024)}MB)` |
| 93 | } |
| 94 | |
| 95 | // Check compression ratio for zip bomb detection |
| 96 | const currentRatio = state.totalUncompressedSize / state.compressedSize |
| 97 | if (currentRatio > LIMITS.MAX_COMPRESSION_RATIO) { |
| 98 | error = `Suspicious compression ratio detected: ${currentRatio.toFixed(1)}:1 (max: ${LIMITS.MAX_COMPRESSION_RATIO}:1). This may be a zip bomb.` |
| 99 | } |
| 100 | |
| 101 | return error ? { isValid: false, error } : { isValid: true } |
| 102 | } |
| 103 | |
| 104 | /** |
| 105 | * Unzips data from a Buffer and returns its contents as a record of file paths to Uint8Array data. |
no test coverage detected