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 | * This file is part of Dimsh\React\Filesystem\Monitor; |
||
4 | * |
||
5 | * (c) Abdulrahman Dimashki <[email protected]> |
||
6 | * |
||
7 | * For the full copyright and license information, please view the LICENSE |
||
8 | * file that was distributed with this source code. |
||
9 | */ |
||
10 | |||
11 | |||
12 | namespace Dimsh\React\Filesystem\Monitor; |
||
13 | |||
14 | use Evenement\EventEmitter; |
||
15 | use MKraemer\ReactInotify\Inotify; |
||
16 | use React\EventLoop\LoopInterface; |
||
17 | |||
18 | class Monitor extends EventEmitter |
||
19 | { |
||
20 | use MonitorTrait; |
||
21 | |||
22 | public const EV_CREATE = 'EV_CREATE'; |
||
23 | public const EV_MODIFY = 'EV_MODIFY'; |
||
24 | public const EV_DELETE = 'EV_DELETE'; |
||
25 | |||
26 | protected const MASK_FILE = 0 |
||
27 | | IN_MODIFY; |
||
28 | |||
29 | protected const MASK_FILE_WITH_ATTRIB = 0 |
||
30 | | IN_MODIFY |
||
31 | | IN_ATTRIB; |
||
32 | |||
33 | /** |
||
34 | * the flag |
||
35 | * IN_MODIFY |
||
36 | * makes no sense for directories, as they will report the modify for the first child |
||
37 | * of the directory, and that MODIFY flag is set for the inner files in self::MASK_FILE |
||
38 | * For directories, the modify event is related to mtime and permissions, which is |
||
39 | * included in the IN_ATTRIB flag. |
||
40 | */ |
||
41 | protected const MASK_DIRECTORY = 0 |
||
42 | // | IN_MODIFY |
||
43 | | IN_MOVED_TO |
||
44 | | IN_MOVED_FROM |
||
45 | | IN_CREATE |
||
46 | | IN_DELETE |
||
47 | | IN_DELETE_SELF |
||
48 | | IN_ISDIR; |
||
49 | |||
50 | protected const MASK_DIRECTORY_WITH_MODIFY = 0 |
||
51 | // | IN_MODIFY |
||
52 | | IN_ATTRIB |
||
53 | | IN_MOVED_TO |
||
54 | | IN_MOVED_FROM |
||
55 | | IN_CREATE |
||
56 | | IN_DELETE |
||
57 | | IN_DELETE_SELF |
||
58 | | IN_ISDIR; |
||
59 | |||
60 | protected const MASK_DIRECTORY_CREATED_ONLY = 0 |
||
61 | | IN_MOVED_FROM |
||
62 | | IN_CREATE |
||
63 | | IN_ISDIR; |
||
64 | |||
65 | |||
66 | /** |
||
67 | * @var \MKraemer\ReactInotify\Inotify |
||
68 | */ |
||
69 | protected $inotify; |
||
70 | |||
71 | /** |
||
72 | * @var \Dimsh\React\Filesystem\Monitor\MonitorConfigurator |
||
73 | */ |
||
74 | protected $configurator; |
||
75 | |||
76 | /** |
||
77 | * @var [] |
||
78 | */ |
||
79 | protected $descriptors; |
||
80 | |||
81 | /** |
||
82 | * @var \React\EventLoop\LoopInterface|null |
||
83 | */ |
||
84 | protected $external_event_loop = null; |
||
85 | |||
86 | /** |
||
87 | * @var \React\EventLoop\LoopInterface|null |
||
88 | */ |
||
89 | protected $internal_loop; |
||
90 | |||
91 | /** |
||
92 | * @internal |
||
93 | * @var \Dimsh\React\Filesystem\Monitor\Monitor|null |
||
94 | */ |
||
95 | protected $base_dir_monitor = null; |
||
96 | |||
97 | /** |
||
98 | * Tell if $this->setupListeners() is called. |
||
99 | * |
||
100 | * @internal |
||
101 | * @var bool |
||
102 | */ |
||
103 | protected $is_inotify_event_listener_attached = false; |
||
104 | |||
105 | |||
106 | /** |
||
107 | * MonitorMultiManual constructor. |
||
108 | * |
||
109 | * @param \Dimsh\React\Filesystem\Monitor\MonitorConfigurator $configurator |
||
110 | * @param \React\EventLoop\LoopInterface|null $event_loop if passed then this event loop will be |
||
111 | * used to construct the Inotify object and |
||
112 | * should be "run" externally. When it is |
||
113 | * null an internal loop is created and |
||
114 | * method $this->run() must be called. |
||
115 | */ |
||
116 | public function __construct(MonitorConfigurator $configurator, ?LoopInterface $event_loop = null) |
||
117 | { |
||
118 | $this->configurator = $configurator; |
||
119 | if ($event_loop === null) { |
||
120 | $this->internal_loop = \React\EventLoop\Factory::create(); |
||
121 | } else { |
||
122 | $this->external_event_loop = $event_loop; |
||
123 | } |
||
124 | /** |
||
125 | * Two conditions I could use below: |
||
126 | * - && PHP_OS == "Linux" |
||
127 | * - && file_exists('/') |
||
128 | * |
||
129 | * And I have chosen the second, since as the time of this writing, there is no check |
||
130 | * for platform in the whole code. |
||
131 | */ |
||
132 | if (!file_exists($configurator->getBaseDirectory()) && |
||
133 | file_exists('/') && |
||
134 | $configurator->getBaseDirectory() != '/' && |
||
135 | $configurator->isAutoCreateNotFoundMonitor()) { |
||
136 | // $base_directory is required to be with trailing slash |
||
137 | $base_directory = $configurator->getBaseDirectoryWithTrailingSlash(); |
||
138 | $level = substr_count($base_directory, '/') - 1; |
||
139 | $pattern = $base_directory; |
||
140 | $second_confiurator = MonitorConfigurator::factory() |
||
141 | ->setMonitorCreatedOnly(true) |
||
142 | ->setLevel($level) |
||
143 | ->setFilesToMonitor([ |
||
144 | $pattern, |
||
145 | ]); |
||
146 | try { |
||
147 | $second_confiurator->setBaseDirectory('/'); |
||
148 | $this->base_dir_monitor = new Monitor( |
||
149 | $second_confiurator, |
||
150 | $this->getEventLoop() |
||
151 | ); |
||
152 | $this->base_dir_monitor->on( |
||
153 | Monitor::EV_CREATE, |
||
154 | function ($path, $monitor) use ($base_directory) { |
||
155 | /** |
||
156 | * @var \Dimsh\React\Filesystem\Monitor\Monitor $monitor |
||
157 | */ |
||
158 | if ($path == $base_directory) { |
||
159 | $monitor->getEventLoop()->stop(); |
||
160 | $this->inotify = new Inotify($monitor->getEventLoop()); |
||
161 | $this->setupListeners(); |
||
162 | $this->add($this->configurator->getBaseDirectoryWithTrailingSlash()); |
||
163 | $monitor->getEventLoop()->run(); |
||
164 | } |
||
165 | } |
||
166 | ); |
||
167 | } catch (\Exception $exception) { |
||
0 ignored issues
–
show
Coding Style
Comprehensibility
introduced
by
![]() |
|||
168 | } |
||
169 | } else { |
||
170 | $this->inotify = new Inotify($this->getEventLoop()); |
||
171 | $this->setupListeners(); |
||
172 | $this->add($this->configurator->getBaseDirectoryWithTrailingSlash()); |
||
173 | } |
||
174 | } |
||
175 | |||
176 | /** |
||
177 | * Get the event loop used by this instance. |
||
178 | * |
||
179 | * It is either the internal loop created in the constructor if no external one is |
||
180 | * passed to it, or the external. |
||
181 | * |
||
182 | * @return \React\EventLoop\LoopInterface |
||
183 | */ |
||
184 | public function getEventLoop(): LoopInterface |
||
185 | { |
||
186 | return $this->external_event_loop === null ? $this->internal_loop : $this->external_event_loop; |
||
187 | } |
||
188 | |||
189 | /** |
||
190 | * Fire the created event. |
||
191 | * |
||
192 | * If $path has a trailing slash, then a directory is created, else a file. |
||
193 | * This will only be fired for files/dirs that matches the patterns defined |
||
194 | * in the configurator. |
||
195 | * |
||
196 | * @param string $path |
||
197 | */ |
||
198 | protected function fireCreated($path): void |
||
199 | { |
||
200 | $this->emit(self::EV_CREATE, [$path, $this]); |
||
201 | } |
||
202 | |||
203 | /** |
||
204 | * Fire the modified event. |
||
205 | * |
||
206 | * If $path has a trailing slash, then a directory is modified, else a file. |
||
207 | * This will only be fired for files/dirs that matches the patterns defined |
||
208 | * in the configurator. |
||
209 | * |
||
210 | * @param string $path |
||
211 | */ |
||
212 | protected function fireModified($path): void |
||
213 | { |
||
214 | $this->emit(self::EV_MODIFY, [$path, $this]); |
||
215 | } |
||
216 | |||
217 | /** |
||
218 | * Fire the deleted event. |
||
219 | * |
||
220 | * If $path has a trailing slash, then a directory is deleted, else a file. |
||
221 | * This will only be fired for files/dirs that matches the patterns defined |
||
222 | * in the configurator. |
||
223 | * |
||
224 | * @param string $path |
||
225 | */ |
||
226 | protected function fireDeleted($path): void |
||
227 | { |
||
228 | $this->emit(self::EV_DELETE, [$path, $this]); |
||
229 | } |
||
230 | |||
231 | /** |
||
232 | * Setup event listeners on the inotify. |
||
233 | * |
||
234 | * @param bool $detach set to true to detach event listeners |
||
235 | * |
||
236 | * @return void |
||
237 | */ |
||
238 | protected function setupListeners($detach = false): void |
||
239 | { |
||
240 | $method = $detach ? 'removeListener' : 'on'; |
||
241 | $this->inotify->$method(IN_MODIFY, function ($path) { |
||
242 | if ($this->configurator->isPathMatchMonitor($path)) { |
||
243 | $this->fireModified($path); |
||
244 | } |
||
245 | }); |
||
246 | |||
247 | $this->inotify->$method(IN_ATTRIB, function ($path) { |
||
248 | if ($this->configurator->isPathMatchMonitor($path)) { |
||
249 | $this->fireModified($path); |
||
250 | } |
||
251 | }); |
||
252 | |||
253 | if ($this->configurator->isFireModifiedOnDirectories()) { |
||
254 | $this->inotify->$method(IN_ATTRIB | IN_ISDIR, function ($path) { |
||
255 | if ($this->hasTrailingSlash($path) && $this->configurator->isPathMatchMonitor($path)) { |
||
256 | $this->fireModified($path); |
||
257 | } |
||
258 | }); |
||
259 | } |
||
260 | |||
261 | $this->inotify->$method(IN_MOVED_FROM, function ($path) { |
||
262 | $this->remove($path); |
||
263 | }); |
||
264 | |||
265 | $this->inotify->$method(IN_MOVED_TO, function ($path) { |
||
266 | $this->add($path); |
||
267 | }); |
||
268 | |||
269 | $this->inotify->$method(IN_MOVED_FROM | IN_ISDIR, function ($path) { |
||
270 | $path = $this->dirPath($path); |
||
271 | $this->remove($path); |
||
272 | }); |
||
273 | |||
274 | $this->inotify->$method(IN_MOVED_TO | IN_ISDIR, function ($path) { |
||
275 | $path = $this->dirPath($path); |
||
276 | $this->add($path); |
||
277 | }); |
||
278 | |||
279 | $this->inotify->$method(IN_CREATE, function ($path) { |
||
280 | $this->add($path); |
||
281 | }); |
||
282 | |||
283 | $this->inotify->$method(IN_CREATE | IN_ISDIR, function ($path) { |
||
284 | $path = $this->dirPath($path); |
||
285 | $this->add($path); |
||
286 | }); |
||
287 | |||
288 | $this->inotify->$method(IN_DELETE, function ($path) { |
||
289 | $this->remove($path); |
||
290 | }); |
||
291 | |||
292 | $this->inotify->$method(IN_DELETE | IN_ISDIR, function ($path) { |
||
293 | $path = $this->dirPath($path); |
||
294 | $this->remove($path); |
||
295 | }); |
||
296 | |||
297 | $this->inotify->$method(IN_DELETE_SELF, function ($path) { |
||
298 | $this->remove($path); |
||
299 | }); |
||
300 | |||
301 | $this->inotify->$method(IN_DELETE_SELF | IN_ISDIR, function ($path) { |
||
302 | $path = $this->dirPath($path); |
||
303 | $this->remove($path); |
||
304 | }); |
||
305 | |||
306 | $this->is_inotify_event_listener_attached = !$detach; |
||
307 | } |
||
308 | |||
309 | /** |
||
310 | * Add a file/directory to the monitored list. |
||
311 | * |
||
312 | * if $path has a trailing slash then it is a directory, else a file. |
||
313 | * |
||
314 | * @param string $path |
||
315 | * |
||
316 | * @return void |
||
317 | */ |
||
318 | protected function add($path): void |
||
319 | { |
||
320 | if ($this->hasTrailingSlash($path)) { |
||
321 | $mask = $this->configurator->isFireModifiedOnDirectories() ? |
||
322 | self::MASK_DIRECTORY_WITH_MODIFY : self::MASK_DIRECTORY; |
||
323 | if ($this->configurator->isMonitorCreatedOnly()) { |
||
324 | $mask = self::MASK_DIRECTORY_CREATED_ONLY; |
||
325 | } |
||
326 | if (!$this->configurator->mayDirectoryContainMonitoredItems($path)) { |
||
327 | // if this directory will not contain items we want to watch, then |
||
328 | // conditionally watch it if it itself matches a pattern. |
||
329 | $this->conditionallyAddAndFire($path, $mask); |
||
330 | } else { |
||
331 | // else if it may contain other items we want to watch, then add it to |
||
332 | // watch list and conditionally fire the event it its name match a pattern. |
||
333 | // Then include its subitems to the watch list. |
||
334 | $this->addAndConditionallyFire($path, $mask); |
||
335 | $sub_items = @scandir($path); |
||
336 | if ($sub_items) { |
||
337 | foreach ($sub_items as $sub_item) { |
||
338 | if ($sub_item === '.' || $sub_item === '..') { |
||
339 | continue; |
||
340 | } |
||
341 | $new_path = $path . $sub_item; |
||
342 | if (is_dir($new_path)) { |
||
343 | $this->add($new_path . '/'); |
||
344 | } else { |
||
345 | $this->add($new_path); |
||
346 | } |
||
347 | } |
||
348 | } |
||
349 | } |
||
350 | } else { |
||
351 | $mask = $this->configurator->isFireModifiedOnDirectories() ? self::MASK_FILE : self::MASK_FILE_WITH_ATTRIB; |
||
352 | $this->conditionallyAddAndFire($path, $mask); |
||
353 | } |
||
354 | } |
||
355 | |||
356 | /** |
||
357 | * If the path matches a pattern we want to monitor, then add the path to |
||
358 | * the watched list and fire the created event. |
||
359 | * |
||
360 | * @param string $path |
||
361 | * @param int $mask |
||
362 | * |
||
363 | * @return void |
||
364 | */ |
||
365 | protected function conditionallyAddAndFire($path, $mask): void |
||
366 | { |
||
367 | if ($this->configurator->isPathMatchMonitor($path)) { |
||
368 | if (is_readable($path)) { |
||
369 | if ($descriptor = $this->inotify->add($path, $mask)) { |
||
370 | $this->descriptors[$path] = $descriptor; |
||
371 | $this->fireCreated($path); |
||
372 | } |
||
373 | } |
||
374 | } |
||
375 | } |
||
376 | |||
377 | /** |
||
378 | * add the passed path to watched list and if the path match a pattern |
||
379 | * configured then fire the created event |
||
380 | * |
||
381 | * @param string $path |
||
382 | * @param int $mask |
||
383 | * |
||
384 | * @return void |
||
385 | */ |
||
386 | protected function addAndConditionallyFire($path, $mask): void |
||
387 | { |
||
388 | if (is_readable($path)) { |
||
389 | if ($descriptor = $this->inotify->add($path, $mask)) { |
||
390 | $this->descriptors[$path] = $descriptor; |
||
391 | if ($this->configurator->isPathMatchMonitor($path)) { |
||
392 | $this->fireCreated($path); |
||
393 | } |
||
394 | } |
||
395 | } |
||
396 | } |
||
397 | |||
398 | /** |
||
399 | * Remove file/directory from the monitored list. |
||
400 | * |
||
401 | * if $path has a trailing slash then it is a directory, else a file. |
||
402 | * When removing a directory, all items below it are removed from the list. |
||
403 | * |
||
404 | * @param string $path |
||
405 | * @param bool $skip_fire set to true to not fire the deleted event |
||
406 | * |
||
407 | * @return void |
||
408 | */ |
||
409 | protected function remove($path, $skip_fire = false): void |
||
410 | { |
||
411 | if (isset($this->descriptors[$path])) { |
||
412 | if ($this->hasTrailingSlash($path)) { |
||
413 | /** |
||
414 | * If a directory is removed recursively, the inner items are not |
||
415 | * triggered the delete, so here we will remove watches for them |
||
416 | * starting from the deepest. |
||
417 | */ |
||
418 | $inner_paths = []; |
||
419 | foreach (array_keys($this->descriptors) as $item_path) { |
||
420 | if (strpos($item_path, $path) === 0 && $item_path !== $path) { |
||
421 | $inner_paths[$item_path] = substr_count($item_path, '/'); |
||
422 | } |
||
423 | } |
||
424 | if ($inner_paths) { |
||
0 ignored issues
–
show
The expression
$inner_paths of type array 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 ![]() |
|||
425 | arsort($inner_paths, SORT_NUMERIC); |
||
426 | foreach (array_keys($inner_paths) as $inner_path) { |
||
427 | $this->remove($inner_path, $skip_fire); |
||
428 | } |
||
429 | } |
||
430 | } |
||
431 | |||
432 | @$this->inotify->remove($this->descriptors[$path]); |
||
0 ignored issues
–
show
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.
If you suppress an error, we recommend checking for the error condition explicitly: // For example instead of
@mkdir($dir);
// Better use
if (@mkdir($dir) === false) {
throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
![]() |
|||
433 | unset($this->descriptors[$path]); |
||
434 | |||
435 | if (!$skip_fire && $this->configurator->isPathMatchMonitor($path)) { |
||
436 | $this->fireDeleted($path); |
||
437 | } |
||
438 | } |
||
439 | } |
||
440 | |||
441 | /** |
||
442 | * Run the event loop, this will block. |
||
443 | * |
||
444 | * If the event loop is passed externally to the constructor, then a user warning is triggered. |
||
445 | */ |
||
446 | public function run() |
||
447 | { |
||
448 | if ($this->external_event_loop !== null) { |
||
449 | trigger_error( |
||
450 | 'calling run on Monitor external loop inside the monitor, this is mostly in-correct as the external loop must be run from the caller.', |
||
451 | E_USER_WARNING |
||
452 | ); |
||
453 | } |
||
454 | return $this->getEventLoop()->run(); |
||
455 | } |
||
456 | |||
457 | /** |
||
458 | * Stop the monitor using the traditional way of removing the listeners. |
||
459 | * |
||
460 | * @return void |
||
461 | */ |
||
462 | public function stop(): void |
||
463 | { |
||
464 | if ($this->is_inotify_event_listener_attached) { |
||
465 | $this->setupListeners(true); |
||
466 | } |
||
467 | |||
468 | $this->remove($this->configurator->getBaseDirectoryWithTrailingSlash(), true); |
||
469 | if ($this->base_dir_monitor instanceof Monitor) { |
||
470 | $this->base_dir_monitor->stop(); |
||
471 | } |
||
472 | } |
||
473 | |||
474 | /** |
||
475 | * Stop the monitor using the traditional way and call the stop on the event loop. |
||
476 | * |
||
477 | * @return void |
||
478 | */ |
||
479 | public function stopAll(): void |
||
480 | { |
||
481 | $this->stop(); |
||
482 | $this->getEventLoop()->stop(); |
||
483 | } |
||
484 | |||
485 | /** |
||
486 | * Stop the monitor using a quick call to inotify close. |
||
487 | * |
||
488 | * This should be used carefully, this is not the correct way of stopping the monitor. |
||
489 | * |
||
490 | * If called too many times, the following warning may be generated: |
||
491 | * |
||
492 | * PHP Warning: inotify_add_watch(): The user limit on the total number of inotify |
||
493 | * watches was reached or the kernel failed to allocate a needed resource in |
||
494 | * vendor/mkraemer/react-inotify/src/MKraemer/ReactInotify/Inotify.php on line 77 |
||
495 | * |
||
496 | * @return void |
||
497 | */ |
||
498 | public function stopQuick(): void |
||
499 | { |
||
500 | $this->inotify->close(); |
||
501 | $this->descriptors = []; |
||
502 | if ($this->base_dir_monitor instanceof Monitor) { |
||
503 | $this->base_dir_monitor->stopQuick(); |
||
504 | } |
||
505 | } |
||
506 | |||
507 | /** |
||
508 | * Perform a quick stop and stop the event loop also. |
||
509 | * |
||
510 | */ |
||
511 | public function stopQuickAll(): void |
||
512 | { |
||
513 | $this->stopQuick(); |
||
514 | $this->getEventLoop()->stop(); |
||
515 | } |
||
516 | } |
||
517 |