Completed
Branch master (5090d0)
by Pierre-Henry
35:42
created

elFinderVolumeLocalFileSystem.class.php (1 issue)

Labels
Severity

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
defined('PH7') or exit('Restricted access');
3
if (!\PH7\Admin::auth()) exit('Restricted access'); // Accessible only for admins
4
5
// Implement similar functionality in PHP 5.2 or 5.3
6
// http://php.net/manual/class.recursivecallbackfilteriterator.php#110974
7
if (! class_exists('RecursiveCallbackFilterIterator', false)) {
8
    class RecursiveCallbackFilterIterator extends RecursiveFilterIterator {
9
10
        public function __construct ( RecursiveIterator $iterator, $callback ) {
11
            $this->callback = $callback;
12
            parent::__construct($iterator);
13
        }
14
15
        public function accept () {
16
            return call_user_func($this->callback, parent::current(), parent::key(), parent::getInnerIterator());
17
        }
18
19
        public function getChildren () {
20
            return new self($this->getInnerIterator()->getChildren(), $this->callback);
0 ignored issues
show
It seems like you code against a concrete implementation and not the interface Iterator as the method getChildren() does only exist in the following implementations of said interface: PHPUnit_Runner_Filter_GroupFilterIterator, PHPUnit_Runner_Filter_Group_Exclude, PHPUnit_Runner_Filter_Group_Include, PHPUnit_Runner_Filter_Test, PHPUnit_Util_TestSuiteIterator, ParentIterator, Phar, PharData, RecursiveArrayIterator, RecursiveCachingIterator, RecursiveCallbackFilterIterator, RecursiveCallbackFilterIterator, RecursiveCallbackFilterIterator, RecursiveDirectoryIterator, RecursiveFilterIterator, RecursiveRegexIterator, SebastianBergmann\CodeCoverage\Node\Iterator, SimpleXMLIterator, SplFileObject, SplTempFileObject, Symfony\Component\Finder...DirectoryFilterIterator, Symfony\Component\Finder...ursiveDirectoryIterator.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
21
        }
22
    }
23
}
24
25
/**
26
 * elFinder driver for local filesystem.
27
 *
28
 * @author Dmitry (dio) Levashov
29
 * @author Troex Nevelin
30
 **/
