1
|
|
|
<?php |
2
|
|
|
declare(strict_types=1); |
3
|
|
|
/** |
4
|
|
|
* Copyright (c) 2012 Bart Visscher <[email protected]> |
5
|
|
|
* This file is licensed under the Affero General Public License version 3 or |
6
|
|
|
* later. |
7
|
|
|
* See the COPYING-README file. |
8
|
|
|
*/ |
9
|
|
|
|
10
|
|
|
namespace OCA\Files_Antivirus\BackgroundJob; |
11
|
|
|
|
12
|
|
|
use OC\BackgroundJob\TimedJob; |
13
|
|
|
use OCA\Files_Antivirus\AppConfig; |
14
|
|
|
use OCA\Files_Antivirus\ItemFactory; |
15
|
|
|
use OCA\Files_Antivirus\Scanner\ScannerFactory; |
16
|
|
|
use OCP\DB\QueryBuilder\IQueryBuilder; |
17
|
|
|
use OCP\Files\File; |
18
|
|
|
use OCP\Files\IMimeTypeLoader; |
19
|
|
|
use OCP\IDBConnection; |
20
|
|
|
use OCP\Files\IRootFolder; |
21
|
|
|
use OCP\ILogger; |
22
|
|
|
use OCP\IUser; |
23
|
|
|
use OCP\IUserManager; |
24
|
|
|
|
25
|
|
|
class BackgroundScanner extends TimedJob { |
26
|
|
|
/** @var IRootFolder */ |
27
|
|
|
protected $rootFolder; |
28
|
|
|
|
29
|
|
|
/** @var ScannerFactory */ |
30
|
|
|
private $scannerFactory; |
31
|
|
|
|
32
|
|
|
/** @var AppConfig */ |
33
|
|
|
private $appConfig; |
34
|
|
|
|
35
|
|
|
/** @var ILogger */ |
36
|
|
|
protected $logger; |
37
|
|
|
|
38
|
|
|
/** @var IUserManager */ |
39
|
|
|
protected $userManager; |
40
|
|
|
|
41
|
|
|
/** @var IDBConnection */ |
42
|
|
|
protected $db; |
43
|
|
|
|
44
|
|
|
/** @var IMimeTypeLoader */ |
45
|
|
|
protected $mimeTypeLoader; |
46
|
|
|
|
47
|
|
|
/** @var ItemFactory */ |
48
|
|
|
protected $itemFactory; |
49
|
|
|
/** @var bool */ |
50
|
|
|
private $isCLI; |
51
|
|
|
|
52
|
|
|
public function __construct(ScannerFactory $scannerFactory, |
53
|
|
|
AppConfig $appConfig, |
54
|
|
|
IRootFolder $rootFolder, |
55
|
|
|
ILogger $logger, |
56
|
|
|
IUserManager $userManager, |
57
|
|
|
IDBConnection $db, |
58
|
|
|
IMimeTypeLoader $mimeTypeLoader, |
59
|
|
|
ItemFactory $itemFactory, |
60
|
|
|
bool $isCLI |
61
|
|
|
){ |
62
|
|
|
$this->rootFolder = $rootFolder; |
63
|
|
|
$this->scannerFactory = $scannerFactory; |
64
|
|
|
$this->appConfig = $appConfig; |
65
|
|
|
$this->logger = $logger; |
66
|
|
|
$this->userManager = $userManager; |
67
|
|
|
$this->db = $db; |
68
|
|
|
$this->mimeTypeLoader = $mimeTypeLoader; |
69
|
|
|
$this->itemFactory = $itemFactory; |
70
|
|
|
$this->isCLI = $isCLI; |
71
|
|
|
|
72
|
|
|
// Run once per 15 minutes |
73
|
|
|
$this->setInterval(60 * 15); |
74
|
|
|
} |
75
|
|
|
|
76
|
|
|
/** |
77
|
|
|
* Background scanner main job |
78
|
|
|
*/ |
79
|
|
|
public function run($args): void { |
80
|
|
|
// locate files that are not checked yet |
81
|
|
|
try { |
82
|
|
|
$result = $this->getUnscannedFiled(); |
83
|
|
|
} catch(\Exception $e) { |
84
|
|
|
$this->logger->logException($e); |
85
|
|
|
return; |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
$batchSize = $this->getBatchSize(); |
89
|
|
|
|
90
|
|
|
// Run for unscanned files |
91
|
|
|
$cnt = 0; |
92
|
|
View Code Duplication |
while (($row = $result->fetch()) && $cnt < $batchSize) { |
|
|
|
|
93
|
|
|
try { |
94
|
|
|
$fileId = $row['fileid']; |
95
|
|
|
$users = $this->getUserWithAccessToStorage((int)$row['storage']); |
96
|
|
|
|
97
|
|
|
foreach ($users as $user) { |
98
|
|
|
/** @var IUser $owner */ |
99
|
|
|
$owner = $this->userManager->get($user['user_id']); |
100
|
|
|
if (!$owner instanceof IUser){ |
|
|
|
|
101
|
|
|
continue; |
102
|
|
|
} |
103
|
|
|
|
104
|
|
|
$userFolder = $this->rootFolder->getUserFolder($owner->getUID()); |
105
|
|
|
$files = $userFolder->getById($fileId); |
106
|
|
|
|
107
|
|
|
if ($files === []) { |
108
|
|
|
continue; |
109
|
|
|
} |
110
|
|
|
|
111
|
|
|
$file = array_pop($files); |
112
|
|
|
if ($file instanceof File) { |
|
|
|
|
113
|
|
|
$this->scanOneFile($file); |
114
|
|
|
} else { |
115
|
|
|
$this->logger->error('Tried to scan non file'); |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
// increased only for successfully scanned files |
119
|
|
|
$cnt++; |
120
|
|
|
break; |
121
|
|
|
} |
122
|
|
|
} catch (\Exception $e) { |
123
|
|
|
$this->logger->error( __METHOD__ . ', exception: ' . $e->getMessage(), ['app' => 'files_antivirus']); |
124
|
|
|
} |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
if ($cnt === $batchSize) { |
128
|
|
|
// we are done |
129
|
|
|
return; |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
// Run for updated files |
133
|
|
|
try { |
134
|
|
|
$result = $this->getToRescanFiles(); |
135
|
|
|
} catch(\Exception $e) { |
136
|
|
|
$this->logger->logException($e); |
137
|
|
|
return; |
138
|
|
|
} |
139
|
|
|
|
140
|
|
View Code Duplication |
while (($row = $result->fetch()) && $cnt < $batchSize) { |
|
|
|
|
141
|
|
|
try { |
142
|
|
|
$fileId = $row['fileid']; |
143
|
|
|
$users = $this->getUserWithAccessToStorage((int)$row['storage']); |
144
|
|
|
|
145
|
|
|
foreach ($users as $user) { |
146
|
|
|
/** @var IUser $owner */ |
147
|
|
|
$owner = $this->userManager->get($user); |
148
|
|
|
if (!$owner instanceof IUser){ |
|
|
|
|
149
|
|
|
continue; |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
$userFolder = $this->rootFolder->getUserFolder($owner->getUID()); |
153
|
|
|
$files = $userFolder->getById($fileId); |
154
|
|
|
|
155
|
|
|
if ($files === []) { |
156
|
|
|
continue; |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
$file = array_pop($files); |
160
|
|
|
if ($file instanceof File) { |
|
|
|
|
161
|
|
|
$this->scanOneFile($file); |
162
|
|
|
} else { |
163
|
|
|
$this->logger->error('Tried to scan non file'); |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
// increased only for successfully scanned files |
167
|
|
|
$cnt++; |
168
|
|
|
break; |
169
|
|
|
} |
170
|
|
|
} catch (\Exception $e) { |
171
|
|
|
$this->logger->error( __METHOD__ . ', exception: ' . $e->getMessage(), ['app' => 'files_antivirus']); |
172
|
|
|
} |
173
|
|
|
} |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
protected function getBatchSize(): int { |
177
|
|
|
$batchSize = 10; |
178
|
|
|
if ($this->isCLI) { |
179
|
|
|
$batchSize = 100; |
180
|
|
|
} |
181
|
|
|
return $batchSize; |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
protected function getSizeLimitExpression(IQueryBuilder $qb) { |
185
|
|
|
$sizeLimit = (int)$this->appConfig->getAvMaxFileSize(); |
186
|
|
|
if ( $sizeLimit === -1 ){ |
187
|
|
|
$sizeLimitExpr = $qb->expr()->neq('fc.size', $qb->expr()->literal('0')); |
188
|
|
|
} else { |
189
|
|
|
$sizeLimitExpr = $qb->expr()->andX( |
190
|
|
|
$qb->expr()->neq('fc.size', $qb->expr()->literal('0')), |
191
|
|
|
$qb->expr()->lt('fc.size', $qb->createNamedParameter($sizeLimit)) |
192
|
|
|
); |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
return $sizeLimitExpr; |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
protected function getUserWithAccessToStorage(int $storageId): array { |
199
|
|
|
$qb = $this->db->getQueryBuilder(); |
200
|
|
|
|
201
|
|
|
$qb->select('user_id') |
202
|
|
|
->from('mounts') |
203
|
|
|
->where($qb->expr()->eq('storage_id', $qb->createNamedParameter($storageId))); |
204
|
|
|
|
205
|
|
|
$cursor = $qb->execute(); |
206
|
|
|
$data = $cursor->fetchAll(); |
207
|
|
|
$cursor->closeCursor(); |
208
|
|
|
return $data; |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
protected function getUnscannedFiled() { |
212
|
|
|
$dirMimeTypeId = $this->mimeTypeLoader->getId('httpd/unix-directory'); |
213
|
|
|
|
214
|
|
|
$qb1 = $this->db->getQueryBuilder(); |
215
|
|
|
$qb1->select('fileid') |
216
|
|
|
->from('files_antivirus'); |
217
|
|
|
|
218
|
|
|
$qb2 = $this->db->getQueryBuilder(); |
219
|
|
|
$qb2->select('fileid', 'storage') |
220
|
|
|
->from('filecache', 'fc') |
221
|
|
|
->where($qb2->expr()->notIn('fileid', $qb2->createFunction($qb1->getSQL()))) |
222
|
|
|
->andWhere($qb2->expr()->neq('mimetype', $qb2->expr()->literal($dirMimeTypeId))) |
223
|
|
|
->andWhere($qb2->expr()->like('path', $qb2->expr()->literal('files/%'))) |
224
|
|
|
->andWhere($this->getSizeLimitExpression($qb2)) |
225
|
|
|
->setMaxResults($this->getBatchSize() * 10); |
226
|
|
|
|
227
|
|
|
return $qb2->execute(); |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
protected function getToRescanFiles() { |
231
|
|
|
$qb = $this->db->getQueryBuilder(); |
232
|
|
|
$qb->select('fc.fileid', 'fc.storage') |
233
|
|
|
->from('filecache', 'fc') |
234
|
|
|
->join('fc', 'files_antivirus', 'fa', $qb->expr()->eq('fc.fileid', 'fa.fileid')) |
235
|
|
|
->andWhere($qb->expr()->lt('fa.check_time', 'fc.mtime')) |
236
|
|
|
->andWhere($this->getSizeLimitExpression($qb)) |
237
|
|
|
->setMaxResults($this->getBatchSize() * 10); |
238
|
|
|
|
239
|
|
|
return $qb->execute(); |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
protected function scanOneFile(File $file): void { |
243
|
|
|
$item = $this->itemFactory->newItem($file); |
244
|
|
|
$scanner = $this->scannerFactory->getScanner(); |
245
|
|
|
$status = $scanner->scan($item); |
246
|
|
|
$status->dispatch($item); |
247
|
|
|
} |
248
|
|
|
} |
249
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.