This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * @author Bart Visscher <[email protected]> |
||
4 | * @author Jörn Friedrich Dreyer <[email protected]> |
||
5 | * @author [email protected] <[email protected]> |
||
6 | * @author Morris Jobke <[email protected]> |
||
7 | * @author Robin Appelman <[email protected]> |
||
8 | * @author Thomas Müller <[email protected]> |
||
9 | * @author Vincent Petry <[email protected]> |
||
10 | * @author Sujith Haridasan <[email protected]> |
||
11 | * |
||
12 | * @copyright Copyright (c) 2018, ownCloud GmbH |
||
13 | * @license AGPL-3.0 |
||
14 | * |
||
15 | * This code is free software: you can redistribute it and/or modify |
||
16 | * it under the terms of the GNU Affero General Public License, version 3, |
||
17 | * as published by the Free Software Foundation. |
||
18 | * |
||
19 | * This program is distributed in the hope that it will be useful, |
||
20 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
21 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
22 | * GNU Affero General Public License for more details. |
||
23 | * |
||
24 | * You should have received a copy of the GNU Affero General Public License, version 3, |
||
25 | * along with this program. If not, see <http://www.gnu.org/licenses/> |
||
26 | * |
||
27 | */ |
||
28 | |||
29 | namespace OCA\Files\Command; |
||
30 | |||
31 | use Doctrine\DBAL\Connection; |
||
32 | use OC\Core\Command\Base; |
||
33 | use OC\Core\Command\InterruptedException; |
||
34 | use OC\ForbiddenException; |
||
35 | use OC\Migration\ConsoleOutput; |
||
36 | use OC\Repair\RepairMismatchFileCachePath; |
||
37 | use OCP\Files\IMimeTypeLoader; |
||
38 | use OCP\Files\StorageNotAvailableException; |
||
39 | use OCP\IConfig; |
||
40 | use OCP\IDBConnection; |
||
41 | use OCP\IGroupManager; |
||
42 | use OCP\IUserManager; |
||
43 | use OCP\Lock\ILockingProvider; |
||
44 | use OCP\Lock\LockedException; |
||
45 | use OCP\ILogger; |
||
46 | use Symfony\Component\Console\Helper\Table; |
||
47 | use Symfony\Component\Console\Input\InputArgument; |
||
48 | use Symfony\Component\Console\Input\InputInterface; |
||
49 | use Symfony\Component\Console\Input\InputOption; |
||
50 | use Symfony\Component\Console\Output\OutputInterface; |
||
51 | |||
52 | class Scan extends Base { |
||
53 | |||
54 | /** @var IUserManager $userManager */ |
||
55 | private $userManager; |
||
56 | /** @var IGroupManager $groupManager */ |
||
57 | private $groupManager; |
||
58 | /** @var ILockingProvider */ |
||
59 | private $lockingProvider; |
||
60 | /** @var IMimeTypeLoader */ |
||
61 | private $mimeTypeLoader; |
||
62 | /** @var ILogger */ |
||
63 | private $logger; |
||
64 | /** @var IConfig */ |
||
65 | private $config; |
||
66 | /** @var float */ |
||
67 | protected $execTime = 0; |
||
68 | /** @var int */ |
||
69 | protected $foldersCounter = 0; |
||
70 | /** @var int */ |
||
71 | protected $filesCounter = 0; |
||
72 | |||
73 | public function __construct( |
||
74 | IUserManager $userManager, |
||
75 | IGroupManager $groupManager, |
||
76 | ILockingProvider $lockingProvider, |
||
77 | IMimeTypeLoader $mimeTypeLoader, |
||
78 | ILogger $logger, |
||
79 | IConfig $config |
||
80 | ) { |
||
81 | $this->userManager = $userManager; |
||
82 | $this->groupManager = $groupManager; |
||
83 | $this->lockingProvider = $lockingProvider; |
||
84 | $this->mimeTypeLoader = $mimeTypeLoader; |
||
85 | $this->logger = $logger; |
||
86 | $this->config = $config; |
||
87 | parent::__construct(); |
||
88 | } |
||
89 | |||
90 | protected function configure() { |
||
91 | parent::configure(); |
||
92 | |||
93 | $this |
||
94 | ->setName('files:scan') |
||
95 | ->setDescription('Scans the filesystem for changes and updates the file cache accordingly.') |
||
96 | ->addArgument( |
||
97 | 'user_id', |
||
98 | InputArgument::OPTIONAL | InputArgument::IS_ARRAY, |
||
99 | 'Will rescan all files of the given user(s).' |
||
100 | ) |
||
101 | ->addOption( |
||
102 | 'path', |
||
103 | 'p', |
||
104 | InputArgument::OPTIONAL, |
||
105 | 'Limit rescan to this path, e.g., --path="/alice/files/Music", the user_id is determined by the path and the user_id parameter and --all are ignored.' |
||
106 | ) |
||
107 | ->addOption( |
||
108 | 'group', |
||
109 | 'g', |
||
110 | InputOption::VALUE_IS_ARRAY|InputOption::VALUE_REQUIRED, |
||
111 | 'Scan user(s) under the group(s). This option can be used as --group=foo --group=bar to scan groups foo and bar' |
||
112 | ) |
||
113 | ->addOption( |
||
114 | 'quiet', |
||
115 | 'q', |
||
116 | InputOption::VALUE_NONE, |
||
117 | 'Suppress any output.' |
||
118 | ) |
||
119 | ->addOption( |
||
120 | 'verbose', |
||
121 | '-v|vv|vvv', |
||
122 | InputOption::VALUE_NONE, |
||
123 | "Increase the output's verbosity." |
||
124 | ) |
||
125 | ->addOption( |
||
126 | 'all', |
||
127 | null, |
||
128 | InputOption::VALUE_NONE, |
||
129 | 'Will rescan all files of all known users.' |
||
130 | ) |
||
131 | ->addOption( |
||
132 | 'repair', |
||
133 | null, |
||
134 | InputOption::VALUE_NONE, |
||
135 | 'Will repair detached filecache entries (slow).' |
||
136 | )->addOption( |
||
137 | 'unscanned', |
||
138 | null, |
||
139 | InputOption::VALUE_NONE, |
||
140 | 'Only scan files which are marked as not fully scanned.' |
||
141 | ); |
||
142 | } |
||
143 | |||
144 | public function checkScanWarning($fullPath, OutputInterface $output) { |
||
145 | $normalizedPath = \basename(\OC\Files\Filesystem::normalizePath($fullPath)); |
||
146 | $path = \basename($fullPath); |
||
147 | |||
148 | if ($normalizedPath !== $path) { |
||
149 | $output->writeln("\t<error>Entry \"" . $fullPath . '" will not be accessible due to incompatible encoding</error>'); |
||
150 | } |
||
151 | } |
||
152 | |||
153 | /** |
||
154 | * Repair all storages at once |
||
155 | * |
||
156 | * @param OutputInterface $output |
||
157 | */ |
||
158 | protected function repairAll(OutputInterface $output) { |
||
159 | $connection = $this->reconnectToDatabase($output); |
||
160 | $repairStep = new RepairMismatchFileCachePath( |
||
161 | $connection, |
||
162 | $this->mimeTypeLoader, |
||
163 | $this->logger, |
||
164 | $this->config |
||
165 | ); |
||
166 | $repairStep->setStorageNumericId(null); |
||
167 | $repairStep->setCountOnly(false); |
||
168 | $repairStep->doRepair(new ConsoleOutput($output)); |
||
169 | } |
||
170 | |||
171 | protected function scanFiles($user, $path, $verbose, OutputInterface $output, $backgroundScan = false, $shouldRepair = false) { |
||
172 | $connection = $this->reconnectToDatabase($output); |
||
173 | $scanner = new \OC\Files\Utils\Scanner($user, $connection, \OC::$server->getLogger()); |
||
174 | if ($shouldRepair) { |
||
175 | $scanner->listen('\OC\Files\Utils\Scanner', 'beforeScanStorage', function ($storage) use ($output, $connection) { |
||
176 | try { |
||
177 | // FIXME: this will lock the storage even if there is nothing to repair |
||
178 | $storage->acquireLock('', ILockingProvider::LOCK_EXCLUSIVE, $this->lockingProvider); |
||
179 | } catch (LockedException $e) { |
||
180 | $output->writeln("\t<error>Storage \"" . $storage->getCache()->getNumericStorageId() . '" cannot be repaired as it is currently in use, please try again later</error>'); |
||
181 | return; |
||
182 | } |
||
183 | try { |
||
184 | $repairStep = new RepairMismatchFileCachePath( |
||
185 | $connection, |
||
186 | $this->mimeTypeLoader, |
||
187 | $this->logger, |
||
188 | $this->config |
||
189 | ); |
||
190 | $repairStep->setStorageNumericId($storage->getCache()->getNumericStorageId()); |
||
191 | $repairStep->setCountOnly(false); |
||
192 | $repairStep->run(new ConsoleOutput($output)); |
||
193 | } finally { |
||
194 | $storage->releaseLock('', ILockingProvider::LOCK_EXCLUSIVE, $this->lockingProvider); |
||
195 | } |
||
196 | }); |
||
197 | } |
||
198 | |||
199 | # check on each file/folder if there was a user interrupt (ctrl-c) and throw an exception |
||
200 | # printout and count |
||
201 | if ($verbose) { |
||
202 | $scanner->listen('\OC\Files\Utils\Scanner', 'scanFile', function ($path) use ($output) { |
||
203 | $output->writeln("\tFile <info>$path</info>"); |
||
204 | $this->filesCounter += 1; |
||
205 | if ($this->hasBeenInterrupted()) { |
||
206 | throw new InterruptedException(); |
||
207 | } |
||
208 | }); |
||
209 | $scanner->listen('\OC\Files\Utils\Scanner', 'scanFolder', function ($path) use ($output) { |
||
210 | $output->writeln("\tFolder <info>$path</info>"); |
||
211 | $this->foldersCounter += 1; |
||
212 | if ($this->hasBeenInterrupted()) { |
||
213 | throw new InterruptedException(); |
||
214 | } |
||
215 | }); |
||
216 | $scanner->listen('\OC\Files\Utils\Scanner', 'StorageNotAvailable', function (StorageNotAvailableException $e) use ($output) { |
||
217 | $output->writeln("Error while scanning, storage not available (" . $e->getMessage() . ")"); |
||
218 | }); |
||
219 | # count only |
||
220 | } else { |
||
221 | $scanner->listen('\OC\Files\Utils\Scanner', 'scanFile', function () { |
||
222 | $this->filesCounter += 1; |
||
223 | if ($this->hasBeenInterrupted()) { |
||
224 | throw new InterruptedException(); |
||
225 | } |
||
226 | }); |
||
227 | $scanner->listen('\OC\Files\Utils\Scanner', 'scanFolder', function () { |
||
228 | $this->foldersCounter += 1; |
||
229 | if ($this->hasBeenInterrupted()) { |
||
230 | throw new InterruptedException(); |
||
231 | } |
||
232 | }); |
||
233 | } |
||
234 | $scanner->listen('\OC\Files\Utils\Scanner', 'scanFile', function ($path) use ($output) { |
||
235 | $this->checkScanWarning($path, $output); |
||
236 | }); |
||
237 | $scanner->listen('\OC\Files\Utils\Scanner', 'scanFolder', function ($path) use ($output) { |
||
238 | $this->checkScanWarning($path, $output); |
||
239 | }); |
||
240 | |||
241 | try { |
||
242 | if ($backgroundScan) { |
||
243 | $scanner->backgroundScan($path); |
||
244 | } else { |
||
245 | $scanner->scan($path, $shouldRepair); |
||
246 | } |
||
247 | } catch (ForbiddenException $e) { |
||
248 | $output->writeln("<error>Home storage for user $user not writable</error>"); |
||
249 | $output->writeln("Make sure you're running the scan command only as the user the web server runs as"); |
||
250 | } catch (InterruptedException $e) { |
||
251 | # exit the function if ctrl-c has been pressed |
||
252 | $output->writeln('Interrupted by user'); |
||
253 | return; |
||
254 | } catch (\Exception $e) { |
||
255 | $output->writeln('<error>Exception during scan: ' . \get_class($e) . ': ' . $e->getMessage() . "\n" . $e->getTraceAsString() . '</error>'); |
||
256 | } |
||
257 | } |
||
258 | |||
259 | protected function getAllUsersFromGroup($group) { |
||
260 | $count = 0; |
||
261 | $users = []; |
||
262 | foreach ($this->groupManager->findUsersInGroup($group) as $user) { |
||
263 | \array_push($users, $user->getUID()); |
||
264 | $count++; |
||
265 | //Take 200 users at a time |
||
266 | if ($count > 199) { |
||
267 | yield $users; |
||
268 | $count = 1; |
||
269 | $users = []; |
||
270 | } |
||
271 | } |
||
272 | if (\count($users) > 0) { |
||
273 | yield $users; |
||
274 | } |
||
275 | } |
||
276 | |||
277 | protected function execute(InputInterface $input, OutputInterface $output) { |
||
278 | $inputPath = $input->getOption('path'); |
||
279 | $groups = $input->getOption('group'); |
||
280 | $shouldRepairStoragesIndividually = (bool) $input->getOption('repair'); |
||
281 | |||
282 | if (\count($groups) >= 1) { |
||
283 | $users = []; |
||
284 | foreach ($groups as $group) { |
||
285 | if ($this->groupManager->groupExists($group) === false) { |
||
286 | $output->writeln("Group name $group doesn't exist"); |
||
287 | return 1; |
||
288 | } else { |
||
289 | $users[$group] = []; |
||
290 | foreach ($this->getAllUsersFromGroup($group) as $users_array) { |
||
291 | $users[$group] = $users_array; |
||
292 | $this->processUserChunks($input, $output, $users, $inputPath, $shouldRepairStoragesIndividually, $group); |
||
293 | } |
||
294 | } |
||
295 | } |
||
296 | } elseif ($inputPath) { |
||
297 | $inputPath = '/' . \trim($inputPath, '/'); |
||
298 | list(, $user, ) = \explode('/', $inputPath, 3); |
||
299 | $users = [$user]; |
||
300 | } elseif ($input->getOption('all')) { |
||
301 | // we can only repair all storages in bulk (more efficient) if singleuser or maintenance mode |
||
302 | // is enabled to prevent concurrent user access |
||
303 | if ($input->getOption('repair')) { |
||
304 | if ($this->config->getSystemValue('singleuser', false) || $this->config->getSystemValue('maintenance', false)) { |
||
305 | // repair all storages at once |
||
306 | $this->repairAll($output); |
||
307 | // don't fix individually |
||
308 | $shouldRepairStoragesIndividually = false; |
||
309 | } else { |
||
310 | $output->writeln("<comment>Please switch to single user mode to repair all storages: occ maintenance:singleuser --on</comment>"); |
||
311 | $output->writeln("<comment>Alternatively, you can specify a user to repair. Please note that this is slower than repairing in bulk</comment>"); |
||
312 | return 1; |
||
313 | } |
||
314 | } |
||
315 | $users = $this->userManager->search(''); |
||
316 | } else { |
||
317 | $users = $input->getArgument('user_id'); |
||
318 | } |
||
319 | |||
320 | if (\count($groups) === 0) { |
||
321 | $this->processUserChunks($input, $output, $users, $inputPath, $shouldRepairStoragesIndividually); |
||
322 | } |
||
323 | } |
||
324 | |||
325 | protected function processUserChunks($input, $output, $users, $inputPath, $shouldRepairStoragesIndividually, $group = null) { |
||
326 | # no messaging level option means: no full printout but statistics |
||
327 | # $quiet means no print at all |
||
328 | # $verbose means full printout including statistics |
||
329 | # -q -v full stat |
||
330 | # 0 0 no yes |
||
331 | # 0 1 yes yes |
||
332 | # 1 -- no no (quiet overrules verbose) |
||
333 | $verbose = $input->getOption('verbose'); |
||
334 | $quiet = $input->getOption('quiet'); |
||
335 | # restrict the verbosity level to VERBOSITY_VERBOSE |
||
336 | if ($output->getVerbosity()>OutputInterface::VERBOSITY_VERBOSE) { |
||
337 | $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); |
||
338 | } |
||
339 | if ($quiet) { |
||
340 | $verbose = false; |
||
341 | } |
||
342 | |||
343 | # check quantity of users to be process and show it on the command line |
||
344 | $users_total = \count($users); |
||
345 | if ($users_total === 0) { |
||
346 | $output->writeln("<error>Please specify the user id to scan, \"--all\" to scan for all users or \"--path=...\"</error>"); |
||
347 | return; |
||
348 | } else { |
||
349 | $this->initTools(); |
||
350 | if ($group !== null) { |
||
351 | $output->writeln("Scanning group $group"); |
||
352 | $this->userScan($users[$group], $inputPath, $shouldRepairStoragesIndividually, $input, $output, $verbose); |
||
353 | } elseif ($users_total >= 1) { |
||
354 | $output->writeln("\nScanning files for $users_total users"); |
||
355 | $this->userScan($users, $inputPath, $shouldRepairStoragesIndividually, $input, $output, $verbose); |
||
356 | } |
||
357 | } |
||
358 | |||
359 | # stat: printout statistics if $quiet was not set |
||
360 | if (!$quiet) { |
||
361 | $this->presentStats($output); |
||
362 | } |
||
363 | } |
||
364 | |||
365 | /** |
||
366 | * Initialises some useful tools for the Command |
||
367 | */ |
||
368 | protected function initTools() { |
||
369 | // Start the timer |
||
370 | $this->execTime = -\microtime(true); |
||
371 | // Convert PHP errors to exceptions |
||
372 | \set_error_handler([$this, 'exceptionErrorHandler'], E_ALL); |
||
373 | } |
||
374 | |||
375 | protected function userScan($users, $inputPath, $shouldRepairStoragesIndividually, $input, $output, $verbose) { |
||
376 | $users_total = \count($users); |
||
377 | $user_count = 0; |
||
378 | foreach ($users as $user) { |
||
379 | if (\is_object($user)) { |
||
380 | $user = $user->getUID(); |
||
381 | } |
||
382 | $path = $inputPath ? $inputPath : '/' . $user; |
||
383 | $user_count += 1; |
||
384 | if ($this->userManager->userExists($user)) { |
||
385 | # add an extra line when verbose is set to optical separate users |
||
386 | if ($verbose) { |
||
387 | $output->writeln(""); |
||
388 | } |
||
389 | $r = $shouldRepairStoragesIndividually ? ' (and repair)' : ''; |
||
390 | $output->writeln("Starting scan$r for user $user_count out of $users_total ($user)"); |
||
391 | # full: printout data if $verbose was set |
||
392 | $this->scanFiles($user, $path, $verbose, $output, $input->getOption('unscanned'), $shouldRepairStoragesIndividually); |
||
393 | } else { |
||
394 | $output->writeln("<error>Unknown user $user_count $user</error>"); |
||
395 | } |
||
396 | # check on each user if there was a user interrupt (ctrl-c) and exit foreach |
||
397 | if ($this->hasBeenInterrupted()) { |
||
398 | break; |
||
399 | } |
||
400 | } |
||
401 | } |
||
402 | |||
403 | /** |
||
404 | * Processes PHP errors as exceptions in order to be able to keep track of problems |
||
405 | * |
||
406 | * @see https://secure.php.net/manual/en/function.set-error-handler.php |
||
407 | * |
||
408 | * @param int $severity the level of the error raised |
||
409 | * @param string $message |
||
410 | * @param string $file the filename that the error was raised in |
||
411 | * @param int $line the line number the error was raised |
||
412 | * |
||
413 | * @throws \ErrorException |
||
414 | */ |
||
415 | public function exceptionErrorHandler($severity, $message, $file, $line) { |
||
416 | if (!(\error_reporting() & $severity)) { |
||
417 | // This error code is not included in error_reporting |
||
418 | return; |
||
419 | } |
||
420 | throw new \ErrorException($message, 0, $severity, $file, $line); |
||
421 | } |
||
422 | |||
423 | /** |
||
424 | * @param OutputInterface $output |
||
425 | */ |
||
426 | protected function presentStats(OutputInterface $output) { |
||
427 | // Stop the timer |
||
428 | $this->execTime += \microtime(true); |
||
429 | $output->writeln(""); |
||
430 | |||
431 | $headers = [ |
||
432 | 'Folders', 'Files', 'Elapsed time', 'Items per second' |
||
433 | ]; |
||
434 | |||
435 | $this->showSummary($headers, null, $output); |
||
0 ignored issues
–
show
|
|||
436 | } |
||
437 | |||
438 | /** |
||
439 | * Shows a summary of operations |
||
440 | * |
||
441 | * @param string[] $headers |
||
442 | * @param string[] $rows |
||
443 | * @param OutputInterface $output |
||
444 | */ |
||
445 | protected function showSummary($headers, $rows, OutputInterface $output) { |
||
446 | $niceDate = $this->formatExecTime(); |
||
447 | $itemsPerSecond = $this->getItemsPerSecond(); |
||
448 | if (!$rows) { |
||
0 ignored issues
–
show
The expression
$rows of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using
Loading history...
|
|||
449 | $rows = [ |
||
450 | $this->foldersCounter, |
||
451 | $this->filesCounter, |
||
452 | $niceDate, |
||
453 | $itemsPerSecond |
||
454 | ]; |
||
455 | } |
||
456 | $table = new Table($output); |
||
457 | $table |
||
458 | ->setHeaders($headers) |
||
459 | ->setRows([$rows]); |
||
460 | $table->render(); |
||
461 | } |
||
462 | |||
463 | /** |
||
464 | * Get items per second processed, no fractions |
||
465 | * |
||
466 | * @return string |
||
467 | */ |
||
468 | protected function getItemsPerSecond() { |
||
469 | $items = $this->foldersCounter + $this->filesCounter; |
||
470 | if ($this->execTime === 0) { |
||
471 | // catch div by 0 |
||
472 | $itemsPerSecond = 0; |
||
473 | } else { |
||
474 | $itemsPerSecond = $items / $this->execTime; |
||
475 | } |
||
476 | return \sprintf("%.0f", $itemsPerSecond); |
||
477 | } |
||
478 | |||
479 | /** |
||
480 | * Formats microtime into a human readable format |
||
481 | * |
||
482 | * @return string |
||
483 | */ |
||
484 | protected function formatExecTime() { |
||
485 | list($secs, $tens) = \explode('.', \sprintf("%.1f", ($this->execTime))); |
||
0 ignored issues
–
show
The assignment to
$tens is unused. Consider omitting it like so list($first,,$third) .
This checks looks for assignemnts to variables using the Consider the following code example. <?php
function returnThreeValues() {
return array('a', 'b', 'c');
}
list($a, $b, $c) = returnThreeValues();
print $a . " - " . $c;
Only the variables Instead, the list call could have been. list($a,, $c) = returnThreeValues();
Loading history...
|
|||
486 | |||
487 | # if you want to have microseconds add this: . '.' . $tens; |
||
488 | return \date('H:i:s', $secs); |
||
489 | } |
||
490 | |||
491 | /** |
||
492 | * @return \OCP\IDBConnection |
||
493 | */ |
||
494 | protected function reconnectToDatabase(OutputInterface $output) { |
||
495 | /** @var Connection | IDBConnection $connection*/ |
||
496 | $connection = \OC::$server->getDatabaseConnection(); |
||
497 | try { |
||
498 | $connection->close(); |
||
499 | } catch (\Exception $ex) { |
||
500 | $output->writeln("<info>Error while disconnecting from database: {$ex->getMessage()}</info>"); |
||
501 | } |
||
502 | while (!$connection->isConnected()) { |
||
503 | try { |
||
504 | $connection->connect(); |
||
505 | } catch (\Exception $ex) { |
||
506 | $output->writeln("<info>Error while re-connecting to database: {$ex->getMessage()}</info>"); |
||
507 | \sleep(60); |
||
508 | } |
||
509 | } |
||
510 | return $connection; |
||
511 | } |
||
512 | } |
||
513 |
It seems like the type of the argument is not accepted by the function/method which you are calling.
In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.
We suggest to add an explicit type cast like in the following example: