From 22a14c5c6acba9231f59ddb23e16e1d213975db8 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 23 May 2024 16:16:39 +0200 Subject: [PATCH 1/4] test: add tests for ICAP response parsing Signed-off-by: Robin Appelman --- tests/ICAP/ResponseParserTest.php | 38 +++++++++++++++++++++++++++++++ tests/data/icap/403-response.txt | 13 +++++++++++ 2 files changed, 51 insertions(+) create mode 100644 tests/ICAP/ResponseParserTest.php create mode 100644 tests/data/icap/403-response.txt diff --git a/tests/ICAP/ResponseParserTest.php b/tests/ICAP/ResponseParserTest.php new file mode 100644 index 0000000..0b9c2be --- /dev/null +++ b/tests/ICAP/ResponseParserTest.php @@ -0,0 +1,38 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Files_Antivirus\Tests\ICAP; + +use OCA\Files_Antivirus\ICAP\ResponseParser; +use Test\TestCase; + +class ResponseParserTest extends TestCase { + private function parseResponse(string $responsePath) { + return (new ResponseParser())->read_response(fopen($responsePath, 'r')); + } + + public function testParse403() { + $response = $this->parseResponse(__DIR__ . '/../data/icap/403-response.txt'); + $this->assertEquals('HTTP/1.1 403 Forbidden', $response->getResponseHeaders()['HTTP_STATUS']); + } +} diff --git a/tests/data/icap/403-response.txt b/tests/data/icap/403-response.txt new file mode 100644 index 0000000..cc771b6 --- /dev/null +++ b/tests/data/icap/403-response.txt @@ -0,0 +1,13 @@ +ICAP/1.0 200 OK +ISTag: "b9eb4f031598bb0b-1716440776" +Encapsulated: res-hdr=0, res-body=70 +X-Infection-Found: Type=0; Resolution=0; Threat=Eicar; +X-Virus-ID: Testdatei +X-Response-Info: Blocked +X-Response-Description: Durch Löschen gesäubert + +HTTP/1.1 403 Forbidden +Content-Type: text/html +Content-Length: 0 + +0 From 7de05b413c8950cd6044deac8061b16b40699f87 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 23 May 2024 16:17:00 +0200 Subject: [PATCH 2/4] fix: improve parsing of encapsulated icap headers Signed-off-by: Robin Appelman --- lib/ICAP/ResponseParser.php | 45 ++++++++++--------------------------- 1 file changed, 12 insertions(+), 33 deletions(-) diff --git a/lib/ICAP/ResponseParser.php b/lib/ICAP/ResponseParser.php index d24ebbc..a4d6ce0 100644 --- a/lib/ICAP/ResponseParser.php +++ b/lib/ICAP/ResponseParser.php @@ -66,58 +66,37 @@ private function readIcapStatusLine($stream): IcapResponseStatus { } private function parseResHdr($stream, string $headerValue): array { - $encapsulatedHeaders = []; $encapsulatedParts = \explode(",", $headerValue); foreach ($encapsulatedParts as $encapsulatedPart) { $pieces = \explode("=", \trim($encapsulatedPart)); - if ($pieces[1] === "0") { - continue; + if ($pieces[0] === "res-hdr") { + $offset = (int)$pieces[1]; + if ($offset > 0) { + fseek($stream, $offset); + } + break; } - $rawEncapsulatedHeaders = \fread($stream, (int)$pieces[1]); - $encapsulatedHeaders = $this->parseEncapsulatedHeaders($rawEncapsulatedHeaders); - // According to the spec we have a single res-hdr part and are not interested in res-body content - break; } + + $status = trim(\fgets($stream)); + $encapsulatedHeaders = $this->readHeaders($stream); + $encapsulatedHeaders['HTTP_STATUS'] = $status; + return $encapsulatedHeaders; } private function readHeaders($stream): array { $headers = []; - $prevString = ""; while (($headerString = \fgets($stream)) !== false) { $trimmedHeaderString = \trim($headerString); - if ($prevString === "" && $trimmedHeaderString === "") { + if ($trimmedHeaderString === "") { break; } [$headerName, $headerValue] = $this->parseHeader($trimmedHeaderString); if ($headerName !== '') { $headers[$headerName] = $headerValue; - if ($headerName == "Encapsulated") { - break; - } - } - $prevString = $trimmedHeaderString; - } - return $headers; - } - - private function parseEncapsulatedHeaders(string $headerString): array { - $headers = []; - $split = \preg_split('/\r?\n/', \trim($headerString)); - $statusLine = \array_shift($split); - if ($statusLine !== null) { - $headers['HTTP_STATUS'] = $statusLine; - } - foreach (\preg_split('/\r?\n/', $headerString) as $line) { - if ($line === '') { - continue; - } - [$name, $value] = $this->parseHeader($line); - if ($name !== '') { - $headers[$name] = $value; } } - return $headers; } From 0c351be62f7a4887f61fb1293bdf76ebd5b8319d Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 29 May 2024 15:14:40 +0200 Subject: [PATCH 3/4] increate icap timeout Signed-off-by: Robin Appelman --- lib/ICAP/ICAPClient.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/ICAP/ICAPClient.php b/lib/ICAP/ICAPClient.php index 7a37b56..cd8f71d 100644 --- a/lib/ICAP/ICAPClient.php +++ b/lib/ICAP/ICAPClient.php @@ -67,6 +67,8 @@ protected function connect() { $this->connectTimeout ); + socket_set_timeout($stream, 600); + if (!$stream) { throw new RuntimeException( "Cannot connect to \"tcp://{$this->host}:{$this->port}\": $errorMessage (code $errorCode)" From 67a415fa89472bc768673b85c75d46e822a96aeb Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 29 May 2024 15:15:22 +0200 Subject: [PATCH 4/4] 5.5.3 Signed-off-by: Robin Appelman --- appinfo/info.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index f4093f3..11d635b 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -14,7 +14,7 @@ This application inspects files that are uploaded to Nextcloud for viruses before they are written to the Nextcloud storage. If a file is identified as a virus, it is either logged or not uploaded to the server. The application relies on the underlying ClamAV virus scanning engine, which the admin points Nextcloud to when configuring the application. Alternatively, a Kaspersky Scan Engine can be configured, which has to run on a separate server. For this app to be effective, the ClamAV virus definitions should be kept up to date. Also note that enabling this app will impact system performance as additional processing is required for every upload. More information is available in the Antivirus documentation. ]]> - 5.5.2 + 5.5.3 agpl Manuel Delgado