31
class elFinderVolumeLocalFileSystem extends elFinderVolumeDriver {
32
33
    /**
34
     * Driver id
35
     * Must be started from letter and contains [a-z0-9]
36
     * Used as part of volume id
37
     *
38
     * @var string
39
     **/
40
    protected $driverId = 'l';
41
42
    /**
43
     * Required to count total archive files size
44
     *
45
     * @var int
46
     **/
47
    protected $archiveSize = 0;
48
49
    /**
50
     * Constructor
51
     * Extend options with required fields
52
     *
53
     * @author Dmitry (dio) Levashov
54
     */
55
    public function __construct() {
56
        $this->options['alias']    = '';              // alias to replace root dir name
57
        $this->options['dirMode']  = 0755;            // new dirs mode
58
        $this->options['fileMode'] = 0644;            // new files mode
59
        $this->options['quarantine'] = '.quarantine'; // quarantine folder name - required to check archive (must be hidden)
60
        $this->options['rootCssClass'] = 'elfinder-navbar-root-local';
61
        $this->options['followSymLinks'] = true;
62
        $this->options['detectDirIcon'] = '';         // file name that is detected as a folder icon e.g. '.diricon.png'
63
        $this->options['keepTimestamp'] = array('copy', 'move'); // keep timestamp at inner filesystem allowed 'copy', 'move' and 'upload'
64
    }
65
66
    /*********************************************************************/
67
    /*                        INIT AND CONFIGURE                         */
68
    /*********************************************************************/
69
70
    /**
71
     * Prepare driver before mount volume.
72
     * Return true if volume is ready.
73
     *
74
     * @return bool
75
     **/
76
    protected function init() {
77
        // Normalize directory separator for windows
78
        if (DIRECTORY_SEPARATOR !== '/') {
79
            foreach(array('path', 'tmbPath', 'tmpPath', 'quarantine') as $key) {
80
                if (!empty($this->options[$key])) {
81
                    $this->options[$key] = str_replace('/', DIRECTORY_SEPARATOR, $this->options[$key]);
82
                }
83
            }
84
        }
85
        if (!$cwd = getcwd()) {
86
            return $this->setError('elFinder LocalVolumeDriver requires a result of getcwd().');
87
        }
88
        // detect systemRoot
89
        if (!isset($this->options['systemRoot'])) {
90
            if ($cwd[0] === $this->separator || $this->root[0] === $this->separator) {
91
                $this->systemRoot = $this->separator;
92
            } else if (preg_match('/^([a-zA-Z]:'.preg_quote($this->separator, '/').')/', $this->root, $m)) {
93
                $this->systemRoot = $m[1];
94
            } else if (preg_match('/^([a-zA-Z]:'.preg_quote($this->separator, '/').')/', $cwd, $m)) {
95
                $this->systemRoot = $m[1];
96
            }
97
        }
98
        $this->root = $this->getFullPath($this->root, $cwd);
99
        if (!empty($this->options['startPath'])) {
100
            $this->options['startPath'] = $this->getFullPath($this->options['startPath'], $cwd);
101
        }
102
103
        if (is_null($this->options['syncChkAsTs'])) {
104
            $this->options['syncChkAsTs'] = true;
105
        }
106
        if (is_null($this->options['syncCheckFunc'])) {
107
            $this->options['syncCheckFunc'] = array($this, 'localFileSystemInotify');
108
        }
109
110
        return true;
111
    }
112
113
    /**
114
     * Configure after successfull mount.
115
     *
116
     * @return void
117
     * @author Dmitry (dio) Levashov
118
     **/
119
    protected function configure() {
120
        $root = $this->stat($this->root);
121
122
        // chek thumbnails path
123
        if ($this->options['tmbPath']) {
124
            $this->options['tmbPath'] = strpos($this->options['tmbPath'], DIRECTORY_SEPARATOR) === false
125
                // tmb path set as dirname under root dir
126
                ? $this->_abspath($this->options['tmbPath'])
127
                // tmb path as full path
128
                : $this->_normpath($this->options['tmbPath']);
129
        }
130
131
        parent::configure();
132
133
        // set $this->tmp by options['tmpPath']
134
        $this->tmp = '';
135
        if (!empty($this->options['tmpPath'])) {
136
            if ((is_dir($this->options['tmpPath']) || mkdir($this->options['tmpPath'], 0755, true)) && is_writable($this->options['tmpPath'])) {
137
                $this->tmp = $this->options['tmpPath'];
138
            }
139
        }
140
        if (!$this->tmp && ($tmp = elFinder::getStaticVar('commonTempPath'))) {
141
            $this->tmp = $tmp;
142
        }
143
144
        // if no thumbnails url - try detect it
145
        if ($root['read'] && !$this->tmbURL && $this->URL) {
146
            if (strpos($this->tmbPath, $this->root) === 0) {
147
                $this->tmbURL = $this->URL.str_replace(DIRECTORY_SEPARATOR, '/', substr($this->tmbPath, strlen($this->root)+1));
148
                if (preg_match("|[^/?&=]$|", $this->tmbURL)) {
149
                    $this->tmbURL .= '/';
150
                }
151
            }
152
        }
153
154
        // check quarantine dir
155
        $this->quarantine = '';
156
        if (!empty($this->options['quarantine'])) {
157
            if (is_dir($this->options['quarantine'])) {
158
                if (is_writable($this->options['quarantine'])) {
159
                    $this->quarantine = $this->options['quarantine'];
160
                }
161
                $this->options['quarantine'] = '';
162
            } else {
163
                $this->quarantine = $this->_abspath($this->options['quarantine']);
164
                if ((!is_dir($this->quarantine) && !mkdir($this->quarantine)) || !is_writable($this->quarantine)) {
165
                    $this->options['quarantine'] = $this->quarantine = '';
166
                }
167
            }
168
        }
169
170
        if (!$this->quarantine) {
171
            if (!$this->tmp) {
172
                $this->archivers['extract'] = array();
173
                $this->disabled[] = 'extract';
174
            } else {
175
                $this->quarantine = $this->tmp;
176
            }
177
        }
178
179
        if ($this->options['quarantine']) {
180
            $this->attributes[] = array(
181
                    'pattern' => '~^'.preg_quote(DIRECTORY_SEPARATOR.$this->options['quarantine']).'$~',
182
                    'read'    => false,
183
                    'write'   => false,
184
                    'locked'  => true,
185
                    'hidden'  => true
186
            );
187
        }
188
189
        if (! empty($this->options['keepTimestamp'])) {
190
            $this->options['keepTimestamp'] = array_flip($this->options['keepTimestamp']);
191
        }
192
    }
193
194
    /**
195
     * Long pooling sync checker
196
     * This function require server command `inotifywait`
197
     * If `inotifywait` need full path, Please add `define('ELFINER_INOTIFYWAIT_PATH', '/PATH_TO/inotifywait');` into connector.php
198
     *
199
     * @param string     $path
200
     * @param int        $standby
201
     * @param number     $compare
202
     * @return number|bool
203
     */
204
    public function localFileSystemInotify($path, $standby, $compare) {
205
        if (isset($this->sessionCache['localFileSystemInotify_disable'])) {
206
            return false;
207
        }
208
        $path = realpath($path);
209
        $mtime = filemtime($path);
210
        if (! $mtime) {
211
            return false;
212
        }
213
        if ($mtime != $compare) {
214
            return $mtime;
215
        }
216
        $inotifywait = defined('ELFINER_INOTIFYWAIT_PATH')? ELFINER_INOTIFYWAIT_PATH : 'inotifywait';
217
        $standby = max(1, intval($standby));
218
        $cmd = $inotifywait.' '.escapeshellarg($path).' -t '.$standby.' -e moved_to,moved_from,move,close_write,delete,delete_self';
219
        $this->procExec($cmd , $o, $r);
220
        if ($r === 0) {
221
            // changed
222
            clearstatcache();
223
            if (file_exists($path)) {
224
                $mtime = filemtime($path); // error on busy?
225
                return $mtime? $mtime : time();
226
            } else {
227
                // target was removed
228
                return 0;
229
            }
230
        } else if ($r === 2) {
231
            // not changed (timeout)
232
            return $compare;
233
        }
234
        // error
235
        // cache to $_SESSION
236
        $this->sessionCache['localFileSystemInotify_disable'] = true;
237
        $this->session->set($this->id, $this->sessionCache, true);
238
        return false;
239
    }
240
241
    /*********************************************************************/
242
    /*                               FS API                              */
243
    /*********************************************************************/
244
245
    /*********************** paths/urls *************************/
246
247
    /**
248
     * Return parent directory path
249
     *
250
     * @param  string  $path  file path
251
     * @return string
252
     * @author Dmitry (dio) Levashov
253
     **/
254
    protected function _dirname($path) {
255
        return dirname($path);
256
    }
257
258
    /**
259
     * Return file name
260
     *
261
     * @param  string  $path  file path
262
     * @return string
263
     * @author Dmitry (dio) Levashov
264
     **/
265
    protected function _basename($path) {
266
        return basename($path);
267
    }
268
269
    /**
270
     * Join dir name and file name and retur full path
271
     *
272
     * @param  string  $dir
273
     * @param  string  $name
274
     * @return string
275
     * @author Dmitry (dio) Levashov
276
     **/
277
    protected function _joinPath($dir, $name) {
278
        return rtrim($dir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $name;
279
    }
280
281
    /**
282
     * Return normalized path, this works the same as os.path.normpath() in Python
283
     *
284
     * @param  string  $path  path
285
     * @return string
286
     * @author Troex Nevelin
287
     **/
288
    protected function _normpath($path) {
289
        if (empty($path)) {
290
            return '.';
291
        }
292
293
        $changeSep = (DIRECTORY_SEPARATOR !== '/');
294
        if ($changeSep) {
295
            $drive = '';
296
            if (preg_match('/^([a-zA-Z]:)(.*)/', $path, $m)) {
297
                $drive = $m[1];
298
                $path = $m[2]? $m[2] : '/';
299
            }
300
            $path = str_replace(DIRECTORY_SEPARATOR, '/', $path);
301
        }
302
303
        if (strpos($path, '/') === 0) {
304
            $initial_slashes = true;
305
        } else {
306
            $initial_slashes = false;
307
        }
308
309
        if (($initial_slashes)
310
        && (strpos($path, '//') === 0)
311
        && (strpos($path, '///') === false)) {
312
            $initial_slashes = 2;
313
        }
314
315
        $initial_slashes = (int) $initial_slashes;
316
317
        $comps = explode('/', $path);
318
        $new_comps = array();
319
        foreach ($comps as $comp) {
320
            if (in_array($comp, array('', '.'))) {
321
                continue;
322
            }
323
324
            if (($comp != '..')
325
            || (!$initial_slashes && !$new_comps)
326
            || ($new_comps && (end($new_comps) == '..'))) {
327
                array_push($new_comps, $comp);
328
            } elseif ($new_comps) {
329
                array_pop($new_comps);
330
            }
331
        }
332
        $comps = $new_comps;
333
        $path = implode('/', $comps);
334
        if ($initial_slashes) {
335
            $path = str_repeat('/', $initial_slashes) . $path;
336
        }
337
338
        if ($changeSep) {
339
            $path = $drive . str_replace('/', DIRECTORY_SEPARATOR, $path);
340
        }
341
342
        return $path ? $path : '.';
343
    }
344
345
    /**
346
     * Return file path related to root dir
347
     *
348
     * @param  string  $path  file path
349
     * @return string
350
     * @author Dmitry (dio) Levashov
351
     **/
352
    protected function _relpath($path) {
353
        if ($path === $this->root) {
354
            return '';
355
        } else {
356
            if (strpos($path, $this->root) === 0) {
357
                return ltrim(substr($path, strlen($this->root)), DIRECTORY_SEPARATOR);
358
            } else {
359
                // for link
360
                return $path;
361
            }
362
        }
363
    }
364
365
    /**
366
     * Convert path related to root dir into real path
367
     *
368
     * @param  string  $path  file path
369
     * @return string
370
     * @author Dmitry (dio) Levashov
371
     **/
372
    protected function _abspath($path) {
373
        if ($path === DIRECTORY_SEPARATOR) {
374
            return $this->root;
375
        } else {
376
            if ($path[0] === DIRECTORY_SEPARATOR) {
377
                // for link
378
                return $path;
379
            } else {
380
                return $this->_joinPath($this->root, $path);
381
            }
382
        }
383
    }
384
385
    /**
386
     * Return fake path started from root dir
387
     *
388
     * @param  string  $path  file path
389
     * @return string
390
     * @author Dmitry (dio) Levashov
391
     **/
392
    protected function _path($path) {
393
        return $this->rootName.($path == $this->root ? '' : $this->separator.$this->_relpath($path));
394
    }
395
396
    /**
397
     * Return true if $path is children of $parent
398
     *
399
     * @param  string  $path    path to check
400
     * @param  string  $parent  parent path
401
     * @return bool
402
     * @author Dmitry (dio) Levashov
403
     **/
404
    protected function _inpath($path, $parent) {
405
        $cwd = getcwd();
406
        $real_path   = $this->getFullPath($path,   $cwd);
407
        $real_parent = $this->getFullPath($parent, $cwd);
408
        if ($real_path && $real_parent) {
409
            return $real_path === $real_parent || strpos($real_path, rtrim($real_parent, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR) === 0;
410
        }
411
        return false;
412
    }
413
414
415
416
    /***************** file stat ********************/
417
418
    /**
419
     * Return stat for given path.
420
     * Stat contains following fields:
421
     * - (int)    size    file size in b. required
422
     * - (int)    ts      file modification time in unix time. required
423
     * - (string) mime    mimetype. required for folders, others - optionally
424
     * - (bool)   read    read permissions. required
425
     * - (bool)   write   write permissions. required
426
     * - (bool)   locked  is object locked. optionally
427
     * - (bool)   hidden  is object hidden. optionally
428
     * - (string) alias   for symlinks - link target path relative to root path. optionally
429
     * - (string) target  for symlinks - link target path. optionally
430
     *
431
     * If file does not exists - returns empty array or false.
432
     *
433
     * @param  string  $path    file path
434
     * @return array|false
435
     * @author Dmitry (dio) Levashov
436
     **/
437
    protected function _stat($path) {
438
439
        static $statOwner;
440
        if (is_null($statOwner)) {
441
            $statOwner = (!empty($this->options['statOwner']));
442
        }
443
444
        $stat = array();
445
446
        if (!file_exists($path) && !is_link($path)) {
447
            return $stat;
448
        }
449
450
        //Verifies the given path is the root or is inside the root. Prevents directory traveral.
451
        if (!$this->_inpath($path, $this->root)) {
452
            return $stat;
453
        }
454
455
        $gid = $uid = 0;
456
        $stat['isowner'] = false;
457
        $linkreadable = false;
458
        if ($path != $this->root && is_link($path)) {
459
            if (! $this->options['followSymLinks']) {
460
                return array();
461
            }
462
            if (!($target = $this->readlink($path))
463
            || $target == $path) {
464
                if (is_null($target)) {
465
                    $stat = array();
466
                    return $stat;
467
                } else {
468
                    $stat['mime']  = 'symlink-broken';
469
                    $target = readlink($path);
470
                    $lstat = lstat($path);
471
                    $ostat = $this->getOwnerStat($lstat['uid'], $lstat['gid']);
472
                    $linkreadable = !empty($ostat['isowner']);
473
                }
474
            }
475
            $stat['alias'] = $this->_path($target);
476
            $stat['target'] = $target;
477
        }
478
        $size = sprintf('%u', filesize($path));
479
        $stat['ts'] = filemtime($path);
480
        if ($statOwner) {
481
            $fstat = stat($path);
482
            $uid = $fstat['uid'];
483
            $gid = $fstat['gid'];
484
            $stat['perm'] = substr((string)decoct($fstat['mode']), -4);
485
            $stat = array_merge($stat, $this->getOwnerStat($uid, $gid));
486
        }
487
488
        if (($dir = is_dir($path)) && $this->options['detectDirIcon']) {
489
            $favicon = $path . DIRECTORY_SEPARATOR . $this->options['detectDirIcon'];
490
            if ($this->URL && file_exists($favicon)) {
491
                $stat['icon'] = $this->URL . str_replace(DIRECTORY_SEPARATOR, '/', substr($favicon, strlen($this->root) + 1));
492
            }
493
        }
494
495
        if (!isset($stat['mime'])) {
496
            $stat['mime'] = $dir ? 'directory' : $this->mimetype($path);
497
        }
498
        //logical rights first
499
        $stat['read'] = ($linkreadable || is_readable($path))? null : false;
500
        $stat['write'] = is_writable($path)? null : false;
501
502
        if (is_null($stat['read'])) {
503
            $stat['size'] = $dir ? 0 : $size;
504
        }
505
506
        return $stat;
507
    }
508
509
    /**
510
     * Get stat `owner`, `group` and `isowner` by `uid` and `gid`
511
     * Sub-fuction of _stat() and _scandir()
512
     *
513
     * @param integer $uid
514
     * @param integer $gid
515
     * @return array  stat
516
     */
517
    protected function getOwnerStat($uid, $gid) {
518
        static $names = null;
519
        static $phpuid = null;
520
521
        if (is_null($names)) {
522
            $names = array('uid' => array(), 'gid' =>array());
523
        }
524
        if (is_null($phpuid)) {
525
            if (is_callable('posix_getuid')) {
526
                $phpuid = posix_getuid();
527
            } else {
528
                $phpuid = 0;
529
            }
530
        }
531
532
        $stat = array();
533
534
        if ($uid) {
535
            $stat['isowner'] = ($phpuid == $uid);
536
            if (isset($names['uid'][$uid])) {
537
                $stat['owner'] = $names['uid'][$uid];
538
            } else if (is_callable('posix_getpwuid')) {
539
                $pwuid = posix_getpwuid($uid);
540
                $stat['owner'] = $names['uid'][$uid] = $pwuid['name'];
541
            } else {
542
                $stat['owner'] = $names['uid'][$uid] = $uid;
543
            }
544
        }
545
        if ($gid) {
546
            if (isset($names['gid'][$gid])) {
547
                $stat['group'] = $names['gid'][$gid];
548
            } else if (is_callable('posix_getgrgid')) {
549
                $grgid = posix_getgrgid($gid);
550
                $stat['group'] = $names['gid'][$gid] = $grgid['name'];
551
            } else {
552
                $stat['group'] = $names['gid'][$gid] = $gid;
553
            }
554
        }
555
556
        return $stat;
557
    }
558
559
    /**
560
     * Return true if path is dir and has at least one childs directory
561
     *
562
     * @param  string  $path  dir path
563
     * @return bool
564
     * @author Dmitry (dio) Levashov
565
     **/
566
    protected function _subdirs($path) {
567
568
        $dirs = false;
569
        if (is_dir($path)) {
570
            if (class_exists('FilesystemIterator', false)) {
571
                $dirItr = new ParentIterator(
572
                    new RecursiveDirectoryIterator($path,
573
                        FilesystemIterator::SKIP_DOTS |
574
                        (defined('RecursiveDirectoryIterator::FOLLOW_SYMLINKS')?
575
                            RecursiveDirectoryIterator::FOLLOW_SYMLINKS : 0)
576
                    )
577
                );
578
                $dirItr->rewind();
579
                if ($dirItr->hasChildren()) {
580
                    $dirs = true;
581
                    $name = $dirItr->getSubPathName();
582
                    while($name) {
583
                        if (!$this->attr($path . DIRECTORY_SEPARATOR . $name, 'read', null, true)) {
584
                            $dirs = false;
585
                            $dirItr->next();
586
                            $name = $dirItr->getSubPathName();
587
                            continue;
588
                        }
589
                        $dirs = true;
590
                        break;
591
                    }
592
                }
593
            } else {
594
                $path = strtr($path, array('['  => '\\[', ']'  => '\\]', '*'  => '\\*', '?'  => '\\?'));
595
                return (bool)glob(rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . '*', GLOB_ONLYDIR);
596
            }
597
        }
598
        return $dirs;
599
    }
600
601
    /**
602
     * Return object width and height
603
     * Usualy used for images, but can be realize for video etc...
604
     *
605
     * @param  string  $path  file path
606
     * @param  string  $mime  file mime type
607
     * @return string
608
     * @author Dmitry (dio) Levashov
609
     **/
610
    protected function _dimensions($path, $mime) {
611
        clearstatcache();
612
        return strpos($mime, 'image') === 0 && is_readable($path) && ($s = getimagesize($path)) !== false
613
            ? $s[0].'x'.$s[1]
614
            : false;
615
    }
616
    /******************** file/dir content *********************/
617
618
    /**
619
     * Return symlink target file
620
     *
621
     * @param  string  $path  link path
622
     * @return string
623
     * @author Dmitry (dio) Levashov
624
     **/
625
    protected function readlink($path) {
626
        if (!($target = readlink($path))) {
627
            return null;
628
        }
629
630
        if (strpos($target, $this->systemRoot) !== 0) {
631
            $target = $this->_joinPath(dirname($path), $target);
632
        }
633
634
        if (!file_exists($target)) {
635
            return false;
636
        }
637
638
        return $target;
639
    }
640
641
    /**
642
     * Return files list in directory.
643
     *
644
     * @param  string  $path  dir path
645
     * @return array
646
     * @author Dmitry (dio) Levashov
647
     **/
648
    protected function _scandir($path) {
649
        $files = array();
650
        $cache = array();
651
        $dirWritable = is_writable($path);
652
        $statOwner = (!empty($this->options['statOwner']));
653
        $dirItr = array();
654
        $followSymLinks = $this->options['followSymLinks'];
655
        try {
656
            $dirItr = new DirectoryIterator($path);
657
        } catch (UnexpectedValueException $e) {}
658
659
        foreach ($dirItr as $file) {
660
            try {
661
                if ($file->isDot()) { continue; }
662
663
                $files[] = $fpath = $file->getPathname();
664
665
                $br = false;
666
                $stat = array();
667
668
                $gid = $uid = 0;
669
                $stat['isowner'] = false;
670
                $linkreadable = false;
671
                if ($file->isLink()) {
672
                    if (! $followSymLinks) { continue; }
673
                    if (!($target = $this->readlink($fpath))
674
                    || $target == $fpath) {
675
                        if (is_null($target)) {
676
                            $stat = array();
677
                            $br = true;
678
                        } else {
679
                            $_path = $fpath;
680
                            $stat['mime']  = 'symlink-broken';
681
                            $target = readlink($_path);
682
                            $lstat = lstat($_path);
683
                            $ostat = $this->getOwnerStat($lstat['uid'], $lstat['gid']);
684
                            $linkreadable = !empty($ostat['isowner']);
685
                            $dir = false;
686
                            $stat['alias'] = $this->_path($target);
687
                            $stat['target'] = $target;
688
                        }
689
                    } else {
690
                        $dir = is_dir($target);
691
                        $stat['alias'] = $this->_path($target);
692
                        $stat['target'] = $target;
693
                        $stat['mime'] = $dir ? 'directory' : $this->mimetype($stat['alias']);
694
                    }
695
                } else {
696
                    if (($dir = $file->isDir()) && $this->options['detectDirIcon']) {
697
                        $path = $file->getPathname();
698
                        $favicon = $path . DIRECTORY_SEPARATOR . $this->options['detectDirIcon'];
699
                        if ($this->URL && file_exists($favicon)) {
700
                            $stat['icon'] = $this->URL . str_replace(DIRECTORY_SEPARATOR, '/', substr($favicon, strlen($this->root) + 1));
701
                        }
702
                    }
703
                    $stat['mime'] = $dir ? 'directory' : $this->mimetype($fpath);
704
                }
705
                $size = sprintf('%u', $file->getSize());
706
                $stat['ts'] = $file->getMTime();
707
                if (!$br) {
708
                    if ($statOwner && !$linkreadable) {
709
                        $uid = $file->getOwner();
710
                        $gid = $file->getGroup();
711
                        $stat['perm'] = substr((string)decoct($file->getPerms()), -4);
712
                        $stat = array_merge($stat, $this->getOwnerStat($uid, $gid));
713
                    }
714
715
                    //logical rights first
716
                    $stat['read'] = ($linkreadable || $file->isReadable())? null : false;
717
                    $stat['write'] = $file->isWritable()? null : false;
718
                    $stat['locked'] = $dirWritable? null : true;
719
720
                    if (is_null($stat['read'])) {
721
                        $stat['size'] = $dir ? 0 : $size;
722
                    }
723
724
                }
725
726
                $cache[] = array($fpath, $stat);
727
            } catch (RuntimeException $e) {
728
                continue;
729
            }
730
        }
731
732
        if ($cache) {
733
            $cache = $this->convEncOut($cache, false);
734
            foreach($cache as $d) {
735
                $this->updateCache($d[0], $d[1]);
736
            }
737
        }
738
739
        return $files;
740
    }
741
742
    /**
743
     * Open file and return file pointer
744
     *
745
     * @param  string $path file path
746
     * @param string $mode
747
     * @return false|resource
748
     * @internal param bool $write open file for writing
749
     * @author Dmitry (dio) Levashov
750
     */
751
    protected function _fopen($path, $mode='rb') {
752
        return fopen($path, $mode);
753
    }
754
755
    /**
756
     * Close opened file
757
     *
758
     * @param  resource $fp file pointer
759
     * @param string $path
760
     * @return bool
761
     * @author Dmitry (dio) Levashov
762
     */
763
    protected function _fclose($fp, $path='') {
764
        return (is_resource($fp) && fclose($fp));
765
    }
766
767
    /********************  file/dir manipulations *************************/
768
769
    /**
770
     * Create dir and return created dir path or false on failed
771
     *
772
     * @param  string  $path  parent dir path
773
     * @param string  $name  new directory name
774
     * @return string|bool
775
     * @author Dmitry (dio) Levashov
776
     **/
777
    protected function _mkdir($path, $name) {
778
        $path = $this->_joinPath($path, $name);
779
780
        if (mkdir($path)) {
781
            chmod($path, $this->options['dirMode']);
782
            clearstatcache();
783
            return $path;
784
        }
785
786
        return false;
787
    }
788
789
    /**
790
     * Create file and return it's path or false on failed
791
     *
792
     * @param  string  $path  parent dir path
793
     * @param string  $name  new file name
794
     * @return string|bool
795
     * @author Dmitry (dio) Levashov
796
     **/
797
    protected function _mkfile($path, $name) {
798
        $path = $this->_joinPath($path, $name);
799
800
        if (($fp = fopen($path, 'w'))) {
801
            fclose($fp);
802
            chmod($path, $this->options['fileMode']);
803
            clearstatcache();
804
            return $path;
805
        }
806
        return false;
807
    }
808
809
    /**
810
     * Create symlink
811
     *
812
     * @param  string  $source     file to link to
813
     * @param  string  $targetDir  folder to create link in
814
     * @param  string  $name       symlink name
815
     * @return bool
816
     * @author Dmitry (dio) Levashov
817
     **/
818
    protected function _symlink($source, $targetDir, $name) {
819
        return symlink($source, $this->_joinPath($targetDir, $name));
820
    }
821
822
    /**
823
     * Copy file into another file
824
     *
825
     * @param  string  $source     source file path
826
     * @param  string  $targetDir  target directory path
827
     * @param  string  $name       new file name
828
     * @return bool
829
     * @author Dmitry (dio) Levashov
830
     **/
831
    protected function _copy($source, $targetDir, $name) {
832
        $mtime = filemtime($source);
833
        $target = $this->_joinPath($targetDir, $name);
834
        if ($ret = copy($source, $target)) {
835
            isset($this->options['keepTimestamp']['copy']) && $mtime && touch($target, $mtime);
836
            clearstatcache();
837
        }
838
        return $ret;
839
    }
840
841
    /**
842
     * Move file into another parent dir.
843
     * Return new file path or false.
844
     *
845
     * @param  string $source source file path
846
     * @param $targetDir
847
     * @param  string $name file name
848
     * @return bool|string
849
     * @internal param string $target target dir path
850
     * @author Dmitry (dio) Levashov
851
     */
852
    protected function _move($source, $targetDir, $name) {
853
        $mtime = filemtime($source);
854
        $target = $this->_joinPath($targetDir, $name);
855
        if ($ret = rename($source, $target) ? $target : false) {
856
            isset($this->options['keepTimestamp']['move']) && $mtime && touch($target, $mtime);
857
            clearstatcache();
858
        }
859
        return $ret;
860
    }
861
862
    /**
863
     * Remove file
864
     *
865
     * @param  string  $path  file path
866
     * @return bool
867
     * @author Dmitry (dio) Levashov
868
     **/
869
    protected function _unlink($path) {
870
        $ret = unlink($path);
871
        $ret && clearstatcache();
872
        return $ret;
873
    }
874
875
    /**
876
     * Remove dir
877
     *
878
     * @param  string  $path  dir path
879
     * @return bool
880
     * @author Dmitry (dio) Levashov
881
     **/
882
    protected function _rmdir($path) {
883
        $ret = rmdir($path);
884
        $ret && clearstatcache();
885
        return $ret;
886
    }
887
888
    /**
889
     * Create new file and write into it from file pointer.
890
     * Return new file path or false on error.
891
     *
892
     * @param  resource  $fp   file pointer
893
     * @param  string    $dir  target dir path
894
     * @param  string    $name file name
895
     * @param  array     $stat file stat (required by some virtual fs)
896
     * @return bool|string
897
     * @author Dmitry (dio) Levashov
898
     **/
899
    protected function _save($fp, $dir, $name, $stat) {
900
        $path = $this->_joinPath($dir, $name);
901
902
        $meta = stream_get_meta_data($fp);
903
        $uri = isset($meta['uri'])? $meta['uri'] : '';
904
        if ($uri && ! preg_match('#^[a-zA-Z0-9]+://#', $uri)) {
905
            fclose($fp);
906
            $mtime = filemtime($uri);
907
            $isCmdPaste = ($this->ARGS['cmd'] === 'paste');
908
            $isCmdCopy = ($isCmdPaste && empty($this->ARGS['cut']));
909
            if (($isCmdCopy || !rename($uri, $path)) && !copy($uri, $path)) {
910
                return false;
911
            }
912
            // keep timestamp on upload
913
            if ($mtime && $this->ARGS['cmd'] === 'upload' && isset($this->options['keepTimestamp']['upload'])) {
914
                touch($path, $mtime);
915
            }
916
            // re-create the source file for remove processing of paste command
917
            $isCmdPaste && !$isCmdCopy && touch($uri);
918
        } else {
919
            if (file_put_contents($path, $fp, LOCK_EX) === false) {
920
                return false;
921
            }
922
        }
923
924
        if (is_link($path)) {
925
            unlink($path);
926
            return $this->setError(elFinder::ERROR_SAVE, $name);
927
        }
928
929
        chmod($path, $this->options['fileMode']);
930
        clearstatcache();
931
        return $path;
932
    }
933
934
    /**
935
     * Get file contents
936
     *
937
     * @param  string  $path  file path
938
     * @return string|false
939
     * @author Dmitry (dio) Levashov
940
     **/
941
    protected function _getContents($path) {
942
        return file_get_contents($path);
943
    }
944
945
    /**
946
     * Write a string to a file
947
     *
948
     * @param  string  $path     file path
949
     * @param  string  $content  new file content
950
     * @return bool
951
     * @author Dmitry (dio) Levashov
952
     **/
953
    protected function _filePutContents($path, $content) {
954
        if (file_put_contents($path, $content, LOCK_EX) !== false) {
955
            clearstatcache();
956
            return true;
957
        }
958
        return false;
959
    }
960
961
    /**
962
     * Detect available archivers
963
     *
964
     * @return void
965
     **/
966
    protected function _checkArchivers() {
967
        $this->archivers = $this->getArchivers();
968
        return;
969
    }
970
971
    /**
972
     * chmod availability
973
     *
974
     * @param string $path
975
     * @param string $mode
976
     * @return bool
977
     */
978
    protected function _chmod($path, $mode) {
979
        $modeOct = is_string($mode) ? octdec($mode) : octdec(sprintf("%04o",$mode));
980
        $ret = chmod($path, $modeOct);
981
        $ret && clearstatcache();
982
        return  $ret;
983
    }
984
985
    /**
986
     * Recursive symlinks search
987
     *
988
     * @param  string  $path  file/dir path
989
     * @return bool
990
     * @author Dmitry (dio) Levashov
991
     **/
992
    protected function _findSymlinks($path) {
993
        if (is_link($path)) {
994
            return true;
995
        }
996
997
        if (is_dir($path)) {
998
            foreach (self::localScandir($path) as $name) {
999
                $p = $path.DIRECTORY_SEPARATOR.$name;
1000
                if (is_link($p) || !$this->nameAccepted($name)
1001
                    ||
1002
                (($mimeByName = elFinderVolumeDriver::mimetypeInternalDetect($name)) && $mimeByName !== 'unknown' && !$this->allowPutMime($mimeByName))) {
1003
                    $this->setError(elFinder::ERROR_SAVE, $name);
1004
                    return true;
1005
                }
1006
                if (is_dir($p) && $this->_findSymlinks($p)) {
1007
                    return true;
1008
                } elseif (is_file($p)) {
1009
                    $this->archiveSize += sprintf('%u', filesize($p));
1010
                }
1011
            }
1012
        } else {
1013
1014
            $this->archiveSize += sprintf('%u', filesize($path));
1015
        }
1016
1017
        return false;
1018
    }
1019
1020
    /**
1021
     * Extract files from archive
1022
     *
1023
     * @param  string  $path  archive path
1024
     * @param  array   $arc   archiver command and arguments (same as in $this->archivers)
1025
     * @return true
1026
     * @author Dmitry (dio) Levashov,
1027
     * @author Alexey Sukhotin
1028
     **/
1029
    protected function _extract($path, $arc) {
1030
1031
        if ($this->quarantine) {
1032
1033
            $dir     = $this->quarantine.DIRECTORY_SEPARATOR.md5(basename($path).mt_rand());
1034
            $archive = $dir.DIRECTORY_SEPARATOR.basename($path);
1035
1036
            if (!mkdir($dir)) {
1037
                return false;
1038
            }
1039
1040
            // insurance unexpected shutdown
1041
            register_shutdown_function(array($this, 'rmdirRecursive'), realpath($dir));
1042
1043
            chmod($dir, 0777);
1044
1045
            // copy in quarantine
1046
            if (!copy($path, $archive)) {
1047
                return false;
1048
            }
1049
1050
            // extract in quarantine
1051
            $this->unpackArchive($archive, $arc);
1052
1053
            // get files list
1054
            $ls = self::localScandir($dir);
1055
1056
            // no files - extract error ?
1057
            if (empty($ls)) {
1058
                return false;
1059
            }
1060
1061
            $this->archiveSize = 0;
1062
1063
            // find symlinks
1064
            $symlinks = $this->_findSymlinks($dir);
1065
1066
            if ($symlinks) {
1067
                $this->delTree($dir);
1068
                return $this->setError(array_merge($this->error, array(elFinder::ERROR_ARC_SYMLINKS)));
1069
            }
1070
1071
            // check max files size
1072
            if ($this->options['maxArcFilesSize'] > 0 && $this->options['maxArcFilesSize'] < $this->archiveSize) {
1073
                $this->delTree($dir);
1074
                return $this->setError(elFinder::ERROR_ARC_MAXSIZE);
1075
            }
1076
1077
            $extractTo = $this->extractToNewdir; // 'auto', ture or false
1078
1079
            // archive contains one item - extract in archive dir
1080
            $name = '';
1081
            $src = $dir.DIRECTORY_SEPARATOR.$ls[0];
1082
            if (($extractTo === 'auto' || !$extractTo) && count($ls) === 1 && is_file($src)) {
1083
                $name = $ls[0];
1084
            } else if ($extractTo === 'auto' || $extractTo) {
1085
                // for several files - create new directory
1086
                // create unique name for directory
1087
                $src = $dir;
1088
                $name = basename($path);
1089
                if (preg_match('/\.((tar\.(gz|bz|bz2|z|lzo))|cpio\.gz|ps\.gz|xcf\.(gz|bz2)|[a-z0-9]{1,4})$/i', $name, $m)) {
1090
                    $name = substr($name, 0,  strlen($name)-strlen($m[0]));
1091
                }
1092
                $test = dirname($path).DIRECTORY_SEPARATOR.$name;
1093
                if (file_exists($test) || is_link($test)) {
1094
                    $name = $this->uniqueName(dirname($path), $name, '-', false);
1095
                }
1096
            }
1097
1098
            if ($name !== '') {
1099
                $result  = dirname($path).DIRECTORY_SEPARATOR.$name;
1100
1101
                if (! rename($src, $result)) {
1102
                    $this->delTree($dir);
1103
                    return false;
1104
                }
1105
            } else {
1106
                $dstDir = dirname($path);
1107
                $res = false;
1108
                $result = array();
1109
                foreach($ls as $name) {
1110
                    $target = $dstDir.DIRECTORY_SEPARATOR.$name;
1111
                    if (is_dir($target)) {
1112
                        $this->delTree($target);
1113
                    }
1114
                    if (rename($dir.DIRECTORY_SEPARATOR.$name, $target)) {
1115
                        $result[] = $target;
1116
                    }
1117
                }
1118
                if (!$result) {
1119
                    $this->delTree($dir);
1120
                    return false;
1121
                }
1122
            }
1123
1124
            is_dir($dir) && $this->delTree($dir);
1125
1126
            return (is_array($result) || file_exists($result)) ? $result : false;
1127
        }
1128
        //TODO: Add return statement here
1129
    }
1130
1131
    /**
1132
     * Create archive and return its path
1133
     *
1134
     * @param  string  $dir    target dir
1135
     * @param  array   $files  files names list
1136
     * @param  string  $name   archive name
1137
     * @param  array   $arc    archiver options
1138
     * @return string|bool
1139
     * @author Dmitry (dio) Levashov,
1140
     * @author Alexey Sukhotin
1141
     **/
1142
    protected function _archive($dir, $files, $name, $arc) {
1143
        return $this->makeArchive($dir, $files, $name, $arc);
1144
    }
1145
1146
    /******************** Over write functions *************************/
1147
1148
    /**
1149
     * File path of local server side work file path
1150
     *
1151
     * @param  string $path
1152
     * @return string
1153
     * @author Naoki Sawada
1154
     */
1155
    protected function getWorkFile($path) {
1156
        return $path;
1157
    }
1158
1159
    /**
1160
     * Delete dirctory trees
1161
     *
1162
     * @param string $localpath path need convert encoding to server encoding
1163
     * @return boolean
1164
     * @author Naoki Sawada
1165
     */
1166
    protected function delTree($localpath) {
1167
        return $this->rmdirRecursive($localpath);
1168
    }
1169
1170
    /******************** Over write (Optimized) functions *************************/
1171
1172
    /**
1173
     * Recursive files search
1174
     *
1175
     * @param  string  $path   dir path
1176
     * @param  string  $q      search string
1177
     * @param  array   $mimes
1178
     * @return array
1179
     * @author Dmitry (dio) Levashov
1180
     * @author Naoki Sawada
1181
     **/
1182
    protected function doSearch($path, $q, $mimes) {
1183
        if ($this->encoding || ! class_exists('FilesystemIterator', false)) {
1184
            // non UTF-8 use elFinderVolumeDriver::doSearch()
1185
            return parent::doSearch($path, $q, $mimes);
1186
        }
1187
1188
        $result = array();
1189
1190
        $timeout = $this->options['searchTimeout']? $this->searchStart + $this->options['searchTimeout'] : 0;
1191
        if ($timeout && $timeout < time()) {
1192
            $this->setError(elFinder::ERROR_SEARCH_TIMEOUT, $this->path($this->encode($path)));
1193
            return $result;
1194
        }
1195
        elFinder::extendTimeLimit($this->options['searchTimeout'] + 30);
1196
1197
        $match = array();
1198
        try {
1199
            $iterator = new RecursiveIteratorIterator(
1200
                new RecursiveCallbackFilterIterator(
1201
                    new RecursiveDirectoryIterator($path,
1202
                        FilesystemIterator::KEY_AS_PATHNAME |
1203
                        FilesystemIterator::SKIP_DOTS |
1204
                        ((defined('RecursiveDirectoryIterator::FOLLOW_SYMLINKS') && $this->options['followSymLinks'])?
1205
                            RecursiveDirectoryIterator::FOLLOW_SYMLINKS : 0)
1206
                    ),
1207
                    array($this, 'localFileSystemSearchIteratorFilter')
1208
                ),
1209
                RecursiveIteratorIterator::SELF_FIRST,
1210
                RecursiveIteratorIterator::CATCH_GET_CHILD
1211
            );
1212
            foreach ($iterator as $key => $node) {
1213
                if ($timeout && ($this->error || $timeout < time())) {
1214
                    !$this->error && $this->setError(elFinder::ERROR_SEARCH_TIMEOUT, $this->path($this->encode($node->getPath)));
1215
                    break;
1216
                }
1217
                if ($node->isDir()) {
1218
                    if ($this->stripos($node->getFilename(), $q) !== false) {
1219
                        $match[] = $key;
1220
                    }
1221
                } else {
1222
                    $match[] = $key;
1223
                }
1224
            }
1225
        } catch (Exception $e) {}
1226
1227
        if ($match) {
1228
            foreach($match as $p) {
1229
                if ($timeout && ($this->error || $timeout < time())) {
1230
                    !$this->error && $this->setError(elFinder::ERROR_SEARCH_TIMEOUT, $this->path($this->encode(dirname($p))));
1231
                    break;
1232
                }
1233
1234
                $stat = $this->stat($p);
1235
1236
                if (!$stat) { // invalid links
1237
                    continue;
1238
                }
1239
1240
                if (!empty($stat['hidden']) || !$this->mimeAccepted($stat['mime'], $mimes)) {
1241
                    continue;
1242
                }
1243
1244
                $name = $stat['name'];
1245
1246
                if ((!$mimes || $stat['mime'] !== 'directory')) {
1247
                    $stat['path'] = $this->path($stat['hash']);
1248
                    if ($this->URL && !isset($stat['url'])) {
1249
                        $_path = str_replace(DIRECTORY_SEPARATOR, '/', substr($p, strlen($this->root) + 1));
1250
                        $stat['url'] = $this->URL . $_path;
1251
                    }
1252
1253
                    $result[] = $stat;
1254
                }
1255
            }
1256
        }
1257
1258
        return $result;
1259
    }
1260
1261
    /******************** Original local functions ************************
1262
     * @param $file
1263
     * @param $key
1264
     * @param $iterator
1265
     * @return bool
1266
     */
1267
1268
    public function localFileSystemSearchIteratorFilter($file, $key, $iterator) {
1269
        $name = $file->getFilename();
1270
        if ($this->doSearchCurrentQuery['excludes']) {
1271
            foreach($this->doSearchCurrentQuery['excludes'] as $exclude) {
1272
                if ($this->stripos($name, $exclude) !== false) {
1273
                    return false;
1274
                }
1275
            }
1276
        }
1277
        if ($iterator->hasChildren()) {
1278
            if ($this->options['searchExDirReg'] && preg_match($this->options['searchExDirReg'], $key)) {
1279
                return false;
1280
            }
1281
            return (bool)$this->attr($key, 'read', null, true);
1282
        }
1283
        return ($this->stripos($name, $this->doSearchCurrentQuery['q']) === false)? false : true;
1284
    }
1285
1286
} // END class
1287
1288