Files
projectsend/app/Modules/Files/Scanning/ScannerAddress.php
T
ignacionelson 25b92c086b Refuse a scanner address that only looks like one
tcp://clamav:3310djlkasjdlk connected happily. PHP reads a port the way
atoi does — the digits at the front, the rest ignored — so an address
with a typo on the end was saved, tested, and reported as working, while
tcp://clamav:33101 went somewhere else and failed. The feedback an
operator got had nothing to do with the mistake they made.

ScannerAddress says what an address is: tcp:// with a host and a port of
1 to 65535 and nothing after it, or unix:// with an absolute path. It is
asked in all three places an address arrives — saving, testing, and
connecting. The third matters because a managed address comes from the
environment and never passes the screen.

The answer names the problem rather than reporting "no answer", which
would be true of any unreachable scanner and would send somebody to look
at their network for a typo.

Checked in a browser with both addresses from the report: each is now
refused on Test and on Save, with the same sentence, and
tcp://clamav:3310 still comes back "Working. ClamAV 1.5.4 detected the
test file".
2026-09-16 23:09:24 -03:00

57 lines
1.7 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Modules\Files\Scanning;
/**
* Whether a scanner address is one, and not merely one PHP will accept.
*
* `stream_socket_client()` reads a port the way `atoi` does: it takes the
* digits at the front and ignores whatever follows. So
* `tcp://clamav:3310djlkasjdlk` connects happily to port 3310, and an
* address with a typo on the end is saved, tested, and reported as
* working — until the day something parses it differently. Meanwhile
* `tcp://clamav:33101` goes somewhere else entirely and fails, so the
* feedback an operator gets is inconsistent with the mistake they made.
*
* This refuses both, and says so while the field is still on screen.
*/
final class ScannerAddress
{
/**
* A TCP address: host, then a port of one to five digits and nothing
* after it. The host is a hostname, an IPv4 address, or an IPv6
* address in brackets — the three forms PHP itself accepts.
*/
private const TCP = '#^tcp://(?:\[[0-9a-fA-F:]+\]|[a-zA-Z0-9._-]+):([0-9]{1,5})$#';
/** A Unix socket: an absolute path, and nothing clever. */
private const UNIX = '#^unix://(/[^\x00]+)$#';
public static function isValid(string $address): bool
{
$address = trim($address);
if (preg_match(self::UNIX, $address) === 1) {
return true;
}
if (preg_match(self::TCP, $address, $matches) !== 1) {
return false;
}
$port = (int) $matches[1];
return $port >= 1 && $port <= 65535;
}
/**
* English, and the translation key: what to type instead.
*/
public static function message(): string
{
return 'Enter the scanner as tcp://host:3310 or unix:///path/to/clamd.sock.';
}
}