Completed
Pull Request — master (#133)
by Victor
03:28
created

Scanner::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
dl 0
loc 31
ccs 13
cts 21
cp 0.619
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 21
nc 11
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;
25
26
abstract class Scanner {
27
	
28
	/**
29
	 * Scan result
30
	 * @var Status
31
	 */
32
	protected $status;
33
34
	/**
35
	 * If scanning was done part by part
36
	 * the first detected infected part is stored here
37
	 * @var Status
38
	 */
39
	protected $infectedStatus;
40
41
	/** @var  int */
42
	protected $byteCount;
43
44
	/** @var  resource */
45
	protected $writeHandle;
46
47
	/** @var \OCA\Files_Antivirus\AppConfig */
48
	protected $appConfig;
49
50
	/** @var string */
51
	protected $lastChunk;
52
53
	/** @var bool */
54
	protected $isLogUsed = false;
55
56
	/** @var bool */
57
	protected $isAborted = false;
58
59
	/**
60
	 * Close used resources
61
	 */
62
	abstract protected function shutdownScanner();
63
64
65 4
	public function getStatus(){
66 4
		if ($this->infectedStatus instanceof Status){
67
			return $this->infectedStatus;
68
		}
69 4
		if ($this->status instanceof Status){
70 4
			return $this->status;
71
		}
72
		return new Status();
73
	}
74
75
	/**
76
	 * Synchronous scan
77
	 * @param IScannable $item
78
	 * @return Status
79
	 */
80 3
	public function scan(IScannable $item) {
81 3
		$this->initScanner();
82
83 3
		while (false !== ($chunk = $item->fread())) {
84 3
			$this->writeChunk($chunk);
85
		}
86
		
87 3
		$this->shutdownScanner();
88 3
		return $this->getStatus();
89
	}
90
	
91
	/**
92
	 * Async scan - new portion of data is available
93
	 * @param string $data
94
	 */
95 1
	public function onAsyncData($data){
96 1
		$this->writeChunk($data);
97 1
	}
98
	
99
	/**
100
	 * Async scan - resource is closed
101
	 * @return Status
102
	 */
103 1
	public function completeAsyncScan(){
104 1
		$this->shutdownScanner();
105 1
		return $this->getStatus();
106
	}
107
	
108
	/**
109
	 * Open write handle. etc
110
	 */
111 5
	public function initScanner(){
112 5
		$this->byteCount = 0;
113 5
		if ($this->status instanceof Status && $this->status->getNumericStatus() === Status::SCANRESULT_INFECTED){
114
			$this->infectedStatus = clone $this->status;
115
		}
116 5
		$this->status = new Status();
117 5
	}
118
119
	/**
120
	 * @param string $chunk
121
	 */
122 4
	protected function writeChunk($chunk){
123 4
		$this->fwrite(
124 4
			$this->prepareChunk($chunk)
125
		);
126 4
	}
127
128
	/**
129
	 * @param string $data
130
	 */
131 4
	protected final function fwrite($data){
132 4
		if ($this->isAborted){
133
			return;
134
		}
135
136 4
		$dataLength = strlen($data);
137 4
		$streamSizeLimit = intval($this->appConfig->getAvStreamMaxLength());
138 4
		if ($this->byteCount + $dataLength > $streamSizeLimit){
139 4
			\OC::$server->getLogger()->debug(
140 4
				'reinit scanner',
141 4
				['app' => 'files_antivirus']
142
			);
143 4
			$this->shutdownScanner();
144 4
			$isReopenSuccessful = $this->retry();
145
		} else {
146 1
			$isReopenSuccessful = true;
147
		}
148
149 4
		if (!$isReopenSuccessful || !$this->writeRaw($data)){
150
			if (!$this->isLogUsed) {
151
				$this->isLogUsed = true;
152
				\OC::$server->getLogger()->warning(
153
					'Failed to write a chunk. Check if Stream Length matches StreamMaxLength in ClamAV daemon settings',
154
					['app' => 'files_antivirus']
155
				);
156
			}
157
			// retry on error
158
			$isRetrySuccessful = $this->retry() && $this->writeRaw($data);
159
			$this->isAborted = !$isRetrySuccessful;
160
		}
161 4
	}
162
163
	/**
164
	 * @return bool
165
	 */
166 4
	protected function retry(){
167 4
		$this->initScanner();
168 4
		if (!is_null($this->lastChunk)) {
169 1
			return $this->writeRaw($this->lastChunk);
170
		}
171 3
		return true;
172
	}
173
174
	/**
175
	 * @param $data
176
	 * @return bool
177
	 */
178 4
	protected function writeRaw($data){
179 4
		$dataLength = strlen($data);
180 4
		$bytesWritten = @fwrite($this->getWriteHandle(), $data);
181 4
		if ($bytesWritten === $dataLength){
182 4
			$this->byteCount += $bytesWritten;
183 4
			$this->lastChunk = $data;
184 4
			return true;
185
		}
186
		return false;
187
	}
188
189
	/**
190
	 * Get a resource to write data into
191
	 * @return resource
192
	 */
193 5
	protected function getWriteHandle(){
194 5
		return $this->writeHandle;
195
	}
196
197
	/**
198
	 * Prepare chunk (if required)
199
	 * @param string $data
200
	 * @return string
201
	 */
202 3
	protected function prepareChunk($data){
203 3
		return $data;
204
	}
205
}
206