Completed
Push — master ( b32497...b91e8b )
by Roeland
16s queued 12s
created

ScannerBase   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 215
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 6.58%

Importance

Changes 0
Metric Value
wmc 26
lcom 1
cbo 3
dl 0
loc 215
ccs 5
cts 76
cp 0.0658
rs 10
c 0
b 0
f 0

14 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
shutdownScanner() 0 1 ?
A getStatus() 0 9 3
A scan() 0 10 2
A scanString() 0 8 1
A onAsyncData() 0 3 1
A completeAsyncScan() 0 4 1
A initScanner() 0 7 3
A writeChunk() 0 5 1
B fwrite() 0 31 7
A retry() 0 7 2
A writeRaw() 0 10 2
A getWriteHandle() 0 3 1
A prepareChunk() 0 3 1
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->initScanner();
110
111
		while (false !== ($chunk = $item->fread())) {
112
			$this->writeChunk($chunk);
113
		}
114
115
		$this->shutdownScanner();
116
		return $this->getStatus();
117
	}
118
119
	public function scanString(string $data): Status {
120
		$this->initScanner();
121
122
		$this->writeChunk($data);
123
124
		$this->shutdownScanner();
125
		return $this->getStatus();
126
	}
127
128
	/**
129
	 * Async scan - new portion of data is available
130
	 *
131
	 * @param string $data
132
	 */
133
	public function onAsyncData($data) {
134
		$this->writeChunk($data);
135
	}
136
137
	/**
138
	 * Async scan - resource is closed
139
	 *
140
	 * @return Status
141
	 */
142
	public function completeAsyncScan(): Status {
143
		$this->shutdownScanner();
144
		return $this->getStatus();
145
	}
146
147
	/**
148
	 * Open write handle. etc
149
	 */
150
	public function initScanner() {
151
		$this->byteCount = 0;
152
		if ($this->status instanceof Status && $this->status->getNumericStatus() === Status::SCANRESULT_INFECTED) {
153
			$this->infectedStatus = clone $this->status;
154
		}
155
		$this->status = $this->statusFactory->newStatus();
156
	}
157
158
	/**
159
	 * @param string $chunk
160
	 */
161
	protected function writeChunk($chunk) {
162
		$this->fwrite(
163
			$this->prepareChunk($chunk)
164
		);
165
	}
166
167
	/**
168
	 * @param string $data
169
	 */
170
	final protected function fwrite($data) {
171
		if ($this->isAborted) {
172
			return;
173
		}
174
175
		$dataLength = strlen($data);
176
		$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...
177
		if ($this->byteCount + $dataLength > $streamSizeLimit) {
178
			\OC::$server->getLogger()->debug(
179
				'reinit scanner',
180
				['app' => 'files_antivirus']
181
			);
182
			$this->shutdownScanner();
183
			$isReopenSuccessful = $this->retry();
184
		} else {
185
			$isReopenSuccessful = true;
186
		}
187
188
		if (!$isReopenSuccessful || !$this->writeRaw($data)) {
189
			if (!$this->isLogUsed) {
190
				$this->isLogUsed = true;
191
				\OC::$server->getLogger()->warning(
192
					'Failed to write a chunk. Check if Stream Length matches StreamMaxLength in anti virus daemon settings',
193
					['app' => 'files_antivirus']
194
				);
195
			}
196
			// retry on error
197
			$isRetrySuccessful = $this->retry() && $this->writeRaw($data);
198
			$this->isAborted = !$isRetrySuccessful;
199
		}
200
	}
201
202
	/**
203
	 * @return bool
204
	 */
205
	protected function retry() {
206
		$this->initScanner();
207
		if (!is_null($this->lastChunk)) {
208
			return $this->writeRaw($this->lastChunk);
209
		}
210
		return true;
211
	}
212
213
	/**
214
	 * @param $data
215
	 * @return bool
216
	 */
217
	protected function writeRaw($data) {
218
		$dataLength = strlen($data);
219
		$bytesWritten = @fwrite($this->getWriteHandle(), $data);
220
		if ($bytesWritten === $dataLength) {
221
			$this->byteCount += $bytesWritten;
222
			$this->lastChunk = $data;
223
			return true;
224
		}
225
		return false;
226
	}
227
228
	/**
229
	 * Get a resource to write data into
230
	 *
231
	 * @return resource
232
	 */
233
	protected function getWriteHandle() {
234
		return $this->writeHandle;
235
	}
236
237
	/**
238
	 * Prepare chunk (if required)
239
	 *
240
	 * @param string $data
241
	 * @return string
242
	 */
243
	protected function prepareChunk($data) {
244
		return $data;
245
	}
246
}
247