Completed
Pull Request — master (#225)
by Victor
05:30 queued 04:01
created

AbstractScanner::fwrite()   C

Complexity

Conditions 7
Paths 11

Size

Total Lines 31
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 9.71

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 31
ccs 13
cts 21
cp 0.619
rs 6.7272
nc 11
cc 7
eloc 21
nop 1
crap 9.71
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\IScannable;
28
use OCA\Files_Antivirus\Status;
29
use OCP\ILogger;
30
31
abstract class AbstractScanner {
32
	
33
	/**
34
	 * Scan result
35
	 *
36
	 * @var Status
37
	 */
38
	protected $status;
39
40
	/**
41
	 * If scanning was done part by part
42
	 * the first detected infected part is stored here
43
	 *
44
	 * @var Status
45
	 */
46
	protected $infectedStatus;
47
48
	/**
49
	 * @var int
50
	 */
51
	protected $byteCount;
52
53
	/**
54
	 * @var resource
55
	 */
56
	protected $writeHandle;
57
58
	/**
59
	 * @var AppConfig
60
	 */
61
	protected $appConfig;
62
63
	/**
64
	 * @var  ILogger
65
	 */
66
	protected $logger;
67
68
	/**
69
	 * @var string
70
	 */
71
	protected $lastChunk;
72
73
	/**
74
	 * @var bool
75
	 */
76
	protected $isLogUsed = false;
77
78
	/**
79
	 * @var bool
80
	 */
81
	protected $isAborted = false;
82
83
	/**
84
	 * Close used resources
85
	 */
86
	abstract protected function shutdownScanner();
87
88
	/**
89
	 * AbstractScanner constructor.
90
	 *
91
	 * @param AppConfig $config
92
	 * @param ILogger $logger
93
	 */
94 12
	public function __construct(AppConfig $config, ILogger $logger) {
95 12
		$this->appConfig = $config;
96 12
		$this->logger = $logger;
97 12
	}
98
99
	/**
100
	 * @return Status
101
	 */
102 7
	public function getStatus() {
103 7
		if ($this->infectedStatus instanceof Status) {
104
			return $this->infectedStatus;
105
		}
106 7
		if ($this->status instanceof Status) {
107 7
			return $this->status;
108
		}
109
		return new Status();
110
	}
111
112
	/**
113
	 * Synchronous scan
114
	 *
115
	 * @param IScannable $item
116
	 *
117
	 * @return Status
118
	 */
119 2
	public function scan(IScannable $item) {
120 2
		$this->initScanner();
121
122 2
		while (false !== ($chunk = $item->fread())) {
123 2
			$this->writeChunk($chunk);
124
		}
125
		
126 2
		$this->shutdownScanner();
127 2
		return $this->getStatus();
128
	}
129
	
130
	/**
131
	 * Async scan - new portion of data is available
132
	 *
133
	 * @param string $data
134
	 */
135 5
	public function onAsyncData($data) {
136 5
		$this->writeChunk($data);
137 5
	}
138
	
139
	/**
140
	 * Async scan - resource is closed
141
	 *
142
	 * @return Status
143
	 */
144 5
	public function completeAsyncScan() {
145 5
		$this->shutdownScanner();
146 5
		return $this->getStatus();
147
	}
148
	
149
	/**
150
	 * Get write handle here.
151
	 * Do NOT open connection in constructor because this method
152
	 * is used for reconnection
153
	 */
154 9
	public function initScanner() {
155 9
		$this->byteCount = 0;
156 9
		if ($this->status instanceof Status
157 9
			&& $this->status->getNumericStatus() === Status::SCANRESULT_INFECTED
158
		) {
159
			$this->infectedStatus = clone $this->status;
160
		}
161 9
		$this->status = new Status();
162 9
	}
163
164
	/**
165
	 * @param string $chunk
166
	 */
167 7
	protected function writeChunk($chunk) {
168 7
		$this->fwrite(
169 7
			$this->prepareChunk($chunk)
170
		);
171 7
	}
172
173
	/**
174
	 * @param string $data
175
	 */
176 7
	protected final function fwrite($data) {
177 7
		if ($this->isAborted) {
178
			return;
179
		}
180
181 7
		$dataLength = strlen($data);
182 7
		$streamSizeLimit = intval($this->appConfig->getAvStreamMaxLength());
183 7
		if ($this->byteCount + $dataLength > $streamSizeLimit) {
184 3
			$this->logger->debug(
185 3
				'reinit scanner',
186 3
				['app' => 'files_antivirus']
187
			);
188 3
			$this->shutdownScanner();
189 3
			$isReopenSuccessful = $this->retry();
190
		} else {
191 5
			$isReopenSuccessful = true;
192
		}
193
194 7
		if (!$isReopenSuccessful || !$this->writeRaw($data)) {
195
			if (!$this->isLogUsed) {
196
				$this->isLogUsed = true;
197
				$this->logger->warning(
198
					'Failed to write a chunk. Check if Stream Length matches StreamMaxLength in ClamAV daemon settings',
199
					['app' => 'files_antivirus']
200
				);
201
			}
202
			// retry on error
203
			$isRetrySuccessful = $this->retry() && $this->writeRaw($data);
204
			$this->isAborted = !$isRetrySuccessful;
205
		}
206 7
	}
207
208
	/**
209
	 * @return bool
210
	 */
211 3
	protected function retry() {
212 3
		$this->initScanner();
213 3
		if (!is_null($this->lastChunk)) {
214 1
			return $this->writeRaw($this->lastChunk);
215
		}
216 2
		return true;
217
	}
218
219
	/**
220
	 * @param string $data
221
	 *
222
	 * @return bool
223
	 */
224 7
	protected function writeRaw($data) {
225 7
		$dataLength = strlen($data);
226 7
		$bytesWritten = @fwrite($this->getWriteHandle(), $data);
227 7
		if ($bytesWritten === $dataLength) {
228 7
			$this->byteCount += $bytesWritten;
229 7
			$this->lastChunk = $data;
230 7
			return true;
231
		}
232
		return false;
233
	}
234
235
	/**
236
	 * Get a resource to write data into
237
	 *
238
	 * @return resource
239
	 */
240 9
	protected function getWriteHandle() {
241 9
		return $this->writeHandle;
242
	}
243
244
	/**
245
	 * Prepare chunk (if needed)
246
	 *
247
	 * @param string $data
248
	 *
249
	 * @return string
250
	 */
251 3
	protected function prepareChunk($data) {
252 3
		return $data;
253
	}
254
}
255