Completed
Push — skip-by-size ( 8783f0...918c28 )
by Victor
05:40
created

AvirWrapper::isScannableSize()   B

Complexity

Conditions 4
Paths 5

Size

Total Lines 26
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

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