Completed
Pull Request — master (#133)
by Victor
02:14
created

Scanner::writeRaw()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2.0078

Importance

Changes 0
Metric Value
dl 0
loc 10
ccs 7
cts 8
cp 0.875
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 8
nc 2
nop 1
crap 2.0078
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 2
	public function getStatus(){
66 2
		if ($this->infectedStatus instanceof Status){
67
			return $this->infectedStatus;
68
		}
69 2
		if ($this->status instanceof Status){
70 2
			return $this->status;
71
		}
72
		return new Status();
73
	}
74
75
	/**
76
	 * Synchronous scan
77
	 * @param IScannable $item
78
	 * @return Status
79
	 */
80 2
	public function scan(IScannable $item) {
81 2
		$this->initScanner();
82
83 2
		while (false !== ($chunk = $item->fread())) {
84 2
			$this->writeChunk($chunk);
85
		}
86
		
87 2
		$this->shutdownScanner();
88 2
		return $this->getStatus();
89
	}
90
	
91
	/**
92
	 * Async scan - new portion of data is available
93
	 * @param string $data
94
	 */
95
	public function onAsyncData($data){
96
		$this->writeChunk($data);
97
	}
98
	
99
	/**
100
	 * Async scan - resource is closed
101
	 * @return Status
102
	 */
103
	public function completeAsyncScan(){
104
		$this->shutdownScanner();
105
		return $this->getStatus();
106
	}
107
	
108
	/**
109
	 * Open write handle. etc
110
	 */
111 2
	public function initScanner(){
112 2
		$this->byteCount = 0;
113 2
		if ($this->status instanceof Status && $this->status->getNumericStatus() === Status::SCANRESULT_INFECTED){
114
			$this->infectedStatus = clone $this->status;
115
		}
116 2
		$this->status = new Status();
117 2
	}
118
119
	/**
120
	 * @param string $chunk
121
	 */
122 2
	protected function writeChunk($chunk){
123 2
		$this->fwrite(
124 2
			$this->prepareChunk($chunk)
125
		);
126 2
	}
127
128
	/**
129
	 * @param string $data
130
	 */
131 2
	protected final function fwrite($data){
132 2
		if ($this->isAborted){
133
			return;
134
		}
135
136 2
		$dataLength = strlen($data);
137 2
		$streamSizeLimit = intval($this->appConfig->getAvStreamMaxLength());
138 2
		if ($this->byteCount + $dataLength > $streamSizeLimit){
139 2
			\OC::$server->getLogger()->debug(
140 2
				'reinit scanner',
141 2
				['app' => 'files_antivirus']
142
			);
143 2
			$this->shutdownScanner();
144 2
			$this->initScanner();
145
		}
146
147 2
		if (!$this->writeRaw($data)){
148
			if (!$this->isLogUsed) {
149
				$this->isLogUsed = true;
150
				\OC::$server->getLogger()->warning(
151
					'Failed to write a chunk. Check if Stream Length matches StreamMaxLength in ClamAV daemon settings',
152
					['app' => 'files_antivirus']
153
				);
154
			}
155
			// retry on error
156
			$this->initScanner();
157
			if (!is_null($this->lastChunk)) {
158
				$isRetrySuccessful = $this->writeRaw($this->lastChunk) && $this->writeRaw($data);
159
			} else {
160
				$isRetrySuccessful = $this->writeRaw($data);
161
			}
162
			$this->isAborted = !$isRetrySuccessful;
163
		}
164 2
	}
165
166
	/**
167
	 * @param $data
168
	 * @return bool
169
	 */
170 2
	protected function writeRaw($data){
171 2
		$dataLength = strlen($data);
172 2
		$bytesWritten = @fwrite($this->getWriteHandle(), $data);
173 2
		if ($bytesWritten === $dataLength){
174 2
			$this->byteCount += $bytesWritten;
175 2
			$this->lastChunk = $data;
176 2
			return true;
177
		}
178
		return false;
179
	}
180
181
	/**
182
	 * Get a resource to write data into
183
	 * @return resource
184
	 */
185 2
	protected function getWriteHandle(){
186 2
		return $this->writeHandle;
187
	}
188
189
	/**
190
	 * Prepare chunk (if required)
191
	 * @param string $data
192
	 * @return string
193
	 */
194 2
	protected function prepareChunk($data){
195 2
		return $data;
196
	}
197
}
198