Completed
Push — master ( 85c4a2...c508f5 )
by Roeland
16s queued 12s
created

AvirWrapper::writeStream()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 0
cts 4
cp 0
rs 10
c 0
b 0
f 0
cc 4
nc 2
nop 3
crap 20
1
<?php
2
/**
3
 * Copyright (c) 2014 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\Storage\Wrapper\Wrapper;
12
use OCA\Files_Antivirus\Activity\Provider;
13
use OCA\Files_Antivirus\AppInfo\Application;
14
use OCA\Files_Antivirus\Event\ScanStateEvent;
15
use OCA\Files_Antivirus\Scanner\ScannerFactory;
16
use OCP\Activity\IManager as ActivityManager;
17
use OCP\App;
18
use OCP\Files\InvalidContentException;
19
use OCP\IL10N;
20
use OCP\ILogger;
21
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
22
use OCA\Files_Trashbin\Trash\ITrashManager;
23
24
class AvirWrapper extends Wrapper {
25
	
26
	/**
27
	 * Modes that are used for writing 
28
	 * @var array 
29
	 */
30
	private $writingModes = ['r+', 'w', 'w+', 'a', 'a+', 'x', 'x+', 'c', 'c+'];
31
	
32
	/** @var ScannerFactory */
33
	protected $scannerFactory;
34
	
35
	/** @var IL10N */
36
	protected $l10n;
37
	
38
	/** @var ILogger */
39
	protected $logger;
40
41
	/** @var ActivityManager */
42
	protected $activityManager;
43
44
	/** @var bool */
45
	protected $isHomeStorage;
46
47
	/** @var bool */
48
	private $shouldScan = true;
49
50
	/**
51
	 * @param array $parameters
52
	 */
53 2
	public function __construct($parameters) {
54 2
		parent::__construct($parameters);
55 2
		$this->scannerFactory = $parameters['scannerFactory'];
56 2
		$this->l10n = $parameters['l10n'];
57 2
		$this->logger = $parameters['logger'];
58 2
		$this->activityManager = $parameters['activityManager'];
59 2
		$this->isHomeStorage = $parameters['isHomeStorage'];
60
61
		/** @var EventDispatcherInterface $eventDispatcher */
62 2
		$eventDispatcher = $parameters['eventDispatcher'];
63 2
		$eventDispatcher->addListener(ScanStateEvent::class, function(ScanStateEvent $event) {
64
			$this->shouldScan = $event->getState();
65 2
		});
66 2
	}
67
	
68
	/**
69
	 * Asynchronously scan data that are written to the file
70
	 * @param string $path
71
	 * @param string $mode
72
	 * @return resource | bool
73
	 */
74 1
	public function fopen($path, $mode) {
75 1
		$stream = $this->storage->fopen($path, $mode);
76
77
		/*
78
		 * Only check when
79
		 *  - it is a resource
80
		 *  - it is a writing mode
81
		 *  - if it is a homestorage it starts with files/
82
		 *  - if it is not a homestorage we always wrap (external storages)
83
		 */
84 1
		if ($this->shouldScan && is_resource($stream) && $this->isWritingMode($mode) && (!$this->isHomeStorage || strpos($path, 'files/') === 0)) {
85 1
			$stream = $this->wrapSteam($path, $stream);
86
		}
87 1
		return $stream;
88
	}
89
90
	public function writeStream(string $path, $stream, int $size = null): int {
91
		if ($this->shouldScan && (!$this->isHomeStorage || strpos($path, 'files/') === 0)) {
92
			$stream = $this->wrapSteam($path, $stream);
93
		}
94
		return parent::writeStream($path, $stream, $size);
95
	}
96
97 1
	private function wrapSteam(string $path, $stream) {
98
		try {
99 1
			$scanner = $this->scannerFactory->getScanner();
100
			$scanner->initScanner();
101
			return CallbackReadDataWrapper::wrap(
102
				$stream,
103
				function ($count, $data) use ($scanner) {
104
					$scanner->onAsyncData($data);
105
				},
106
				function ($data) use ($scanner) {
107
					$scanner->onAsyncData($data);
108
				},
109
				function () use ($scanner, $path) {
110
					$status = $scanner->completeAsyncScan();
111
					if ((int)$status->getNumericStatus() === Status::SCANRESULT_INFECTED){
112
						//prevent from going to trashbin
113
						if (App::isEnabled('files_trashbin')) {
114
							/** @var ITrashManager $trashManager */
115
							$trashManager = \OC::$server->query(ITrashManager::class);
116
							$trashManager->pauseTrash();
117
						}
118
119
						$owner = $this->getOwner($path);
120
						$this->unlink($path);
121
122
						if (App::isEnabled('files_trashbin')) {
123
							/** @var ITrashManager $trashManager */
124
							$trashManager = \OC::$server->query(ITrashManager::class);
125
							$trashManager->resumeTrash();
126
						}
127
							
128
						$this->logger->warning(
129
							'Infected file deleted. ' . $status->getDetails()
130
							. ' Account: ' . $owner . ' Path: ' . $path,
131
							['app' => 'files_antivirus']
132
						);
133
134
						$activity = $this->activityManager->generateEvent();
135
						$activity->setApp(Application::APP_NAME)
136
							->setSubject(Provider::SUBJECT_VIRUS_DETECTED_UPLOAD, [$status->getDetails()])
137
							->setMessage(Provider::MESSAGE_FILE_DELETED)
138
							->setObject('', 0, $path)
139
							->setAffectedUser($owner)
140
							->setType(Provider::TYPE_VIRUS_DETECTED);
141
						$this->activityManager->publish($activity);
142
143
						$this->logger->error('Infected file deleted. ' . $status->getDetails() .
144
							' File: ' . $path . ' Account: ' . $owner, ['app' => 'files_antivirus']);
145
146
						throw new InvalidContentException(
147
							$this->l10n->t(
148
								'Virus %s is detected in the file. Upload cannot be completed.',
149
								$status->getDetails()
150
							)
151
						);
152
					}
153
				}
154
			);
155 1
		} catch (\Exception $e){
156 1
			$this->logger->logException($e);
157
		}
158 1
		return $stream;
159
	}
160
	
161
	/**
162
	 * Checks whether passed mode is suitable for writing 
163
	 * @param string $mode
164
	 * @return bool
165
	 */
166 1
	private function isWritingMode($mode){
167
		// Strip unessential binary/text flags
168 1
		$cleanMode = str_replace(
169 1
			['t', 'b'],
170 1
			['', ''],
171 1
			$mode
172
		);
173 1
		return in_array($cleanMode, $this->writingModes);
174
	}
175
}
176