Issues (1107)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

lib/elFinderVolumeLocalFileSystem.class.php (6 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
// Implement similar functionality in PHP 5.2 or 5.3
4
// http://php.net/manual/class.recursivecallbackfilteriterator.php#110974
5
if (! class_exists('RecursiveCallbackFilterIterator', false)) {
6
    class RecursiveCallbackFilterIterator extends RecursiveFilterIterator
7
    {
8
        public function __construct(RecursiveIterator $iterator, $callback)
9
        {
10
            $this->callback = $callback;
11
            parent::__construct($iterator);
12
        }
13
14
        public function accept()
15
        {
16
            return call_user_func($this->callback, parent::current(), parent::key(), parent::getInnerIterator());
17
        }
18
19
        public function getChildren()
20
        {
21
            return new self($this->getInnerIterator()->getChildren(), $this->callback);
22
        }
23
    }
24
}
25
26
/**
27
 * elFinder driver for local filesystem.
28
 *
29
 * @author Dmitry (dio) Levashov
30
 * @author Troex Nevelin
31
 **/
32
class elFinderVolumeLocalFileSystem extends elFinderVolumeDriver
33
{
34
    /**
35
     * Driver id
36
     * Must be started from letter and contains [a-z0-9]
37
     * Used as part of volume id.
38
     *
39
     * @var string
40
     **/
41
    protected $driverId = 'l';
42
43
    /**
44
     * Required to count total archive files size.
45
     *
46
     * @var int
47
     **/
48
    protected $archiveSize = 0;
49
50
    /**
51
     * Constructor
52
     * Extend options with required fields.
53
     *
54
     * @author Dmitry (dio) Levashov
55
     */
56
    public function __construct()
57
    {
58
        $this->options['alias'] = '';              // alias to replace root dir name
59
        $this->options['dirMode'] = 0755;            // new dirs mode
60
        $this->options['fileMode'] = 0644;            // new files mode
61
        $this->options['quarantine'] = '.quarantine'; // quarantine folder name - required to check archive (must be hidden)
62
        $this->options['rootCssClass'] = 'elfinder-navbar-root-local';
63
        $this->options['followSymLinks'] = true;
64
        $this->options['detectDirIcon'] = '';         // file name that is detected as a folder icon e.g. '.diricon.png'
65
        $this->options['keepTimestamp'] = ['copy', 'move']; // keep timestamp at inner filesystem allowed 'copy', 'move' and 'upload'
66
    }
67
68
    /**
69
     * Long pooling sync checker
70
     * This function require server command `inotifywait`
71
     * If `inotifywait` need full path, Please add `define('ELFINER_INOTIFYWAIT_PATH', '/PATH_TO/inotifywait');` into connector.php.
72
     *
73
     * @param string     $path
74
     * @param int        $standby
75
     * @param number     $compare
76
     * @return number|bool
77
     */
78
    public function localFileSystemInotify($path, $standby, $compare)
79
    {
80
        if (isset($this->sessionCache['localFileSystemInotify_disable'])) {
81
            return false;
82
        }
83
        $path = realpath($path);
84
        $mtime = filemtime($path);
85
        if (! $mtime) {
86
            return false;
87
        }
88
        if ($mtime != $compare) {
89
            return $mtime;
90
        }
91
        $inotifywait = defined('ELFINER_INOTIFYWAIT_PATH') ? ELFINER_INOTIFYWAIT_PATH : 'inotifywait';
92
        $standby = max(1, intval($standby));
93
        $cmd = $inotifywait.' '.escapeshellarg($path).' -t '.$standby.' -e moved_to,moved_from,move,close_write,delete,delete_self';
94
        $this->procExec($cmd, $o, $r);
95
        if ($r === 0) {
96
            // changed
97
            clearstatcache();
98
            if (file_exists($path)) {
99
                $mtime = filemtime($path); // error on busy?
100
                return $mtime ? $mtime : time();
101
            } else {
102
                // target was removed
103
                return 0;
104
            }
105
        } elseif ($r === 2) {
106
            // not changed (timeout)
107
            return $compare;
108
        }
109
        // error
110
        // cache to $_SESSION
111
        $this->sessionCache['localFileSystemInotify_disable'] = true;
112
        $this->session->set($this->id, $this->sessionCache, true);
113
114
        return false;
115
    }
116
117
    /******************** Original local functions ************************
118
     * @param $file
119
     * @param $key
120
     * @param $iterator
121
     * @return bool
122
     */
123
124
    public function localFileSystemSearchIteratorFilter($file, $key, $iterator)
125
    {
126
        $name = $file->getFilename();
127 View Code Duplication
        if ($this->doSearchCurrentQuery['excludes']) {
128
            foreach ($this->doSearchCurrentQuery['excludes'] as $exclude) {
129
                if ($this->stripos($name, $exclude) !== false) {
130
                    return false;
131
                }
132
            }
133
        }
134
        if ($iterator->hasChildren()) {
135 View Code Duplication
            if ($this->options['searchExDirReg'] && preg_match($this->options['searchExDirReg'], $key)) {
136
                return false;
137
            }
138
139
            return (bool) $this->attr($key, 'read', null, true);
140
        }
141
142
        return ($this->stripos($name, $this->doSearchCurrentQuery['q']) === false) ? false : true;
143
    }
144
145
    /*********************************************************************/
146
    /*                        INIT AND CONFIGURE                         */
147
    /*********************************************************************/
148
149
    /**
150
     * Prepare driver before mount volume.
151
     * Return true if volume is ready.
152
     *
153
     * @return bool
154
     **/
155
    protected function init()
156
    {
157
        // Normalize directory separator for windows
158
        if (DIRECTORY_SEPARATOR !== '/') {
159
            foreach (['path', 'tmbPath', 'tmpPath', 'quarantine'] as $key) {
160
                if (! empty($this->options[$key])) {
161
                    $this->options[$key] = str_replace('/', DIRECTORY_SEPARATOR, $this->options[$key]);
162
                }
163
            }
164
        }
165
        if (! $cwd = getcwd()) {
166
            return $this->setError('elFinder LocalVolumeDriver requires a result of getcwd().');
167
        }
168
        // detect systemRoot
169
        if (! isset($this->options['systemRoot'])) {
170
            if ($cwd[0] === $this->separator || $this->root[0] === $this->separator) {
171
                $this->systemRoot = $this->separator;
172
            } elseif (preg_match('/^([a-zA-Z]:'.preg_quote($this->separator, '/').')/', $this->root, $m)) {
173
                $this->systemRoot = $m[1];
174
            } elseif (preg_match('/^([a-zA-Z]:'.preg_quote($this->separator, '/').')/', $cwd, $m)) {
175
                $this->systemRoot = $m[1];
176
            }
177
        }
178
        $this->root = $this->getFullPath($this->root, $cwd);
179
        if (! empty($this->options['startPath'])) {
180
            $this->options['startPath'] = $this->getFullPath($this->options['startPath'], $this->root);
181
        }
182
183
        if (is_null($this->options['syncChkAsTs'])) {
184
            $this->options['syncChkAsTs'] = true;
185
        }
186
        if (is_null($this->options['syncCheckFunc'])) {
187
            $this->options['syncCheckFunc'] = [$this, 'localFileSystemInotify'];
188
        }
189
190
        return true;
191
    }
192
193
    /**
194
     * Configure after successfull mount.
195
     *
196
     * @return void
197
     * @author Dmitry (dio) Levashov
198
     **/
199
    protected function configure()
200
    {
201
        $root = $this->stat($this->root);
202
203
        // chek thumbnails path
204
        if ($this->options['tmbPath']) {
205
            $this->options['tmbPath'] = strpos($this->options['tmbPath'], DIRECTORY_SEPARATOR) === false
206
                // tmb path set as dirname under root dir
207
                ? $this->_abspath($this->options['tmbPath'])
208
                // tmb path as full path
209
                : $this->_normpath($this->options['tmbPath']);
210
        }
211
212
        parent::configure();
213
214
        // set $this->tmp by options['tmpPath']
215
        $this->tmp = '';
216 View Code Duplication
        if (! empty($this->options['tmpPath'])) {
0 ignored issues
show
This code seems to be duplicated across your project.

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.

Loading history...
217
            if ((is_dir($this->options['tmpPath']) || mkdir($this->options['tmpPath'], 0755, true)) && is_writable($this->options['tmpPath'])) {
218
                $this->tmp = $this->options['tmpPath'];
219
            }
220
        }
221
        if (! $this->tmp && ($tmp = elFinder::getStaticVar('commonTempPath'))) {
222
            $this->tmp = $tmp;
223
        }
224
225
        // if no thumbnails url - try detect it
226
        if ($root['read'] && ! $this->tmbURL && $this->URL) {
227
            if (strpos($this->tmbPath, $this->root) === 0) {
228
                $this->tmbURL = $this->URL.str_replace(DIRECTORY_SEPARATOR, '/', substr($this->tmbPath, strlen($this->root) + 1));
229
                if (preg_match('|[^/?&=]$|', $this->tmbURL)) {
230
                    $this->tmbURL .= '/';
231
                }
232
            }
233
        }
234
235
        // check quarantine dir
236
        $this->quarantine = '';
237
        if (! empty($this->options['quarantine'])) {
238
            if (is_dir($this->options['quarantine'])) {
239
                if (is_writable($this->options['quarantine'])) {
240
                    $this->quarantine = $this->options['quarantine'];
241
                }
242
                $this->options['quarantine'] = '';
243
            } else {
244
                $this->quarantine = $this->_abspath($this->options['quarantine']);
245
                if ((! is_dir($this->quarantine) && ! mkdir($this->quarantine)) || ! is_writable($this->quarantine)) {
246
                    $this->options['quarantine'] = $this->quarantine = '';
247
                }
248
            }
249
        }
250
251
        if (! $this->quarantine) {
252
            if (! $this->tmp) {
253
                $this->archivers['extract'] = [];
254
                $this->disabled[] = 'extract';
255
            } else {
256
                $this->quarantine = $this->tmp;
257
            }
258
        }
259
260
        if ($this->options['quarantine']) {
261
            $this->attributes[] = [
262
                    'pattern' => '~^'.preg_quote(DIRECTORY_SEPARATOR.$this->options['quarantine']).'$~',
263
                    'read' => false,
264
                    'write' => false,
265
                    'locked' => true,
266
                    'hidden' => true,
267
            ];
268
        }
269
270
        if (! empty($this->options['keepTimestamp'])) {
271
            $this->options['keepTimestamp'] = array_flip($this->options['keepTimestamp']);
272
        }
273
    }
274
275
    /*********************************************************************/
276
    /*                               FS API                              */
277
    /*********************************************************************/
278
279
    /*********************** paths/urls *************************/
280
281
    /**
282
     * Return parent directory path.
283
     *
284
     * @param  string  $path  file path
285
     * @return string
286
     * @author Dmitry (dio) Levashov
287
     **/
288
    protected function _dirname($path)
289
    {
290
        return dirname($path);
291
    }
292
293
    /**
294
     * Return file name.
295
     *
296
     * @param  string  $path  file path
297
     * @return string
298
     * @author Dmitry (dio) Levashov
299
     **/
300
    protected function _basename($path)
301
    {
302
        return basename($path);
303
    }
304
305
    /**
306
     * Join dir name and file name and retur full path.
307
     *
308
     * @param  string  $dir
309
     * @param  string  $name
310
     * @return string
311
     * @author Dmitry (dio) Levashov
312
     **/
313
    protected function _joinPath($dir, $name)
314
    {
315
        return rtrim($dir, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.$name;
316
    }
317
318
    /**
319
     * Return normalized path, this works the same as os.path.normpath() in Python.
320
     *
321
     * @param  string  $path  path
322
     * @return string
323
     * @author Troex Nevelin
324
     **/
325
    protected function _normpath($path)
326
    {
327
        if (empty($path)) {
328
            return '.';
329
        }
330
331
        $changeSep = (DIRECTORY_SEPARATOR !== '/');
332
        if ($changeSep) {
333
            $drive = '';
334
            if (preg_match('/^([a-zA-Z]:)(.*)/', $path, $m)) {
335
                $drive = $m[1];
336
                $path = $m[2] ? $m[2] : '/';
337
            }
338
            $path = str_replace(DIRECTORY_SEPARATOR, '/', $path);
339
        }
340
341
        if (strpos($path, '/') === 0) {
342
            $initial_slashes = true;
343
        } else {
344
            $initial_slashes = false;
345
        }
346
347 View Code Duplication
        if (($initial_slashes)
348
        && (strpos($path, '//') === 0)
349
        && (strpos($path, '///') === false)) {
350
            $initial_slashes = 2;
351
        }
352
353
        $initial_slashes = (int) $initial_slashes;
354
355
        $comps = explode('/', $path);
356
        $new_comps = [];
357 View Code Duplication
        foreach ($comps as $comp) {
358
            if (in_array($comp, ['', '.'])) {
359
                continue;
360
            }
361
362
            if (($comp != '..')
363
            || (! $initial_slashes && ! $new_comps)
0 ignored issues
show
Bug Best Practice introduced by
The expression $new_comps 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 empty(..) or ! empty(...) instead.

Loading history...
364
            || ($new_comps && (end($new_comps) == '..'))) {
365
                array_push($new_comps, $comp);
366
            } elseif ($new_comps) {
367
                array_pop($new_comps);
368
            }
369
        }
370
        $comps = $new_comps;
371
        $path = implode('/', $comps);
372
        if ($initial_slashes) {
373
            $path = str_repeat('/', $initial_slashes).$path;
374
        }
375
376
        if ($changeSep) {
377
            $path = $drive.str_replace('/', DIRECTORY_SEPARATOR, $path);
378
        }
379
380
        return $path ? $path : '.';
381
    }
382
383
    /**
384
     * Return file path related to root dir.
385
     *
386
     * @param  string  $path  file path
387
     * @return string
388
     * @author Dmitry (dio) Levashov
389
     **/
390 View Code Duplication
    protected function _relpath($path)
391
    {
392
        if ($path === $this->root) {
393
            return '';
394
        } else {
395
            if (strpos($path, $this->root) === 0) {
396
                return ltrim(substr($path, strlen($this->root)), DIRECTORY_SEPARATOR);
397
            } else {
398
                // for link
399
                return $path;
400
            }
401
        }
402
    }
403
404
    /**
405
     * Convert path related to root dir into real path.
406
     *
407
     * @param  string  $path  file path
408
     * @return string
409
     * @author Dmitry (dio) Levashov
410
     **/
411
    protected function _abspath($path)
412
    {
413
        if ($path === DIRECTORY_SEPARATOR) {
414
            return $this->root;
415
        } else {
416
            if ($path[0] === DIRECTORY_SEPARATOR) {
417
                // for link
418
                return $path;
419
            } else {
420
                return $this->_joinPath($this->root, $path);
421
            }
422
        }
423
    }
424
425
    /**
426
     * Return fake path started from root dir.
427
     *
428
     * @param  string  $path  file path
429
     * @return string
430
     * @author Dmitry (dio) Levashov
431
     **/
432
    protected function _path($path)
433
    {
434
        return $this->rootName.($path == $this->root ? '' : $this->separator.$this->_relpath($path));
435
    }
436
437
    /**
438
     * Return true if $path is children of $parent.
439
     *
440
     * @param  string  $path    path to check
441
     * @param  string  $parent  parent path
442
     * @return bool
443
     * @author Dmitry (dio) Levashov
444
     **/
445
    protected function _inpath($path, $parent)
446
    {
447
        $cwd = getcwd();
448
        $real_path = $this->getFullPath($path, $cwd);
449
        $real_parent = $this->getFullPath($parent, $cwd);
450
        if ($real_path && $real_parent) {
451
            return $real_path === $real_parent || strpos($real_path, rtrim($real_parent, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR) === 0;
452
        }
453
454
        return false;
455
    }
456
457
    /***************** file stat ********************/
458
459
    /**
460
     * Return stat for given path.
461
     * Stat contains following fields:
462
     * - (int)    size    file size in b. required
463
     * - (int)    ts      file modification time in unix time. required
464
     * - (string) mime    mimetype. required for folders, others - optionally
465
     * - (bool)   read    read permissions. required
466
     * - (bool)   write   write permissions. required
467
     * - (bool)   locked  is object locked. optionally
468
     * - (bool)   hidden  is object hidden. optionally
469
     * - (string) alias   for symlinks - link target path relative to root path. optionally
470
     * - (string) target  for symlinks - link target path. optionally.
471
     *
472
     * If file does not exists - returns empty array or false.
473
     *
474
     * @param  string  $path    file path
475
     * @return array|false
476
     * @author Dmitry (dio) Levashov
477
     **/
478
    protected function _stat($path)
479
    {
480
        static $statOwner;
481
        if (is_null($statOwner)) {
482
            $statOwner = (! empty($this->options['statOwner']));
483
        }
484
485
        $stat = [];
486
487
        if (! file_exists($path) && ! is_link($path)) {
488
            return $stat;
489
        }
490
491
        //Verifies the given path is the root or is inside the root. Prevents directory traveral.
492
        if (! $this->_inpath($path, $this->root)) {
493
            return $stat;
494
        }
495
496
        $gid = $uid = 0;
497
        $stat['isowner'] = false;
498
        $linkreadable = false;
499
        if ($path != $this->root && is_link($path)) {
500
            if (! $this->options['followSymLinks']) {
501
                return [];
502
            }
503
            if (! ($target = $this->readlink($path))
504
            || $target == $path) {
505
                if (is_null($target)) {
506
                    $stat = [];
507
508
                    return $stat;
509
                } else {
510
                    $stat['mime'] = 'symlink-broken';
511
                    $target = readlink($path);
512
                    $lstat = lstat($path);
513
                    $ostat = $this->getOwnerStat($lstat['uid'], $lstat['gid']);
514
                    $linkreadable = ! empty($ostat['isowner']);
515
                }
516
            }
517
            $stat['alias'] = $this->_path($target);
518
            $stat['target'] = $target;
519
        }
520
        $size = sprintf('%u', filesize($path));
521
        $stat['ts'] = filemtime($path);
522
        if ($statOwner) {
523
            $fstat = stat($path);
524
            $uid = $fstat['uid'];
525
            $gid = $fstat['gid'];
526
            $stat['perm'] = substr((string) decoct($fstat['mode']), -4);
527
            $stat = array_merge($stat, $this->getOwnerStat($uid, $gid));
528
        }
529
530
        if (($dir = is_dir($path)) && $this->options['detectDirIcon']) {
531
            $favicon = $path.DIRECTORY_SEPARATOR.$this->options['detectDirIcon'];
532 View Code Duplication
            if ($this->URL && file_exists($favicon)) {
533
                $stat['icon'] = $this->URL.str_replace(DIRECTORY_SEPARATOR, '/', substr($favicon, strlen($this->root) + 1));
534
            }
535
        }
536
537
        if (! isset($stat['mime'])) {
538
            $stat['mime'] = $dir ? 'directory' : $this->mimetype($path);
539
        }
540
        //logical rights first
541
        $stat['read'] = ($linkreadable || is_readable($path)) ? null : false;
542
        $stat['write'] = is_writable($path) ? null : false;
543
544
        if (is_null($stat['read'])) {
545
            $stat['size'] = $dir ? 0 : $size;
546
        }
547
548
        return $stat;
549
    }
550
551
    /**
552
     * Get stat `owner`, `group` and `isowner` by `uid` and `gid`
553
     * Sub-fuction of _stat() and _scandir().
554
     *
555
     * @param int $uid
556
     * @param int $gid
557
     * @return array  stat
558
     */
559
    protected function getOwnerStat($uid, $gid)
560
    {
561
        static $names = null;
562
        static $phpuid = null;
563
564
        if (is_null($names)) {
565
            $names = ['uid' => [], 'gid' => []];
566
        }
567
        if (is_null($phpuid)) {
568
            if (is_callable('posix_getuid')) {
569
                $phpuid = posix_getuid();
570
            } else {
571
                $phpuid = 0;
572
            }
573
        }
574
575
        $stat = [];
576
577
        if ($uid) {
578
            $stat['isowner'] = ($phpuid == $uid);
579 View Code Duplication
            if (isset($names['uid'][$uid])) {
580
                $stat['owner'] = $names['uid'][$uid];
581
            } elseif (is_callable('posix_getpwuid')) {
582
                $pwuid = posix_getpwuid($uid);
583
                $stat['owner'] = $names['uid'][$uid] = $pwuid['name'];
584
            } else {
585
                $stat['owner'] = $names['uid'][$uid] = $uid;
586
            }
587
        }
588 View Code Duplication
        if ($gid) {
589
            if (isset($names['gid'][$gid])) {
590
                $stat['group'] = $names['gid'][$gid];
591
            } elseif (is_callable('posix_getgrgid')) {
592
                $grgid = posix_getgrgid($gid);
593
                $stat['group'] = $names['gid'][$gid] = $grgid['name'];
594
            } else {
595
                $stat['group'] = $names['gid'][$gid] = $gid;
596
            }
597
        }
598
599
        return $stat;
600
    }
601
602
    /**
603
     * Return true if path is dir and has at least one childs directory.
604
     *
605
     * @param  string  $path  dir path
606
     * @return bool
607
     * @author Dmitry (dio) Levashov
608
     **/
609
    protected function _subdirs($path)
610
    {
611
        $dirs = false;
612
        if (is_dir($path) && is_readable($path)) {
613
            if (class_exists('FilesystemIterator', false)) {
614
                $dirItr = new ParentIterator(
615
                    new RecursiveDirectoryIterator($path,
616
                        FilesystemIterator::SKIP_DOTS |
617
                        (defined('RecursiveDirectoryIterator::FOLLOW_SYMLINKS') ?
618
                            RecursiveDirectoryIterator::FOLLOW_SYMLINKS : 0)
619
                    )
620
                );
621
                $dirItr->rewind();
622
                if ($dirItr->hasChildren()) {
623
                    $dirs = true;
624
                    $name = $dirItr->getSubPathName();
625
                    while ($name) {
626
                        if (! $this->attr($path.DIRECTORY_SEPARATOR.$name, 'read', null, true)) {
627
                            $dirs = false;
628
                            $dirItr->next();
629
                            $name = $dirItr->getSubPathName();
630
                            continue;
631
                        }
632
                        $dirs = true;
633
                        break;
634
                    }
635
                }
636
            } else {
637
                $path = strtr($path, ['[' => '\\[', ']' => '\\]', '*' => '\\*', '?' => '\\?']);
638
639
                return (bool) glob(rtrim($path, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.'*', GLOB_ONLYDIR);
640
            }
641
        }
642
643
        return $dirs;
644
    }
645
646
    /**
647
     * Return object width and height
648
     * Usualy used for images, but can be realize for video etc...
649
     *
650
     * @param  string  $path  file path
651
     * @param  string  $mime  file mime type
652
     * @return string
653
     * @author Dmitry (dio) Levashov
654
     **/
655
    protected function _dimensions($path, $mime)
656
    {
657
        clearstatcache();
658
659
        return strpos($mime, 'image') === 0 && is_readable($path) && ($s = getimagesize($path)) !== false
660
            ? $s[0].'x'.$s[1]
661
            : false;
662
    }
663
664
    /******************** file/dir content *********************/
665
666
    /**
667
     * Return symlink target file.
668
     *
669
     * @param  string  $path  link path
670
     * @return string
671
     * @author Dmitry (dio) Levashov
672
     **/
673
    protected function readlink($path)
674
    {
675
        if (! ($target = readlink($path))) {
676
            return;
677
        }
678
679
        if (strpos($target, $this->systemRoot) !== 0) {
680
            $target = $this->_joinPath(dirname($path), $target);
681
        }
682
683
        if (! file_exists($target)) {
684
            return false;
685
        }
686
687
        return $target;
688
    }
689
690
    /**
691
     * Return files list in directory.
692
     *
693
     * @param  string  $path  dir path
694
     * @return array
695
     * @author Dmitry (dio) Levashov
696
     **/
697
    protected function _scandir($path)
698
    {
699
        elFinder::extendTimeLimit();
700
        $files = [];
701
        $cache = [];
702
        $dirWritable = is_writable($path);
703
        $statOwner = (! empty($this->options['statOwner']));
704
        $dirItr = [];
705
        $followSymLinks = $this->options['followSymLinks'];
706
        try {
707
            $dirItr = new DirectoryIterator($path);
708
        } catch (UnexpectedValueException $e) {
709
        }
710
711
        foreach ($dirItr as $file) {
712
            try {
713
                if ($file->isDot()) {
714
                    continue;
715
                }
716
717
                $files[] = $fpath = $file->getPathname();
718
719
                $br = false;
720
                $stat = [];
721
722
                $gid = $uid = 0;
723
                $stat['isowner'] = false;
724
                $linkreadable = false;
725
                if ($file->isLink()) {
726
                    if (! $followSymLinks) {
727
                        continue;
728
                    }
729
                    if (! ($target = $this->readlink($fpath))
730
                    || $target == $fpath) {
731
                        if (is_null($target)) {
732
                            $stat = [];
733
                            $br = true;
734
                        } else {
735
                            $_path = $fpath;
736
                            $stat['mime'] = 'symlink-broken';
737
                            $target = readlink($_path);
738
                            $lstat = lstat($_path);
739
                            $ostat = $this->getOwnerStat($lstat['uid'], $lstat['gid']);
740
                            $linkreadable = ! empty($ostat['isowner']);
741
                            $dir = false;
742
                            $stat['alias'] = $this->_path($target);
743
                            $stat['target'] = $target;
744
                        }
745
                    } else {
746
                        $dir = is_dir($target);
747
                        $stat['alias'] = $this->_path($target);
748
                        $stat['target'] = $target;
749
                        $stat['mime'] = $dir ? 'directory' : $this->mimetype($stat['alias']);
750
                    }
751
                } else {
752
                    if (($dir = $file->isDir()) && $this->options['detectDirIcon']) {
753
                        $path = $file->getPathname();
754
                        $favicon = $path.DIRECTORY_SEPARATOR.$this->options['detectDirIcon'];
755 View Code Duplication
                        if ($this->URL && file_exists($favicon)) {
756
                            $stat['icon'] = $this->URL.str_replace(DIRECTORY_SEPARATOR, '/', substr($favicon, strlen($this->root) + 1));
757
                        }
758
                    }
759
                    $stat['mime'] = $dir ? 'directory' : $this->mimetype($fpath);
760
                }
761
                $size = sprintf('%u', $file->getSize());
762
                $stat['ts'] = $file->getMTime();
763
                if (! $br) {
764
                    if ($statOwner && ! $linkreadable) {
765
                        $uid = $file->getOwner();
766
                        $gid = $file->getGroup();
767
                        $stat['perm'] = substr((string) decoct($file->getPerms()), -4);
768
                        $stat = array_merge($stat, $this->getOwnerStat($uid, $gid));
769
                    }
770
771
                    //logical rights first
772
                    $stat['read'] = ($linkreadable || $file->isReadable()) ? null : false;
773
                    $stat['write'] = $file->isWritable() ? null : false;
774
                    $stat['locked'] = $dirWritable ? null : true;
775
776
                    if (is_null($stat['read'])) {
777
                        $stat['size'] = $dir ? 0 : $size;
778
                    }
779
                }
780
781
                $cache[] = [$fpath, $stat];
782
            } catch (RuntimeException $e) {
783
                continue;
784
            }
785
        }
786
787
        if ($cache) {
788
            $cache = $this->convEncOut($cache, false);
789
            foreach ($cache as $d) {
790
                $this->updateCache($d[0], $d[1]);
791
            }
792
        }
793
794
        return $files;
795
    }
796
797
    /**
798
     * Open file and return file pointer.
799
     *
800
     * @param  string $path file path
801
     * @param string $mode
802
     * @return false|resource
803
     * @internal param bool $write open file for writing
804
     * @author Dmitry (dio) Levashov
805
     */
806
    protected function _fopen($path, $mode = 'rb')
807
    {
808
        return fopen($path, $mode);
809
    }
810
811
    /**
812
     * Close opened file.
813
     *
814
     * @param  resource $fp file pointer
815
     * @param string $path
816
     * @return bool
817
     * @author Dmitry (dio) Levashov
818
     */
819
    protected function _fclose($fp, $path = '')
820
    {
821
        return is_resource($fp) && fclose($fp);
822
    }
823
824
    /********************  file/dir manipulations *************************/
825
826
    /**
827
     * Create dir and return created dir path or false on failed.
828
     *
829
     * @param  string  $path  parent dir path
830
     * @param string  $name  new directory name
831
     * @return string|bool
832
     * @author Dmitry (dio) Levashov
833
     **/
834 View Code Duplication
    protected function _mkdir($path, $name)
835
    {
836
        $path = $this->_joinPath($path, $name);
837
838
        if (mkdir($path)) {
839
            chmod($path, $this->options['dirMode']);
840
            clearstatcache();
841
842
            return $path;
843
        }
844
845
        return false;
846
    }
847
848
    /**
849
     * Create file and return it's path or false on failed.
850
     *
851
     * @param  string  $path  parent dir path
852
     * @param string  $name  new file name
853
     * @return string|bool
854
     * @author Dmitry (dio) Levashov
855
     **/
856 View Code Duplication
    protected function _mkfile($path, $name)
857
    {
858
        $path = $this->_joinPath($path, $name);
859
860
        if (($fp = fopen($path, 'w'))) {
861
            fclose($fp);
862
            chmod($path, $this->options['fileMode']);
863
            clearstatcache();
864
865
            return $path;
866
        }
867
868
        return false;
869
    }
870
871
    /**
872
     * Create symlink.
873
     *
874
     * @param  string  $source     file to link to
875
     * @param  string  $targetDir  folder to create link in
876
     * @param  string  $name       symlink name
877
     * @return bool
878
     * @author Dmitry (dio) Levashov
879
     **/
880
    protected function _symlink($source, $targetDir, $name)
881
    {
882
        return symlink($source, $this->_joinPath($targetDir, $name));
883
    }
884
885
    /**
886
     * Copy file into another file.
887
     *
888
     * @param  string  $source     source file path
889
     * @param  string  $targetDir  target directory path
890
     * @param  string  $name       new file name
891
     * @return bool
892
     * @author Dmitry (dio) Levashov
893
     **/
894 View Code Duplication
    protected function _copy($source, $targetDir, $name)
895
    {
896
        $mtime = filemtime($source);
897
        $target = $this->_joinPath($targetDir, $name);
898
        if ($ret = copy($source, $target)) {
899
            isset($this->options['keepTimestamp']['copy']) && $mtime && touch($target, $mtime);
900
            clearstatcache();
901
        }
902
903
        return $ret;
904
    }
905
906
    /**
907
     * Move file into another parent dir.
908
     * Return new file path or false.
909
     *
910
     * @param  string $source source file path
911
     * @param $targetDir
912
     * @param  string $name file name
913
     * @return bool|string
914
     * @internal param string $target target dir path
915
     * @author Dmitry (dio) Levashov
916
     */
917 View Code Duplication
    protected function _move($source, $targetDir, $name)
918
    {
919
        $mtime = filemtime($source);
920
        $target = $this->_joinPath($targetDir, $name);
921
        if ($ret = rename($source, $target) ? $target : false) {
922
            isset($this->options['keepTimestamp']['move']) && $mtime && touch($target, $mtime);
923
            clearstatcache();
924
        }
925
926
        return $ret;
927
    }
928
929
    /**
930
     * Remove file.
931
     *
932
     * @param  string  $path  file path
933
     * @return bool
934
     * @author Dmitry (dio) Levashov
935
     **/
936
    protected function _unlink($path)
937
    {
938
        $ret = unlink($path);
939
        $ret && clearstatcache();
940
941
        return $ret;
942
    }
943
944
    /**
945
     * Remove dir.
946
     *
947
     * @param  string  $path  dir path
948
     * @return bool
949
     * @author Dmitry (dio) Levashov
950
     **/
951
    protected function _rmdir($path)
952
    {
953
        $ret = rmdir($path);
954
        $ret && clearstatcache();
955
956
        return $ret;
957
    }
958
959
    /**
960
     * Create new file and write into it from file pointer.
961
     * Return new file path or false on error.
962
     *
963
     * @param  resource  $fp   file pointer
964
     * @param  string    $dir  target dir path
965
     * @param  string    $name file name
966
     * @param  array     $stat file stat (required by some virtual fs)
967
     * @return bool|string
968
     * @author Dmitry (dio) Levashov
969
     **/
970
    protected function _save($fp, $dir, $name, $stat)
971
    {
972
        $path = $this->_joinPath($dir, $name);
973
974
        $meta = stream_get_meta_data($fp);
975
        $uri = isset($meta['uri']) ? $meta['uri'] : '';
976
        if ($uri && ! preg_match('#^[a-zA-Z0-9]+://#', $uri)) {
977
            fclose($fp);
978
            $mtime = filemtime($uri);
979
            $isCmdPaste = ($this->ARGS['cmd'] === 'paste');
980
            $isCmdCopy = ($isCmdPaste && empty($this->ARGS['cut']));
981
            if (($isCmdCopy || ! rename($uri, $path)) && ! copy($uri, $path)) {
982
                return false;
983
            }
984
            // keep timestamp on upload
985
            if ($mtime && $this->ARGS['cmd'] === 'upload') {
986
                touch($path, isset($this->options['keepTimestamp']['upload']) ? $mtime : time());
987
            }
988
            // re-create the source file for remove processing of paste command
989
            $isCmdPaste && ! $isCmdCopy && touch($uri);
990
        } else {
991
            if (file_put_contents($path, $fp, LOCK_EX) === false) {
992
                return false;
993
            }
994
        }
995
996
        if (is_link($path)) {
997
            unlink($path);
998
999
            return $this->setError(elFinder::ERROR_SAVE, $name);
1000
        }
1001
1002
        chmod($path, $this->options['fileMode']);
1003
        clearstatcache();
1004
1005
        return $path;
1006
    }
1007
1008
    /**
1009
     * Get file contents.
1010
     *
1011
     * @param  string  $path  file path
1012
     * @return string|false
1013
     * @author Dmitry (dio) Levashov
1014
     **/
1015
    protected function _getContents($path)
1016
    {
1017
        return file_get_contents($path);
1018
    }
1019
1020
    /**
1021
     * Write a string to a file.
1022
     *
1023
     * @param  string  $path     file path
1024
     * @param  string  $content  new file content
1025
     * @return bool
1026
     * @author Dmitry (dio) Levashov
1027
     **/
1028
    protected function _filePutContents($path, $content)
1029
    {
1030
        if (file_put_contents($path, $content, LOCK_EX) !== false) {
1031
            clearstatcache();
1032
1033
            return true;
1034
        }
1035
1036
        return false;
1037
    }
1038
1039
    /**
1040
     * Detect available archivers.
1041
     *
1042
     * @return void
1043
     **/
1044
    protected function _checkArchivers()
1045
    {
1046
        $this->archivers = $this->getArchivers();
1047
    }
1048
1049
    /**
1050
     * chmod availability.
1051
     *
1052
     * @param string $path
1053
     * @param string $mode
1054
     * @return bool
1055
     */
1056
    protected function _chmod($path, $mode)
1057
    {
1058
        $modeOct = is_string($mode) ? octdec($mode) : octdec(sprintf('%04o', $mode));
1059
        $ret = chmod($path, $modeOct);
1060
        $ret && clearstatcache();
1061
1062
        return  $ret;
1063
    }
1064
1065
    /**
1066
     * Recursive symlinks search.
1067
     *
1068
     * @param  string  $path  file/dir path
1069
     * @return bool
1070
     * @author Dmitry (dio) Levashov
1071
     **/
1072
    protected function _findSymlinks($path)
1073
    {
1074
        return self::localFindSymlinks($path);
1075
    }
1076
1077
    /**
1078
     * Extract files from archive.
1079
     *
1080
     * @param  string  $path  archive path
1081
     * @param  array   $arc   archiver command and arguments (same as in $this->archivers)
1082
     * @return true
1083
     * @author Dmitry (dio) Levashov,
1084
     * @author Alexey Sukhotin
1085
     **/
1086
    protected function _extract($path, $arc)
1087
    {
1088
        if ($this->quarantine) {
1089
            $dir = $this->quarantine.DIRECTORY_SEPARATOR.md5(basename($path).mt_rand());
1090
            $archive = (isset($arc['toSpec']) || $arc['cmd'] === 'phpfunction') ? '' : $dir.DIRECTORY_SEPARATOR.basename($path);
1091
1092
            if (! mkdir($dir)) {
1093
                return false;
1094
            }
1095
1096
            // insurance unexpected shutdown
1097
            register_shutdown_function([$this, 'rmdirRecursive'], realpath($dir));
1098
1099
            chmod($dir, 0777);
1100
1101
            // copy in quarantine
1102
            if (! is_readable($path) || ($archive && ! copy($path, $archive))) {
1103
                return false;
1104
            }
1105
1106
            // extract in quarantine
1107
            $this->unpackArchive($path, $arc, $archive ? true : $dir);
1108
1109
            // get files list
1110
            $ls = self::localScandir($dir);
1111
1112
            // no files - extract error ?
1113
            if (empty($ls)) {
1114
                return false;
1115
            }
1116
1117
            $this->archiveSize = 0;
1118
1119
            // find symlinks and check extracted items
1120
            $checkRes = $this->checkExtractItems($dir);
1121 View Code Duplication
            if ($checkRes['symlinks']) {
1122
                self::localRmdirRecursive($dir);
1123
1124
                return $this->setError(array_merge($this->error, [elFinder::ERROR_ARC_SYMLINKS]));
1125
            }
1126
            $this->archiveSize = $checkRes['totalSize'];
1127
            if ($checkRes['rmNames']) {
1128
                foreach ($checkRes['rmNames'] as $name) {
1129
                    $this->addError(elFinder::ERROR_SAVE, $name);
1130
                }
1131
            }
1132
1133
            // check max files size
1134 View Code Duplication
            if ($this->options['maxArcFilesSize'] > 0 && $this->options['maxArcFilesSize'] < $this->archiveSize) {
1135
                $this->delTree($dir);
1136
1137
                return $this->setError(elFinder::ERROR_ARC_MAXSIZE);
1138
            }
1139
1140
            $extractTo = $this->extractToNewdir; // 'auto', ture or false
1141
1142
            // archive contains one item - extract in archive dir
1143
            $name = '';
1144
            $src = $dir.DIRECTORY_SEPARATOR.$ls[0];
1145
            if (($extractTo === 'auto' || ! $extractTo) && count($ls) === 1 && is_file($src)) {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $extractTo (integer) and 'auto' (string) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
1146
                $name = $ls[0];
1147
            } elseif ($extractTo === 'auto' || $extractTo) {
1148
                // for several files - create new directory
1149
                // create unique name for directory
1150
                $src = $dir;
1151
                $name = basename($path);
1152 View Code Duplication
                if (preg_match('/\.((tar\.(gz|bz|bz2|z|lzo))|cpio\.gz|ps\.gz|xcf\.(gz|bz2)|[a-z0-9]{1,4})$/i', $name, $m)) {
1153
                    $name = substr($name, 0, strlen($name) - strlen($m[0]));
1154
                }
1155
                $test = dirname($path).DIRECTORY_SEPARATOR.$name;
1156
                if (file_exists($test) || is_link($test)) {
1157
                    $name = $this->uniqueName(dirname($path), $name, '-', false);
1158
                }
1159
            }
1160
1161
            if ($name !== '') {
1162
                $result = dirname($path).DIRECTORY_SEPARATOR.$name;
1163
1164
                if (! rename($src, $result)) {
1165
                    $this->delTree($dir);
1166
1167
                    return false;
1168
                }
1169
            } else {
1170
                $dstDir = dirname($path);
1171
                $result = [];
1172
                foreach ($ls as $name) {
1173
                    $target = $dstDir.DIRECTORY_SEPARATOR.$name;
1174
                    if (self::localMoveRecursive($dir.DIRECTORY_SEPARATOR.$name, $target, true, $this->options['copyJoin'])) {
1175
                        $result[] = $target;
1176
                    }
1177
                }
1178
                if (! $result) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $result 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 empty(..) or ! empty(...) instead.

Loading history...
1179
                    $this->delTree($dir);
1180
1181
                    return false;
1182
                }
1183
            }
1184
1185
            is_dir($dir) && $this->delTree($dir);
1186
1187
            return (is_array($result) || file_exists($result)) ? $result : false;
1188
        }
1189
        //TODO: Add return statement here
1190
    }
1191
1192
    /**
1193
     * Create archive and return its path.
1194
     *
1195
     * @param  string  $dir    target dir
1196
     * @param  array   $files  files names list
1197
     * @param  string  $name   archive name
1198
     * @param  array   $arc    archiver options
1199
     * @return string|bool
1200
     * @author Dmitry (dio) Levashov,
1201
     * @author Alexey Sukhotin
1202
     **/
1203
    protected function _archive($dir, $files, $name, $arc)
1204
    {
1205
        return $this->makeArchive($dir, $files, $name, $arc);
1206
    }
1207
1208
    /******************** Over write functions *************************/
1209
1210
    /**
1211
     * File path of local server side work file path.
1212
     *
1213
     * @param  string $path
1214
     * @return string
1215
     * @author Naoki Sawada
1216
     */
1217
    protected function getWorkFile($path)
1218
    {
1219
        return $path;
1220
    }
1221
1222
    /**
1223
     * Delete dirctory trees.
1224
     *
1225
     * @param string $localpath path need convert encoding to server encoding
1226
     * @return bool
1227
     * @author Naoki Sawada
1228
     */
1229
    protected function delTree($localpath)
1230
    {
1231
        return $this->rmdirRecursive($localpath);
1232
    }
1233
1234
    /******************** Over write (Optimized) functions *************************/
1235
1236
    /**
1237
     * Recursive files search.
1238
     *
1239
     * @param  string  $path   dir path
1240
     * @param  string  $q      search string
1241
     * @param  array   $mimes
1242
     * @return array
1243
     * @author Dmitry (dio) Levashov
1244
     * @author Naoki Sawada
1245
     **/
1246
    protected function doSearch($path, $q, $mimes)
1247
    {
1248
        if ($this->encoding || ! class_exists('FilesystemIterator', false)) {
1249
            // non UTF-8 use elFinderVolumeDriver::doSearch()
1250
            return parent::doSearch($path, $q, $mimes);
1251
        }
1252
1253
        $result = [];
1254
1255
        $timeout = $this->options['searchTimeout'] ? $this->searchStart + $this->options['searchTimeout'] : 0;
1256 View Code Duplication
        if ($timeout && $timeout < time()) {
1257
            $this->setError(elFinder::ERROR_SEARCH_TIMEOUT, $this->path($this->encode($path)));
1258
1259
            return $result;
1260
        }
1261
        elFinder::extendTimeLimit($this->options['searchTimeout'] + 30);
1262
1263
        $match = [];
1264
        try {
1265
            $iterator = new RecursiveIteratorIterator(
1266
                new RecursiveCallbackFilterIterator(
1267
                    new RecursiveDirectoryIterator($path,
1268
                        FilesystemIterator::KEY_AS_PATHNAME |
1269
                        FilesystemIterator::SKIP_DOTS |
1270
                        ((defined('RecursiveDirectoryIterator::FOLLOW_SYMLINKS') && $this->options['followSymLinks']) ?
1271
                            RecursiveDirectoryIterator::FOLLOW_SYMLINKS : 0)
1272
                    ),
1273
                    [$this, 'localFileSystemSearchIteratorFilter']
1274
                ),
1275
                RecursiveIteratorIterator::SELF_FIRST,
1276
                RecursiveIteratorIterator::CATCH_GET_CHILD
1277
            );
1278
            foreach ($iterator as $key => $node) {
1279 View Code Duplication
                if ($timeout && ($this->error || $timeout < time())) {
1280
                    ! $this->error && $this->setError(elFinder::ERROR_SEARCH_TIMEOUT, $this->path($this->encode($node->getPath)));
1281
                    break;
1282
                }
1283
                if ($node->isDir()) {
1284
                    if ($this->stripos($node->getFilename(), $q) !== false) {
1285
                        $match[] = $key;
1286
                    }
1287
                } else {
1288
                    $match[] = $key;
1289
                }
1290
            }
1291
        } catch (Exception $e) {
1292
        }
1293
1294
        if ($match) {
1295
            foreach ($match as $p) {
1296 View Code Duplication
                if ($timeout && ($this->error || $timeout < time())) {
1297
                    ! $this->error && $this->setError(elFinder::ERROR_SEARCH_TIMEOUT, $this->path($this->encode(dirname($p))));
1298
                    break;
1299
                }
1300
1301
                $stat = $this->stat($p);
1302
1303
                if (! $stat) { // invalid links
0 ignored issues
show
Bug Best Practice introduced by
The expression $stat 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 empty(..) or ! empty(...) instead.

Loading history...
1304
                    continue;
1305
                }
1306
1307
                if (! empty($stat['hidden']) || ! $this->mimeAccepted($stat['mime'], $mimes)) {
1308
                    continue;
1309
                }
1310
1311
                $name = $stat['name'];
1312
1313
                if ((! $mimes || $stat['mime'] !== 'directory')) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $mimes 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 empty(..) or ! empty(...) instead.

Loading history...
1314
                    $stat['path'] = $this->path($stat['hash']);
1315
                    if ($this->URL && ! isset($stat['url'])) {
1316
                        $_path = str_replace(DIRECTORY_SEPARATOR, '/', substr($p, strlen($this->root) + 1));
1317
                        $stat['url'] = $this->URL.str_replace('%2F', '/', rawurlencode($_path));
1318
                    }
1319
1320
                    $result[] = $stat;
1321
                }
1322
            }
1323
        }
1324
1325
        return $result;
1326
    }
1327
} // END class
1328