* Append a virus detection record to META_DIR/virus_detections.log (JSONL). * * @param string $path The scanned file path on disk. * @param string $rawMessage Raw clamscan output (stdout/stderr combined). * @param array $context Extra context: folder, file, user, ip, source, etc. * @param string $cmd Command used (clamscan / custom). * @param int $
(
string $path,
string $rawMessage,
array $context,
string $cmd,
int $exitCode
)
| 1687 | * @param int $exitCode ClamAV exit code. |
| 1688 | */ |
| 1689 | private static function logVirusDetection( |
| 1690 | string $path, |
| 1691 | string $rawMessage, |
| 1692 | array $context, |
| 1693 | string $cmd, |
| 1694 | int $exitCode |
| 1695 | ): void { |
| 1696 | try { |
| 1697 | $baseMeta = rtrim(self::metaRoot(), '/\\') . DIRECTORY_SEPARATOR; |
| 1698 | if (!is_dir($baseMeta)) { |
| 1699 | @mkdir($baseMeta, 0775, true); |
| 1700 | } |
| 1701 | |
| 1702 | $user = $context['user'] ?? ($_SESSION['username'] ?? 'Unknown'); |
| 1703 | $ip = $context['ip'] ?? self::getClientIp(); |
| 1704 | $source = $context['source'] ?? 'normal'; |
| 1705 | |
| 1706 | // Folder + file in log – prefer context, fallback to path |
| 1707 | $fileName = $context['file'] ?? basename($path); |
| 1708 | $folder = $context['folder'] ?? null; |
| 1709 | |
| 1710 | if ($folder === null) { |
| 1711 | // Best-effort: derive folder relative to upload root |
| 1712 | $rootDir = rtrim(self::uploadRoot(), '/\\') . DIRECTORY_SEPARATOR; |
| 1713 | if (strpos($path, $rootDir) === 0) { |
| 1714 | $rel = substr($path, strlen($rootDir)); |
| 1715 | $rel = str_replace(DIRECTORY_SEPARATOR, '/', $rel); |
| 1716 | $pos = strrpos($rel, '/'); |
| 1717 | $folder = ($pos !== false) ? substr($rel, 0, $pos) : ''; |
| 1718 | } else { |
| 1719 | $folder = ''; |
| 1720 | } |
| 1721 | } |
| 1722 | |
| 1723 | $msg = self::truncateLogMessage($rawMessage, 400); |
| 1724 | |
| 1725 | $record = [ |
| 1726 | 'ts' => gmdate('c'), |
| 1727 | 'user' => $user, |
| 1728 | 'ip' => $ip, |
| 1729 | 'folder' => ($folder === '' ? 'root' : $folder), |
| 1730 | 'file' => $fileName, |
| 1731 | 'source' => $source, |
| 1732 | 'engine' => $cmd, |
| 1733 | 'exitCode' => $exitCode, |
| 1734 | 'message' => $msg, |
| 1735 | ]; |
| 1736 | |
| 1737 | $json = json_encode($record, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); |
| 1738 | if ($json === false) { |
| 1739 | return; |
| 1740 | } |
| 1741 | |
| 1742 | // *** Canonical base path: matches virusLog.php *** |
| 1743 | $logFile = $baseMeta . 'virus_detections.log'; |
| 1744 | |
| 1745 | // Soft rotation |
| 1746 | if (file_exists($logFile) && filesize($logFile) > self::VIRUS_LOG_MAX_BYTES) { |
no test coverage detected