ScannerBase::scanString()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 9
ccs 0
cts 4
cp 0
rs 9.9666
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 2
1
<?php
2
3
/**
4
 * ownCloud - files_antivirus
5
 *
6
 * @author Manuel Deglado
7
 * @copyright 2012 Manuel Deglado [email protected]
8
 *
9
 * This library is free software; you can redistribute it and/or
10
 * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
11
 * License as published by the Free Software Foundation; either
12
 * version 3 of the License, or any later version.
13
 *
14
 * This library is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
18
 *
19
 * You should have received a copy of the GNU Affero General Public
20
 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
21
 *
22
 */
23
24
namespace OCA\Files_Antivirus\Scanner;
25
26
use OCA\Files_Antivirus\AppConfig;
27
use OCA\Files_Antivirus\Item;
28
use OCA\Files_Antivirus\Status;
29
use OCA\Files_Antivirus\StatusFactory;
30
use OCP\ILogger;
31
32
abstract class ScannerBase implements IScanner {
33
34
	/**
35
	 * Scan result
36
	 *
37
	 * @var Status
38
	 */
39
	protected $status;
40
41
	/**
42
	 * If scanning was done part by part
43
	 * the first detected infected part is stored here
44
	 *
45
	 * @var Status
46
	 */
47
	protected $infectedStatus;
48
49
	/** @var  int */
50
	protected $byteCount;
51
52
	/** @var  resource */
53
	protected $writeHandle;
54
55
	/** @var AppConfig */
56
	protected $appConfig;
57
58
	/** @var ILogger */
59
	protected $logger;
60
61
	/** @var StatusFactory */
62
	protected $statusFactory;
63
64
	/** @var string */
65
	protected $lastChunk;
66
67
	/** @var bool */
68
	protected $isLogUsed = false;
69
70
	/** @var bool */
71
	protected $isAborted = false;
72
73
	/**
74
	 * ScannerBase constructor.
75
	 *
76
	 * @param AppConfig $config
77
	 * @param ILogger $logger
78
	 * @param StatusFactory $statusFactory
79
	 */
80 2
	public function __construct(AppConfig $config, ILogger $logger, StatusFactory $statusFactory) {
81 2
		$this->appConfig = $config;
82 2
		$this->logger = $logger;
83 2
		$this->statusFactory = $statusFactory;
84 2
	}
85
86
	/**
87
	 * Close used resources
88
	 */
89
	abstract protected function shutdownScanner();
90
91
92
	public function getStatus() {
93
		if ($this->infectedStatus instanceof Status) {
94
			return $this->infectedStatus;
95
		}
96
		if ($this->status instanceof Status) {
97
			return $this->status;
98
		}
99
		return $this->statusFactory->newStatus();
100
	}
101
102
	/**
103
	 * Synchronous scan
104
	 *
105
	 * @param Item $item
106
	 * @return Status
107
	 */
108
	public function scan(Item $item): Status {
109
		$this->infectedStatus = null;
110
		$this->initScanner();
111
112
		while (false !== ($chunk = $item->fread())) {
113
			$this->writeChunk($chunk);
114
		}
115
116
		$this->shutdownScanner();
117
		return $this->getStatus();
118
	}
119
120
	public function scanString(string $data): Status {
121
		$this->infectedStatus = null;
122
		$this->initScanner();
123
124
		$this->writeChunk($data);
125
126
		$this->shutdownScanner();
127
		return $this->getStatus();
128
	}
129
130
	/**
131
	 * Async scan - new portion of data is available
132
	 *
133
	 * @param string $data
134
	 */
135
	public function onAsyncData($data) {
136
		$this->writeChunk($data);
137
	}
138
139
	/**
140
	 * Async scan - resource is closed
141
	 *
142
	 * @return Status
143
	 */
144
	public function completeAsyncScan(): Status {
145
		$this->shutdownScanner();
146
		return $this->getStatus();
147
	}
148
149
	/**
150
	 * Open write handle. etc
151
	 */
152
	public function initScanner() {
153
		$this->byteCount = 0;
154
		if ($this->status instanceof Status && $this->status->getNumericStatus() === Status::SCANRESULT_INFECTED) {
155
			$this->infectedStatus = clone $this->status;
156
		}
157
		$this->status = $this->statusFactory->newStatus();
158
	}
159
160
	/**
161
	 * @param string $chunk
162
	 */
163
	protected function writeChunk($chunk) {
164
		$this->fwrite(
165
			$this->prepareChunk($chunk)
166
		);
167
	}
168
169
	/**
170
	 * @param string $data
171
	 */
172
	final protected function fwrite($data) {
173
		if ($this->isAborted) {
174
			return;
175
		}
176
177
		$dataLength = strlen($data);
178
		$streamSizeLimit = (int)$this->appConfig->getAvStreamMaxLength();
0 ignored issues
show
Documentation Bug introduced by
The method getAvStreamMaxLength does not exist on object<OCA\Files_Antivirus\AppConfig>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
179
		if ($this->byteCount + $dataLength > $streamSizeLimit) {
180
			\OC::$server->getLogger()->debug(
181
				'reinit scanner',
182
				['app' => 'files_antivirus']
183
			);
184
			$this->shutdownScanner();
185
			$isReopenSuccessful = $this->retry();
186
		} else {
187
			$isReopenSuccessful = true;
188
		}
189
190
		if (!$isReopenSuccessful || !$this->writeRaw($data)) {
191
			if (!$this->isLogUsed) {
192
				$this->isLogUsed = true;
193
				\OC::$server->getLogger()->warning(
194
					'Failed to write a chunk. Check if Stream Length matches StreamMaxLength in anti virus daemon settings',
195
					['app' => 'files_antivirus']
196
				);
197
			}
198
			// retry on error
199
			$isRetrySuccessful = $this->retry() && $this->writeRaw($data);
200
			$this->isAborted = !$isRetrySuccessful;
201
		}
202
	}
203
204
	/**
205
	 * @return bool
206
	 */
207
	protected function retry() {
208
		$this->initScanner();
209
		if (!is_null($this->lastChunk)) {
210
			return $this->writeRaw($this->lastChunk);
211
		}
212
		return true;
213
	}
214
215
	/**
216
	 * @param $data
217
	 * @return bool
218
	 */
219
	protected function writeRaw($data) {
220
		$dataLength = strlen($data);
221
		$bytesWritten = @fwrite($this->getWriteHandle(), $data);
222
		if ($bytesWritten === $dataLength) {
223
			$this->byteCount += $bytesWritten;
224
			$this->lastChunk = $data;
225
			return true;
226
		}
227
		return false;
228
	}
229
230
	/**
231
	 * Get a resource to write data into
232
	 *
233
	 * @return resource
234
	 */
235
	protected function getWriteHandle() {
236
		return $this->writeHandle;
237
	}
238
239
	/**
240
	 * Prepare chunk (if required)
241
	 *
242
	 * @param string $data
243
	 * @return string
244
	 */
245
	protected function prepareChunk($data) {
246
		return $data;
247
	}
248
}
249