Completed
Push — master ( aa875b...df65e3 )
by Roeland
02:29
created

Item::getFileHandle()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2.0625

Importance

Changes 0
Metric Value
dl 0
loc 10
ccs 6
cts 8
cp 0.75
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 7
nc 2
nop 0
crap 2.0625
1
<?php
2
/**
3
 * Copyright (c) 2015 Victor Dubiniuk <[email protected]>
4
 * This file is licensed under the Affero General Public License version 3 or
5
 * later.
6
 * See the COPYING-README file.
7
 */
8
9
namespace OCA\Files_Antivirus;
10
11
use OC\Files\View;
12
use OCP\App;
13
use OCP\IL10N;
14
15
class Item implements IScannable{
16
	/**
17
	 * Scanned fileid (optional)
18
	 * @var int
19
	 */
20
	protected $id;
21
	
22
	/**
23
	 * File view
24
	 * @var \OC\Files\View
25
	 */
26
	protected $view;
27
	
28
	/**
29
	 * Path relative to the view
30
	 * @var string
31
	 */
32
	protected $path;
33
	
34
	/**
35
	 * file handle, user to read from the file
36
	 * @var resource
37
	 */
38
	protected $fileHandle;
39
	
40
	/**
41
	 * Portion size
42
	 * @var int
43
	 */
44
	protected $chunkSize;
45
	
46
	/**
47
	 * Is filesize match the size conditions
48
	 * @var bool
49
	 */
50
	protected $isValidSize;
51
	
52
	/**
53
	 * @var IL10N
54
	 */
55
	private $l10n;
56
	
57 4
	public function __construct(IL10N $l10n, View $view, $path, $id = null) {
58 4
		$this->l10n = $l10n;
59
		
60 4
		if (!is_object($view)){
61
			$this->logError('Can\'t init filesystem view.', $id, $path);
62
			throw new \RuntimeException();
63
		}
64
		
65 4
		if(!$view->file_exists($path)) {
66 1
			$this->logError('File does not exist.', $id, $path);
67 1
			throw new \RuntimeException();
68
		}
69
70 4
		$this->id = $id;
71 4
		if (is_null($id)){
72 1
			$this->id = $view->getFileInfo($path)->getId();
73
		}
74
75 4
		$this->view = $view;
76 4
		$this->path = $path;
77
		
78 4
		$this->isValidSize = $view->filesize($path) > 0;
79
		
80 4
		$application = new AppInfo\Application();
81 4
		$config = $application->getContainer()->query(AppConfig::class);
82 4
		$this->chunkSize = $config->getAvChunkSize();
83 4
	}
84
	
85
	/**
86
	 * Is this file good for scanning? 
87
	 * @return boolean
88
	 */
89 3
	public function isValid() {
90 3
		return !$this->view->is_dir($this->path) && $this->isValidSize;
91
	}
92
	
93
	/**
94
	 * Reads a file portion by portion until the very end
95
	 * @return string|boolean
96
	 */
97 3
	public function fread() {
98 3
		if (!$this->isValid()) {
99
			return false;
100
		}
101 3
		if (is_null($this->fileHandle)) {
102 3
			$this->getFileHandle();
103
		}
104
		
105 3
		if (!is_null($this->fileHandle) && !$this->feof()) {
106 3
			return fread($this->fileHandle, $this->chunkSize);
107
		}
108 2
		return false;
109
	}
110
	
111
	/**
112
	 * Action to take if this item is infected
113
	 * @param Status $status
114
	 * @param boolean $isBackground
115
	 */
116
	public function processInfected(Status $status, $isBackground) {
117
		$application = new AppInfo\Application();
118
		$appConfig = $application->getContainer()->query('AppConfig');
119
		$infectedAction = $appConfig->getAvInfectedAction();
120
		
121
		$shouldDelete = !$isBackground || ($isBackground && $infectedAction === 'delete');
122
		
123
		$message = $shouldDelete ? Activity::MESSAGE_FILE_DELETED : '';
124
		
125
		\OC::$server->getActivityManager()->publishActivity(
126
					'files_antivirus',
127
					Activity::SUBJECT_VIRUS_DETECTED,
128
					[$this->path, $status->getDetails()],
129
					$message,
130
					[],
131
					$this->path, 
132
					'', 
133
					$this->view->getOwner($this->path),
134
					Activity::TYPE_VIRUS_DETECTED, 
135
					Activity::PRIORITY_HIGH
136
				);
137
		if ($isBackground) {
138
			if ($shouldDelete) {
139
				$this->logError('Infected file deleted. ' . $status->getDetails());
140
				$this->deleteFile();
141
			} else {
142
				$this->logError('File is infected. '  . $status->getDetails());
143
			}
144
		} else {
145
			$this->logError('Virus(es) found: ' . $status->getDetails());
146
			//remove file
147
			$this->deleteFile();
148
			Notification::sendMail($this->path);
149
			$message = $this->l10n->t(
150
						"Virus detected! Can't upload the file %s", 
151
						[basename($this->path)]
152
			);
153
			\OCP\JSON::error(['data' => ['message' => $message]]);
154
			exit();
155
		}
156
	}
157
158
	/**
159
	 * Action to take if this item status is unclear
160
	 * @param Status $status
161
	 * @param boolean $isBackground
162
	 */
163
	public function processUnchecked(Status $status, $isBackground) {
0 ignored issues
show
Unused Code introduced by
The parameter $isBackground is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
164
		//TODO: Show warning to the user: The file can not be checked
165
		$this->logError('Not Checked. ' . $status->getDetails());
166
	}
167
	
168
	/**
169
	 * Action to take if this item status is not infected
170
	 * @param Status $status
171
	 * @param boolean $isBackground
172
	 */
173
	public function processClean(Status $status, $isBackground) {
0 ignored issues
show
Unused Code introduced by
The parameter $status is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
174
		if (!$isBackground) {
175
			return;
176
		}
177
		try {
178
			$stmt = \OCP\DB::prepare('DELETE FROM `*PREFIX*files_antivirus` WHERE `fileid` = ?');
179
			$result = $stmt->execute([$this->id]);
180
			if (\OCP\DB::isError($result)) {
181
				//TODO: Use logger
182
				$this->logError(__METHOD__. ', DB error: ' . \OCP\DB::getErrorMessage());
183
			}
184
			$stmt = \OCP\DB::prepare('INSERT INTO `*PREFIX*files_antivirus` (`fileid`, `check_time`) VALUES (?, ?)');
185
			$result = $stmt->execute([$this->id, time()]);
186
			if (\OCP\DB::isError($result)) {
187
				$this->logError(__METHOD__. ', DB error: ' . \OCP\DB::getErrorMessage());
188
			}
189
		} catch(\Exception $e) {
190
			\OCP\Util::writeLog('files_antivirus', __METHOD__.', exception: '.$e->getMessage(), \OCP\Util::ERROR);
191
		}
192
	}
193
194
	/**
195
	 * Check if the end of file is reached
196
	 * @return boolean
197
	 */
198 3
	private function feof() {
199 3
		$isDone = feof($this->fileHandle);
200 3
		if ($isDone) {
201 2
			$this->logDebug('Scan is done');
202 2
			fclose($this->fileHandle);
203 2
			$this->fileHandle = null;
204
		}
205 3
		return $isDone;
206
	}
207
	
208
	/**
209
	 * Opens a file for reading
210
	 * @throws \RuntimeException
211
	 */
212 3
	private function getFileHandle() {
213 3
		$fileHandle = $this->view->fopen($this->path, 'r');
214 3
		if ($fileHandle === false) {
215
			$this->logError('Can not open for reading.', $this->id, $this->path);
216
			throw new \RuntimeException();
217
		}
218
219 3
		$this->logDebug('Scan started');
220 3
		$this->fileHandle = $fileHandle;
221 3
	}
222
223
	/**
224
	 * Delete infected file
225
	 */
226
	private function deleteFile() {
227
		//prevent from going to trashbin
228
		if (App::isEnabled('files_trashbin')) {
229
			\OCA\Files_Trashbin\Storage::preRenameHook([]);
230
		}
231
		$this->view->unlink($this->path);
232
		if (App::isEnabled('files_trashbin')) {
233
			\OCA\Files_Trashbin\Storage::postRenameHook([]);
234
		}
235
	}
236
	
237
	/**
238
	 * @param string $message
239
	 */
240 3
	public function logDebug($message) {
241 3
		$extra = ' File: ' . $this->id 
242 3
				. 'Account: ' . $this->view->getOwner($this->path) 
243 3
				. ' Path: ' . $this->path;
244 3
		\OCP\Util::writeLog('files_antivirus', $message . $extra, \OCP\Util::DEBUG);
245 3
	}
246
	
247
	/**
248
	 * @param string $message
249
	 * @param int $id optional
250
	 * @param string $path optional
251
	 */
252 1
	public function logError($message, $id=null, $path=null) {
253 1
		$ownerInfo = is_null($this->view) ? '' : 'Account: ' . $this->view->getOwner($path);
254 1
		$extra = ' File: ' . (is_null($id) ? $this->id : $id)
255 1
				. $ownerInfo 
256 1
				. ' Path: ' . (is_null($path) ? $this->path : $path);
257 1
		\OCP\Util::writeLog(
258 1
				'files_antivirus',
259 1
				$message . $extra,
260 1
				\OCP\Util::ERROR
261
		);
262 1
	}
263
}
264