Completed
Pull Request — master (#195)
by Victor
03:10
created

AvirWrapper   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 174
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 88.54%

Importance

Changes 6
Bugs 0 Features 0
Metric Value
wmc 15
lcom 1
cbo 5
dl 0
loc 174
ccs 85
cts 96
cp 0.8854
rs 10
c 6
b 0
f 0

4 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 1
C fopen() 0 76 9
A isWritingMode() 0 9 1
B isScannableSize() 0 26 4
1
<?php
2
/**
3
 * Copyright (c) 2014 Viktar 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\Filesystem;
12
use OC\Files\Storage\Wrapper\Wrapper;
13
use OCA\Files_Antivirus\Scanner\InitException;
14
use \OCP\App;
15
use \OCP\IL10N;
16
use \OCP\ILogger;
17
use \OCP\Files\InvalidContentException;
18
use \OCP\Files\ForbiddenException;
19
use Icewind\Streams\CallbackWrapper;
20
21
22
class AvirWrapper extends Wrapper{
23
	
24
	/**
25
	 * Modes that are used for writing 
26
	 * @var array 
27
	 */
28
	private $writingModes = array('r+', 'w', 'w+', 'a', 'a+', 'x', 'x+', 'c', 'c+');
29
30
	/**
31
	 * @var AppConfig
32
	 */
33
	protected $appConfig;
34
35
	/**
36
	 * @var \OCA\Files_Antivirus\ScannerFactory
37
	 */
38
	protected $scannerFactory;
39
	
40
	/**
41
	 * @var IL10N 
42
	 */
43
	protected $l10n;
44
	
45
	/**
46
	 * @var ILogger;
47
	 */
48
	protected $logger;
49
50
	/** @var  RequestHelper */
51
	protected $requestHelper;
52
53
	/**
54
	 * @param array $parameters
55
	 */
56 5
	public function __construct($parameters) {
57 5
		parent::__construct($parameters);
58 5
		$this->appConfig = $parameters['appConfig'];
59 5
		$this->scannerFactory = $parameters['scannerFactory'];
60 5
		$this->l10n = $parameters['l10n'];
61 5
		$this->logger = $parameters['logger'];
62 5
		$this->requestHelper = $parameters['requestHelper'];
63 5
	}
64
	
65
	/**
66
	 * Asynchronously scan data that are written to the file
67
	 * @param string $path
68
	 * @param string $mode
69
	 * @return resource | bool
70
	 */
71 4
	public function fopen($path, $mode){
72 4
		$stream = $this->storage->fopen($path, $mode);
73
74
		if (
75 4
			is_resource($stream)
76 4
			&& $this->isWritingMode($mode)
77 4
			&& $this->isScannableSize($path)
78 4
		) {
79
			try {
80 2
				$scanner = $this->scannerFactory->getScanner();
81 2
				$scanner->initScanner();
82 2
				return CallBackWrapper::wrap(
83 2
					$stream,
84 2
					null,
85
					function ($data) use ($scanner) {
86 2
						$scanner->onAsyncData($data);
87 2
					},
88 2
					function () use ($scanner, $path) {
89 2
						$status = $scanner->completeAsyncScan();
90 2
						if (intval($status->getNumericStatus()) === \OCA\Files_Antivirus\Status::SCANRESULT_INFECTED) {
91
							//prevent from going to trashbin
92 2
							if (App::isEnabled('files_trashbin')) {
93 2
								\OCA\Files_Trashbin\Storage::preRenameHook([
94 2
									Filesystem::signal_param_oldpath => '',
95 2
									Filesystem::signal_param_newpath => ''
96 2
								]);
97 2
							}
98
99 2
							$owner = $this->getOwner($path);
100 2
							$this->unlink($path);
101
102 2
							if (App::isEnabled('files_trashbin')) {
103 2
								\OCA\Files_Trashbin\Storage::postRenameHook([]);
104 2
							}
105 2
							$this->logger->warning(
106 2
								'Infected file deleted. ' . $status->getDetails()
107 2
								. ' Account: ' . $owner . ' Path: ' . $path,
108 2
								['app' => 'files_antivirus']
109 2
							);
110
111 2
							\OC::$server->getActivityManager()->publishActivity(
112 2
								'files_antivirus',
113 2
								Activity::SUBJECT_VIRUS_DETECTED,
114 2
								[$path, $status->getDetails()],
115 2
								Activity::MESSAGE_FILE_DELETED,
116 2
								[],
117 2
								$path,
118 2
								'',
119 2
								$owner,
120 2
								Activity::TYPE_VIRUS_DETECTED,
121
								Activity::PRIORITY_HIGH
122 2
							);
123
124 2
							throw new InvalidContentException(
125 2
								$this->l10n->t(
126 2
									'Virus %s is detected in the file. Upload cannot be completed.',
127 2
									$status->getDetails()
128 2
								)
129 2
							);
130
						}
131
					}
132 2
				);
133
			} catch (InitException $e) {
134
				$message = sprintf(
135
					'Antivirus app is misconfigured or antivirus inaccessible. %s',
136 1
					$e->getMessage()
137
				);
138
				$this->logger->warning($message, ['app' => 'files_antivirus']);
139
				throw new ForbiddenException($message, false, $e);
140
			} catch (\Exception $e){
141
				$message = 	implode(' ', [ __CLASS__, __METHOD__, $e->getMessage()]);
142
				$this->logger->warning($message, ['app' => 'files_antivirus']);
143
			}
144
		}
145 3
		return $stream;
146
	}
147
	
148
	/**
149
	 * Checks whether passed mode is suitable for writing 
150
	 * @param string $mode
151
	 * @return bool
152
	 */
153 4
	private function isWritingMode($mode){
154
		// Strip unessential binary/text flags
155 4
		$cleanMode = str_replace(
156 4
			['t', 'b'],
157 4
			['', ''],
158
			$mode
159 4
		);
160 4
		return in_array($cleanMode, $this->writingModes);
161
	}
162
163
	/**
164
	 * Checks upload size against the av_max_file_size config option
165
	 *
166
	 * @param string $path
167
	 * @return bool
168
	 */
169 3
	private function isScannableSize($path) {
170 3
		$scanSizeLimit = intval($this->appConfig->getAvMaxFileSize());
171 3
		$size = $this->requestHelper->getUploadSize($path);
172
173
		// No upload in progress. Skip this file.
174 3
		if (is_null($size)){
175 2
			$this->logger->debug(
176 2
				'No upload in progress or chunk is being uploaded. Scanning is skipped.',
177 2
				['app' => 'files_antivirus']
178 2
			);
179 2
			return false;
180
		}
181
182 2
		$matchesLimit = $scanSizeLimit === -1 || $scanSizeLimit >= $size;
183 2
		$action = $matchesLimit ? 'Scanning is scheduled.' : 'Scanning is skipped.';
184 2
		$this->logger->debug(
185 2
			'File size is {filesize}. av_max_file_size is {av_max_file_size}. {action}',
186
			[
187 2
				'app' => 'files_antivirus',
188 2
				'av_max_file_size' => $scanSizeLimit,
189 2
				'filesize' => $size,
190
				'action' => $action
191 2
			]
192 2
		);
193 2
		return $matchesLimit;
194
	}
195
}
196