@@ -14,7 +14,7 @@ use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use Throwable;
|
||||
|
||||
class ScanDocumentForMalware implements ShouldQueue, ShouldBeUnique
|
||||
class ScanDocumentForMalware implements ShouldBeUnique, ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
@@ -31,6 +31,7 @@ class ScanDocumentForMalware implements ShouldQueue, ShouldBeUnique
|
||||
return $this->documentId;
|
||||
}
|
||||
|
||||
/** @return list<int> */
|
||||
public function backoff(): array
|
||||
{
|
||||
return [15, 60];
|
||||
@@ -38,7 +39,7 @@ class ScanDocumentForMalware implements ShouldQueue, ShouldBeUnique
|
||||
|
||||
public function handle(MalwareScanner $scanner): void
|
||||
{
|
||||
$document = DB::transaction(function (): ?object {
|
||||
$document = DB::transaction(function (): ?\stdClass {
|
||||
$row = DB::table('documents')->where('id', $this->documentId)->lockForUpdate()->first();
|
||||
if (! $row || $row->status === 'deleted' || $row->deleted_at !== null) {
|
||||
return null;
|
||||
@@ -66,8 +67,8 @@ class ScanDocumentForMalware implements ShouldQueue, ShouldBeUnique
|
||||
$disk = Storage::disk('local');
|
||||
$root = $disk->path('');
|
||||
$path = $disk->path($document->storage_key);
|
||||
$beforeHash = is_file($path) ? hash_file('sha256', $path) : false;
|
||||
if (! is_string($beforeHash) || ! hash_equals(trim($document->sha256), $beforeHash)) {
|
||||
$beforeHash = $this->fileHash($path);
|
||||
if ($beforeHash === null || ! hash_equals(trim($document->sha256), $beforeHash)) {
|
||||
$this->record($document, [
|
||||
'result' => 'inconclusive', 'reason_code' => 'file_hash_mismatch', 'engine' => 'ClamAV',
|
||||
'engine_version' => 'unknown', 'signatures_at' => null, 'duration_ms' => 0, 'detail' => null,
|
||||
@@ -78,8 +79,8 @@ class ScanDocumentForMalware implements ShouldQueue, ShouldBeUnique
|
||||
|
||||
$result = $scanner->scan($path, (int) ($document->size_bytes ?? filesize($path)), $root);
|
||||
|
||||
$afterHash = is_file($path) ? hash_file('sha256', $path) : false;
|
||||
if (! is_string($afterHash) || ! hash_equals(trim($document->sha256), $afterHash)) {
|
||||
$afterHash = $this->fileHash($path);
|
||||
if ($afterHash === null || ! hash_equals(trim($document->sha256), $afterHash)) {
|
||||
$result['result'] = 'inconclusive';
|
||||
$result['reason_code'] = 'file_changed_during_scan';
|
||||
$result['detail'] = null;
|
||||
@@ -102,7 +103,17 @@ class ScanDocumentForMalware implements ShouldQueue, ShouldBeUnique
|
||||
], now(), 'failed');
|
||||
}
|
||||
|
||||
private function record(object $document, array $result, \DateTimeInterface $startedAt, string $state): void
|
||||
private function fileHash(string $path): ?string
|
||||
{
|
||||
// Re-read the filesystem after the scanner; files may change meanwhile.
|
||||
clearstatcache(true, $path);
|
||||
$hash = is_file($path) ? hash_file('sha256', $path) : false;
|
||||
|
||||
return is_string($hash) ? $hash : null;
|
||||
}
|
||||
|
||||
/** @param array<string,mixed> $result */
|
||||
private function record(\stdClass $document, array $result, \DateTimeInterface $startedAt, string $state): void
|
||||
{
|
||||
DB::transaction(function () use ($document, $result, $startedAt, $state): void {
|
||||
$locked = DB::table('documents')->where('id', $this->documentId)->lockForUpdate()->first();
|
||||
|
||||
Reference in New Issue
Block a user