Skip to content

Commit

Permalink
Errors from clamav when scanning files now throw FileScanException or…
Browse files Browse the repository at this point in the history
… \RuntimeException if the response could not be parsed.

Fixed reading response from socket so any errors are caught.
  • Loading branch information
pmishev committed Jan 18, 2022
1 parent 5fba062 commit 5601b75
Show file tree
Hide file tree
Showing 8 changed files with 99 additions and 18 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"symfony/http-kernel": "~4.0 || ~5.0",
"symfony/config": "~4.0 || ~5.0",
"symfony/dependency-injection": "~4.0 || ~5.0",
"symfony/yaml": "~4.0 || ~5.0"
"symfony/yaml": "~4.0 || ~5.0",
"ext-sockets": "*"
},
"require-dev": {
"phpunit/phpunit": "^9.0"
Expand Down
14 changes: 12 additions & 2 deletions src/DTO/ScannedFile.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace Sineflow\ClamAV\DTO;

use Sineflow\ClamAV\Exception\FileScanException;

class ScannedFile
{
/**
Expand Down Expand Up @@ -29,9 +31,17 @@ class ScannedFile
*/
public function __construct(string $rawResponse)
{
$this->rawResponse = $rawResponse;
$isParsed = preg_match('/(.*):(.*)(FOUND|OK|ERROR)/i', $rawResponse, $matches);

if (!$isParsed) {
throw new \RuntimeException(sprintf('Failed to parse clamav response: %s', $rawResponse));
}

preg_match('/(.*):(.*)(FOUND|OK)/i', $rawResponse, $matches);
if ($matches[3] === 'ERROR') {
throw new FileScanException($matches[1], trim($matches[2]));
}

$this->rawResponse = $rawResponse;
$this->fileName = $matches[1];
$this->virusName = trim($matches[2]);
$this->isClean = ($matches[3] === 'OK');
Expand Down
45 changes: 45 additions & 0 deletions src/Exception/FileScanException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

namespace Sineflow\ClamAV\Exception;

class FileScanException extends \RuntimeException
{
/**
* @var string
*/
private $fileName;

/**
* @var string
*/
private $errorMessage;

/**
* @param string $fileName
* @param string $errorMessage
*/
public function __construct(string $fileName, string $errorMessage)
{
$this->fileName = $fileName;
$this->errorMessage = $errorMessage;
$message = sprintf('Error scanning "%s": %s', $fileName, $errorMessage);

parent::__construct($message);
}

/**
* @return string
*/
public function getFileName(): string
{
return $this->fileName;
}

/**
* @return string
*/
public function getErrorMessage(): string
{
return $this->errorMessage;
}
}
8 changes: 4 additions & 4 deletions src/Exception/SocketException.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ class SocketException extends \RuntimeException
protected $errorCode;

/**
* @param string $message
* @param int $socketErrorCode
* @param string $message
* @param int|null $socketErrorCode
*/
public function __construct($message, int $socketErrorCode = null)
public function __construct(string $message, int $socketErrorCode = null)
{
$this->errorCode = $socketErrorCode;
if ($socketErrorCode) {
Expand All @@ -27,7 +27,7 @@ public function __construct($message, int $socketErrorCode = null)
* Get socket error (returned from 'socket_last_error')
* @return int
*/
public function getErrorCode()
public function getErrorCode(): ?int
{
return $this->errorCode;
}
Expand Down
4 changes: 3 additions & 1 deletion src/ScanStrategy/AbstractScanStrategyClamdSocket.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Sineflow\ClamAV\ScanStrategy;

use Sineflow\ClamAV\DTO\ScannedFile;
use Sineflow\ClamAV\Exception\FileScanException;
use Sineflow\ClamAV\Socket\Socket;

abstract class AbstractScanStrategyClamdSocket
Expand All @@ -19,8 +20,9 @@ abstract class AbstractScanStrategyClamdSocket
*/
public function scan(string $filePath): ScannedFile
{
// clamav can scan a directory, but we don't support this at the moment
if (!is_file($filePath)) {
throw new \RuntimeException(sprintf('%s is not a file', $filePath));
throw new FileScanException($filePath, 'Not a file.');
}

$response = $this->socket->sendCommand('SCAN ' . $filePath);
Expand Down
12 changes: 8 additions & 4 deletions src/Socket/Socket.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,19 @@ public function sendCommand($dataIn, $flagsSend = 0, $flagsReceive = MSG_WAITALL
$this->connect();

if (false === socket_send($this->socket, $dataIn, strlen($dataIn), $flagsSend)) {
throw new SocketException('Writing to socket failed', socket_last_error());
throw new SocketException('Writing to socket failed', socket_last_error($this->socket));
}
$dataOut = '';
while ($bytes = socket_recv($this->socket, $chunk, self::MAX_READ_BYTES, $flagsReceive)) {

do {
$bytes = socket_recv($this->socket, $chunk, self::MAX_READ_BYTES, $flagsReceive);
if (false === $bytes) {
throw new SocketException('Reading from socket failed', socket_last_error());
$socketError = socket_last_error($this->socket);
socket_close($this->socket);
throw new SocketException('Reading from socket failed', $socketError);
}
$dataOut .= $chunk;
}
} while ($bytes);
socket_close($this->socket);

return $dataOut;
Expand Down
1 change: 1 addition & 0 deletions tests/Files/inaccessible.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Permissions for this file will be set to 000 in the beginning of the test, to make sure it's inaccessible
30 changes: 24 additions & 6 deletions tests/Functional/ScannerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,26 @@
namespace Sineflow\ClamAV\ScanStrategy\Tests;

use PHPUnit\Framework\TestCase;
use Sineflow\ClamAV\Exception\FileScanException;
use Sineflow\ClamAV\Scanner;
use Sineflow\ClamAV\ScanStrategy\ScanStrategyClamdNetwork;
use Sineflow\ClamAV\ScanStrategy\ScanStrategyClamdUnix;

class ScannerTest extends TestCase
{
private static $originalFilePermissionsOfInaccessibleFile;

public static function setUpBeforeClass(): void
{
self::$originalFilePermissionsOfInaccessibleFile = fileperms(__DIR__.'/../Files/inaccessible.txt');
chmod(__DIR__.'/../Files/inaccessible.txt', 0000);
}

public static function tearDownAfterClass(): void
{
chmod(__DIR__.'/../Files/inaccessible.txt', self::$originalFilePermissionsOfInaccessibleFile);
}

public function testPingWithClamdUnix()
{
$scanner = new Scanner(new ScanStrategyClamdUnix());
Expand Down Expand Up @@ -50,13 +64,15 @@ public function testScanValidFilesWithClamdUnix(string $filePath, bool $expected
/**
* @dataProvider invalidFilesToCheckProvider
*/
public function testScanInvalidFilesWithClamdUnix(string $filePath)
public function testScanInvalidFilesWithClamdUnix(string $filePath, string $expectedErrorMessage)
{
$scanner = new Scanner(new ScanStrategyClamdUnix());

$this->expectException(\RuntimeException::class);
$this->expectException(FileScanException::class);
$this->expectExceptionMessage($expectedErrorMessage);
$scanner->scan($filePath);
}

/**
* @dataProvider validFilesToCheckProvider
*/
Expand All @@ -73,11 +89,12 @@ public function testScanValidFilesWithClamdNetwork(string $filePath, bool $expec
/**
* @dataProvider invalidFilesToCheckProvider
*/
public function testScanInvalidFilesWithClamdNetwork(string $filePath)
public function testScanInvalidFilesWithClamdNetwork(string $filePath, string $expectedErrorMessage)
{
$scanner = new Scanner(new ScanStrategyClamdNetwork());

$this->expectException(\RuntimeException::class);
$this->expectException(FileScanException::class);
$this->expectExceptionMessage($expectedErrorMessage);
$scanner->scan($filePath);
}

Expand All @@ -94,8 +111,9 @@ public function validFilesToCheckProvider()
public function invalidFilesToCheckProvider()
{
return [
[__DIR__.'/../Files/'],
['file_does_not_exist'],
[__DIR__.'/../Files/', 'Error scanning "'.__DIR__.'/../Files/'.'": Not a file.'],
['file_does_not_exist', 'Error scanning "file_does_not_exist": Not a file.'],
[__DIR__.'/../Files/inaccessible.txt', 'Error scanning "'.__DIR__.'/../Files/inaccessible.txt'.'": Access denied.'],
];
}
}

0 comments on commit 5601b75

Please sign in to comment.