mirror of
https://github.com/projectsend/projectsend.git
synced 2026-09-17 17:15:08 +00:00
0ae3f3d0f0
Sharing a file that is still being checked writes the assignment and says nothing. The announcement goes out when the file becomes available — a clean scan, a file let through while the scanner was down, or an administrator releasing it from quarantine — so nobody is ever sent to a page that refuses them, and a file about to be quarantined is not announced to everyone before anybody knows. Recipients are derived from the assignments as they stand at that moment, not remembered from the moment of sharing: a share taken back in the meantime produces no email, and one added does. New-version notices ride the same path, which they had to anyway — the audience rule re-checks visibility, and a file being scanned is not visible. Two bugs found while writing the tests, both in the hourly command: Re-queuing a file marked it pending first. Pending means withheld, so running --existing over a library that predates scanning would have hidden every file in it from every client for as long as the backfill ran, and then announced each one to its recipients a second time when it came back. The job now knows which state it expects instead, and a rescan leaves the file downloadable until a verdict actually arrives.
101 lines
3.6 KiB
PHP
101 lines
3.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Modules\Files\Console;
|
|
|
|
use App\Modules\Files\Jobs\ScanFileJob;
|
|
use App\Modules\Files\Models\File;
|
|
use App\Modules\Files\Scanning\NotScannedReason;
|
|
use App\Modules\Files\Scanning\ScanningConfig;
|
|
use App\Modules\Files\Scanning\ScanStatus;
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
|
|
/**
|
|
* Sends files back to the scanner: the ones still waiting, the ones that
|
|
* went through unscanned because it was down, and — when asked — the
|
|
* library that was already here before any of this existed.
|
|
*
|
|
* Hourly rather than daily. A file stuck pending is a file nobody can
|
|
* download, and an installation set to hold has no other way forward
|
|
* once its worker restarted and the job with it.
|
|
*/
|
|
class ScanFilesCommand extends Command
|
|
{
|
|
protected $signature = 'projectsend:scan-files
|
|
{--existing : also work through files that were never scanned because scanning was off}';
|
|
|
|
protected $description = 'Scan files that are waiting, were missed, or were never checked (runs hourly)';
|
|
|
|
public function handle(ScanningConfig $config): int
|
|
{
|
|
if (! $config->enabled()) {
|
|
$this->info('Virus scanning is switched off.');
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
$waiting = $this->dispatchFor(File::query()->where('scan_status', ScanStatus::Pending));
|
|
|
|
// Allowed through while the scanner was unreachable. Now that it
|
|
// may be back, they are asked again — a file found infected at
|
|
// this point is quarantined like any other, and its quarantine
|
|
// notice says it was available in the meantime.
|
|
$missed = $this->dispatchFor(
|
|
File::query()
|
|
->where('scan_status', ScanStatus::NotScanned)
|
|
->where('scan_note', NotScannedReason::ScannerUnavailable->value),
|
|
rescan: true,
|
|
);
|
|
|
|
$this->info("Re-queued {$waiting} waiting file(s) and {$missed} that were missed while the scanner was down.");
|
|
|
|
if ($this->option('existing')) {
|
|
// Paced, because this can be a whole library at once and the
|
|
// scanner is also serving today's uploads. An hour's worth per
|
|
// run, since that is how often this command runs.
|
|
$limit = $config->existingScanRatePerMinute() * 60;
|
|
|
|
$old = $this->dispatchFor(
|
|
File::query()
|
|
->where('scan_status', ScanStatus::NotScanned)
|
|
->where('scan_note', NotScannedReason::BeforeScanning->value),
|
|
$limit,
|
|
rescan: true,
|
|
);
|
|
|
|
$this->info("Queued {$old} file(s) that had never been scanned.");
|
|
}
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* Nothing here changes a file's state before the scanner has spoken.
|
|
*
|
|
* An earlier version marked each file pending first, which reads as
|
|
* tidy and is wrong twice over: pending means "withheld", so a
|
|
* backfill would have hidden an entire library from its clients for
|
|
* as long as it ran, and every file would then have been announced to
|
|
* its recipients a second time when it came back. The job knows which
|
|
* state it expects instead — see its $rescan.
|
|
*
|
|
* @param Builder<File> $query
|
|
*/
|
|
private function dispatchFor(Builder $query, ?int $limit = null, bool $rescan = false): int
|
|
{
|
|
if ($limit !== null) {
|
|
$query->limit($limit);
|
|
}
|
|
|
|
$ids = $query->orderBy('id')->pluck('id');
|
|
|
|
foreach ($ids as $id) {
|
|
ScanFileJob::dispatch((int) $id, $rescan);
|
|
}
|
|
|
|
return $ids->count();
|
|
}
|
|
}
|