Completed
Push — master ( ed1dd0...0d6f9c )
by Roeland
9s
created

Item::processInfected()   B

Complexity

Conditions 6
Paths 18

Size

Total Lines 39
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 2
Bugs 0 Features 0
Metric Value
dl 0
loc 39
ccs 0
cts 29
cp 0
rs 8.439
c 2
b 0
f 0
cc 6
eloc 30
nc 18
nop 2
crap 42
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 OCA\Files_Antivirus\Activity\Provider;
13
use OCA\Files_Antivirus\AppInfo\Application;
14
use OCP\App;
15
use OCP\IL10N;
16
17
class Item implements IScannable{
18
	/**
19
	 * Scanned fileid (optional)
20
	 * @var int
21
	 */
22
	protected $id;
23
	
24
	/**
25
	 * File view
26
	 * @var \OC\Files\View
27
	 */
28
	protected $view;
29
	
30
	/**
31
	 * Path relative to the view
32
	 * @var string
33
	 */
34
	protected $path;
35
	
36
	/**
37
	 * file handle, user to read from the file
38
	 * @var resource
39
	 */
40
	protected $fileHandle;
41
	
42
	/**
43
	 * Portion size
44
	 * @var int
45
	 */
46
	protected $chunkSize;
47
	
48
	/**
49
	 * Is filesize match the size conditions
50
	 * @var bool
51
	 */
52
	protected $isValidSize;
53
	
54
	/**
55
	 * @var IL10N
56
	 */
57
	private $l10n;
58
	
59 4
	public function __construct(IL10N $l10n, View $view, $path, $id = null) {
60 4
		$this->l10n = $l10n;
61
		
62 4
		if (!is_object($view)){
63
			$this->logError('Can\'t init filesystem view.', $id, $path);
64
			throw new \RuntimeException();
65
		}
66
		
67 4
		if(!$view->file_exists($path)) {
68 1
			$this->logError('File does not exist.', $id, $path);
69 1
			throw new \RuntimeException();
70
		}
71
72 4
		$this->id = $id;
73 4
		if (is_null($id)){
74 1
			$this->id = $view->getFileInfo($path)->getId();
75
		}
76
77 4
		$this->view = $view;
78 4
		$this->path = $path;
79
		
80 4
		$this->isValidSize = $view->filesize($path) > 0;
81
		
82 4
		$application = new AppInfo\Application();
83 4
		$config = $application->getContainer()->query(AppConfig::class);
84 4
		$this->chunkSize = $config->getAvChunkSize();
85 4
	}
86
	
87
	/**
88
	 * Is this file good for scanning? 
89
	 * @return boolean
90
	 */
91 3
	public function isValid() {
92 3
		return !$this->view->is_dir($this->path) && $this->isValidSize;
93
	}
94
	
95
	/**
96
	 * Reads a file portion by portion until the very end
97
	 * @return string|boolean
98
	 */
99 3
	public function fread() {
100 3
		if (!$this->isValid()) {
101
			return false;
102
		}
103 3
		if (is_null($this->fileHandle)) {
104 3
			$this->getFileHandle();
105
		}
106
		
107 3
		if (!is_null($this->fileHandle) && !$this->feof()) {
108 3
			return fread($this->fileHandle, $this->chunkSize);
109
		}
110 2
		return false;
111
	}
112
	
113
	/**
114
	 * Action to take if this item is infected
115
	 * @param Status $status
116
	 * @param boolean $isBackground
117
	 */
118
	public function processInfected(Status $status, $isBackground) {
119
		$application = new AppInfo\Application();
120
		$appConfig = $application->getContainer()->query('AppConfig');
121
		$infectedAction = $appConfig->getAvInfectedAction();
122
		
123
		$shouldDelete = !$isBackground || ($isBackground && $infectedAction === 'delete');
124
		
125
		$message = $shouldDelete ? Provider::MESSAGE_FILE_DELETED : '';
126
127
		$activityManager = \OC::$server->getActivityManager();
128
		$activity = $activityManager->generateEvent();
129
		$activity->setApp(Application::APP_NAME)
130
			->setSubject(Provider::SUBJECT_VIRUS_DETECTED, [$this->path, $status->getDetails()])
131
			->setMessage($message)
132
			->setObject('', 0, $this->path)
133
			->setAffectedUser($this->view->getOwner($this->path))
134
			->setType(Provider::TYPE_VIRUS_DETECTED);
135
		$activityManager->publish($activity);
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