Issues (3445)

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.

assets/ajax/fileManager/elFinder.class.php (55 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
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 14 and the first side effect is on line 2.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
defined('PH7') or exit('Restricted access');
3
if (!\PH7\Admin::auth()) exit('Restricted access'); // Accessible only for admins
4
5
/**
6
 * elFinder - file manager for web.
7
 * Core class.
8
 *
9
 * @package elfinder
10
 * @author Dmitry (dio) Levashov
11
 * @author Troex Nevelin
12
 * @author Alexey Sukhotin
13
 **/
14
class elFinder
15
{
16
17
    /**
18
     * API version number
19
     *
20
     * @var string
21
     **/
22
    protected $version = '2.1';
23
24
    /**
25
     * Storages (root dirs)
26
     *
27
     * @var array
28
     **/
29
    protected $volumes = array();
30
31
    /**
32
     * Network mount drivers
33
     *
34
     * @var array
35
     */
36
    public static $netDrivers = array();
37
38
    /**
39
     * elFinder global locale
40
     *
41
     * @var string
42
     */
43
    public static $locale = '';
44
45
    /**
46
     * elFinderVolumeDriver default mime.type file path
47
     *
48
     * @var string
49
     */
50
    public static $defaultMimefile = '';
51
52
    /**
53
     * elFinder session wrapper object
54
     *
55
     * @var elFinderSessionInterface
56
     */
57
    protected $session;
58
59
    /**
60
     * elFinder global sessionCacheKey
61
     *
62
     * @deprecated
63
     * @var string
64
     */
65
    public static $sessionCacheKey = '';
66
67
    /**
68
     * Is session closed
69
     *
70
     * @deprecated
71
     * @var bool
72
     */
73
    private static $sessionClosed = false;
74
75
    /**
76
     * elFinder base64encodeSessionData
77
     * elFinder save session data as `UTF-8`
78
     * If the session storage mechanism of the system does not allow `UTF-8`
79
     * And it must be `true` option 'base64encodeSessionData' of elFinder
80
     *
81
     * @var bool
82
     */
83
    protected static $base64encodeSessionData = false;
84
85
    /**
86
     * elFinder common tempraly path
87
     *
88
     * @var string
89
     **/
90
    protected static $commonTempPath = '';
91
92
    /**
93
     * Additional volume root options for network mounting volume
94
     *
95
     * @var array
96
     */
97
    protected $optionsNetVolumes = array();
98
99
    /**
100
     * Session key of net mount volumes
101
     *
102
     * @deprecated
103
     * @var string
104
     */
105
    protected $netVolumesSessionKey = '';
106
107
    /**
108
     * Mounted volumes count
109
     * Required to create unique volume id
110
     *
111
     * @var int
112
     **/
113
    public static $volumesCnt = 1;
114
115
    /**
116
     * Default root (storage)
117
     *
118
     * @var elFinderStorageDriver
119
     **/
120
    protected $default = null;
121
122
    /**
123
     * Commands and required arguments list
124
     *
125
     * @var array
126
     **/
127
    protected $commands = array(
128
        'open'      => array('target' => false, 'tree' => false, 'init' => false, 'mimes' => false, 'compare' => false),
129
        'ls'        => array('target' => true, 'mimes' => false, 'intersect' => false),
130
        'tree'      => array('target' => true),
131
        'parents'   => array('target' => true),
132
        'tmb'       => array('targets' => true),
133
        'file'      => array('target' => true, 'download' => false),
134
        'zipdl'     => array('targets' => true, 'download' => false),
135
        'size'      => array('targets' => true),
136
        'mkdir'     => array('target' => true, 'name' => false, 'dirs' => false),
137
        'mkfile'    => array('target' => true, 'name' => true, 'mimes' => false),
138
        'rm'        => array('targets' => true),
139
        'rename'    => array('target' => true, 'name' => true, 'mimes' => false),
140
        'duplicate' => array('targets' => true, 'suffix' => false),
141
        'paste'     => array('dst' => true, 'targets' => true, 'cut' => false, 'mimes' => false, 'renames' => false, 'hashes' => false, 'suffix' => false),
142
        'upload'    => array('target' => true, 'FILES' => true, 'mimes' => false, 'html' => false, 'upload' => false, 'name' => false, 'upload_path' => false, 'chunk' => false, 'cid' => false, 'node' => false, 'renames' => false, 'hashes' => false, 'suffix' => false, 'mtime' => false),
143
        'get'       => array('target' => true, 'conv' => false),
144
        'put'       => array('target' => true, 'content' => '', 'mimes' => false),
145
        'archive'   => array('targets' => true, 'type' => true, 'mimes' => false, 'name' => false),
146
        'extract'   => array('target' => true, 'mimes' => false, 'makedir' => false),
147
        'search'    => array('q' => true, 'mimes' => false, 'target' => false),
148
        'info'      => array('targets' => true, 'compare' => false),
149
        'dim'       => array('target' => true),
150
        'resize'    => array('target' => true, 'width' => true, 'height' => true, 'mode' => false, 'x' => false, 'y' => false, 'degree' => false, 'quality' => false),
151
        'netmount'  => array('protocol' => true, 'host' => true, 'path' => false, 'port' => false, 'user' => false, 'pass' => false, 'alias' => false, 'options' => false),
152
        'url'       => array('target' => true, 'options' => false),
153
        'callback'  => array('node' => true, 'json' => false, 'bind' => false, 'done' => false),
154
        'chmod'     => array('targets' => true, 'mode' => true)
155
    );
156
157
    /**
158
     * Plugins instance
159
     *
160
     * @var array
161
     **/
162
    protected $plugins = array();
163
164
    /**
165
     * Commands listeners
166
     *
167
     * @var array
168
     **/
169
    protected $listeners = array();
170
171
    /**
172
     * script work time for debug
173
     *
174
     * @var string
175
     **/
176
    protected $time = 0;
177
    /**
178
     * Is elFinder init correctly?
179
     *
180
     * @var bool
181
     **/
182
    protected $loaded = false;
183
    /**
184
     * Send debug to client?
185
     *
186
     * @var string
187
     **/
188
    protected $debug = false;
189
190
    /**
191
     * Call `session_write_close()` before exec command?
192
     *
193
     * @var bool
194
     */
195
    protected $sessionCloseEarlier = true;
196
197
    /**
198
     * SESSION use commands @see __construct()
199
     *
200
     * @var array
201
     */
202
    protected $sessionUseCmds = array();
203
204
    /**
205
     * session expires timeout
206
     *
207
     * @var int
208
     **/
209
    protected $timeout = 0;
210
211
    /**
212
     * Temp dir path for Upload
213
     *
214
     * @var string
215
     */
216
    protected $uploadTempPath = '';
217
218
    /**
219
     * Max allowed archive files size (0 - no limit)
220
     *
221
     * @var integer
222
     */
223
    protected $maxArcFilesSize = 0;
224
225
    /**
226
     * undocumented class variable
227
     *
228
     * @var string
229
     **/
230
    protected $uploadDebug = '';
231
232
    /**
233
     * Errors from PHP
234
     *
235
     * @var array
236
     **/
237
    public static $phpErrors = array();
238
239
    /**
240
     * Errors from not mounted volumes
241
     *
242
     * @var array
243
     **/
244
    public $mountErrors = array();
245
246
    /**
247
     * URL for callback output window for CORS
248
     * redirect to this URL when callback output
249
     *
250
     * @var string URL
251
     */
252
    protected $callbackWindowURL = '';
253
254
    // Errors messages
255
    const ERROR_UNKNOWN           = 'errUnknown';
256
    const ERROR_UNKNOWN_CMD       = 'errUnknownCmd';
257
    const ERROR_CONF              = 'errConf';
258
    const ERROR_CONF_NO_JSON      = 'errJSON';
259
    const ERROR_CONF_NO_VOL       = 'errNoVolumes';
260
    const ERROR_INV_PARAMS        = 'errCmdParams';
261
    const ERROR_OPEN              = 'errOpen';
262
    const ERROR_DIR_NOT_FOUND     = 'errFolderNotFound';
263
    const ERROR_FILE_NOT_FOUND    = 'errFileNotFound';     // 'File not found.'
264
    const ERROR_TRGDIR_NOT_FOUND  = 'errTrgFolderNotFound'; // 'Target folder "$1" not found.'
265
    const ERROR_NOT_DIR           = 'errNotFolder';
266
    const ERROR_NOT_FILE          = 'errNotFile';
267
    const ERROR_PERM_DENIED       = 'errPerm';
268
    const ERROR_LOCKED            = 'errLocked';        // '"$1" is locked and can not be renamed, moved or removed.'
269
    const ERROR_EXISTS            = 'errExists';        // 'File named "$1" already exists.'
270
    const ERROR_INVALID_NAME      = 'errInvName';       // 'Invalid file name.'
271
    const ERROR_MKDIR             = 'errMkdir';
272
    const ERROR_MKFILE            = 'errMkfile';
273
    const ERROR_RENAME            = 'errRename';
274
    const ERROR_COPY              = 'errCopy';
275
    const ERROR_MOVE              = 'errMove';
276
    const ERROR_COPY_FROM         = 'errCopyFrom';
277
    const ERROR_COPY_TO           = 'errCopyTo';
278
    const ERROR_COPY_ITSELF       = 'errCopyInItself';
279
    const ERROR_REPLACE           = 'errReplace';          // 'Unable to replace "$1".'
280
    const ERROR_RM                = 'errRm';               // 'Unable to remove "$1".'
281
    const ERROR_RM_SRC            = 'errRmSrc';            // 'Unable remove source file(s)'
282
    const ERROR_MKOUTLINK         = 'errMkOutLink';        // 'Unable to create a link to outside the volume root.'
283
    const ERROR_UPLOAD            = 'errUpload';           // 'Upload error.'
284
    const ERROR_UPLOAD_FILE       = 'errUploadFile';       // 'Unable to upload "$1".'
285
    const ERROR_UPLOAD_NO_FILES   = 'errUploadNoFiles';    // 'No files found for upload.'
286
    const ERROR_UPLOAD_TOTAL_SIZE = 'errUploadTotalSize';  // 'Data exceeds the maximum allowed size.'
287
    const ERROR_UPLOAD_FILE_SIZE  = 'errUploadFileSize';   // 'File exceeds maximum allowed size.'
288
    const ERROR_UPLOAD_FILE_MIME  = 'errUploadMime';       // 'File type not allowed.'
289
    const ERROR_UPLOAD_TRANSFER   = 'errUploadTransfer';   // '"$1" transfer error.'
290
    const ERROR_UPLOAD_TEMP       = 'errUploadTemp';       // 'Unable to make temporary file for upload.'
291
    const ERROR_ACCESS_DENIED     = 'errAccess';
292
    const ERROR_NOT_REPLACE       = 'errNotReplace';       // Object "$1" already exists at this location and can not be replaced with object of another type.
293
    const ERROR_SAVE              = 'errSave';
294
    const ERROR_EXTRACT           = 'errExtract';
295
    const ERROR_ARCHIVE           = 'errArchive';
296
    const ERROR_NOT_ARCHIVE       = 'errNoArchive';
297
    const ERROR_ARCHIVE_TYPE      = 'errArcType';
298
    const ERROR_ARC_SYMLINKS      = 'errArcSymlinks';
299
    const ERROR_ARC_MAXSIZE       = 'errArcMaxSize';
300
    const ERROR_RESIZE            = 'errResize';
301
    const ERROR_UNSUPPORT_TYPE    = 'errUsupportType';
302
    const ERROR_CONV_UTF8         = 'errConvUTF8';
303
    const ERROR_NOT_UTF8_CONTENT  = 'errNotUTF8Content';
304
    const ERROR_NETMOUNT          = 'errNetMount';
305
    const ERROR_NETUNMOUNT        = 'errNetUnMount';
306
    const ERROR_NETMOUNT_NO_DRIVER = 'errNetMountNoDriver';
307
    const ERROR_NETMOUNT_FAILED       = 'errNetMountFailed';
308
309
    const ERROR_SESSION_EXPIRES     = 'errSessionExpires';
310
311
    const ERROR_CREATING_TEMP_DIR     = 'errCreatingTempDir';
312
    const ERROR_FTP_DOWNLOAD_FILE     = 'errFtpDownloadFile';
313
    const ERROR_FTP_UPLOAD_FILE     = 'errFtpUploadFile';
314
    const ERROR_FTP_MKDIR         = 'errFtpMkdir';
315
    const ERROR_ARCHIVE_EXEC     = 'errArchiveExec';
316
    const ERROR_EXTRACT_EXEC     = 'errExtractExec';
317
    const ERROR_SEARCH_TIMEOUT    = 'errSearchTimeout';    // 'Timed out while searching "$1". Search result is partial.'
318
    const ERROR_REAUTH_REQUIRE  = 'errReauthRequire';  // 'Re-authorization is required.'
319
320
    /**
321
     * Constructor
322
     *
323
     * @param  array  elFinder and roots configurations
324
     * @author Dmitry (dio) Levashov
325
     */
326
    public function __construct($opts)
0 ignored issues
show
__construct uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
__construct uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
__construct uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
327
    {
328
        // set error handler of WARNING, NOTICE
329
        set_error_handler('elFinder::phpErrorHandler', E_WARNING | E_NOTICE | E_USER_WARNING | E_USER_NOTICE);
330
331
        // setup debug mode
332
        $this->debug = (isset($opts['debug']) && $opts['debug'] ? true : false);
0 ignored issues
show
Documentation Bug introduced by
The property $debug was declared of type string, but isset($opts['debug']) &&...'debug'] ? true : false is of type boolean. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
333
        if ($this->debug) {
334
            error_reporting(-1);
335
            ini_set('diaplay_errors', '1');
336
        }
337
338
        if (!interface_exists('elFinderSessionInterface')) {
339
            include_once dirname(__FILE__) . '/elFinderSessionInterface.php';
340
        }
341
342
        // session handler
343
        if (!empty($opts['session']) && $opts['session'] instanceof elFinderSessionInterface) {
344
            $this->session = $opts['session'];
345
        } else {
346
            $sessionOpts = array(
347
                'base64encode' => !empty($opts['base64encodeSessionData']),
348
                'keys' => array(
349
                    'default'   => !empty($opts['sessionCacheKey']) ? $opts['sessionCacheKey'] : 'elFinderCaches',
350
                    'netvolume' => !empty($opts['netVolumesSessionKey'])? $opts['netVolumesSessionKey'] : 'elFinderNetVolumes'
351
                )
352
            );
353
            if (! class_exists('elFinderSession')) {
354
                include_once dirname(__FILE__) . '/elFinderSession.php';
355
            }
356
            $this->session = new elFinderSession($sessionOpts);
357
        }
358
        // try session start | restart
359
        $this->session->start();
360
361
        $sessionUseCmds = array();
362
        if (isset($opts['sessionUseCmds']) && is_array($opts['sessionUseCmds'])) {
363
            $sessionUseCmds = $opts['sessionUseCmds'];
364
        }
365
366
        // set self::$volumesCnt by HTTP header "X-elFinder-VolumesCntStart"
367
        if (isset($_SERVER['HTTP_X_ELFINDER_VOLUMESCNTSTART']) && ($volumesCntStart = intval($_SERVER['HTTP_X_ELFINDER_VOLUMESCNTSTART']))) {
368
            self::$volumesCnt = $volumesCntStart;
369
        }
370
371
        $this->time  = $this->utime();
0 ignored issues
show
Documentation Bug introduced by
The property $time was declared of type string, but $this->utime() is of type double. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
372
        $this->sessionCloseEarlier = isset($opts['sessionCloseEarlier'])? (bool)$opts['sessionCloseEarlier'] : true;
373
        $this->sessionUseCmds = array_flip($sessionUseCmds);
374
        $this->timeout = (isset($opts['timeout']) ? $opts['timeout'] : 0);
375
        $this->uploadTempPath = (isset($opts['uploadTempPath']) ? $opts['uploadTempPath'] : '');
376
        $this->callbackWindowURL = (isset($opts['callbackWindowURL']) ? $opts['callbackWindowURL'] : '');
377
        elFinder::$commonTempPath = (isset($opts['commonTempPath']) ? $opts['commonTempPath'] : './.tmp');
378
        if (!is_writable(elFinder::$commonTempPath)) {
379
            elFinder::$commonTempPath = '';
380
        }
381
        $this->maxArcFilesSize = isset($opts['maxArcFilesSize'])? intval($opts['maxArcFilesSize']) : 0;
382
        $this->optionsNetVolumes = (isset($opts['optionsNetVolumes']) && is_array($opts['optionsNetVolumes']))? $opts['optionsNetVolumes'] : array();
383
384
        // deprecated settings
385
        $this->netVolumesSessionKey = !empty($opts['netVolumesSessionKey'])? $opts['netVolumesSessionKey'] : 'elFinderNetVolumes';
0 ignored issues
show
Deprecated Code introduced by
The property elFinder::$netVolumesSessionKey has been deprecated.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
386
        self::$sessionCacheKey = !empty($opts['sessionCacheKey']) ? $opts['sessionCacheKey'] : 'elFinderCaches';
0 ignored issues
show
Deprecated Code introduced by
The property elFinder::$sessionCacheKey has been deprecated.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
387
388
        // check session cache
389
        $_optsMD5 = md5(json_encode($opts['roots']));
390
        if ($this->session->get('_optsMD5') !== $_optsMD5) {
391
            $this->session->set('_optsMD5', $_optsMD5);
392
        }
393
394
        // setlocale and global locale regists to elFinder::locale
395
        self::$locale = !empty($opts['locale']) ? $opts['locale'] : 'en_US.UTF-8';
396
        if (false === setlocale(LC_ALL, self::$locale)) {
397
            self::$locale = setlocale(LC_ALL, '');
398
        }
399
400
        // set defaultMimefile
401
        elFinder::$defaultMimefile = (isset($opts['defaultMimefile']) ? $opts['defaultMimefile'] : '');
402
403
        // bind events listeners
404
        if (!empty($opts['bind']) && is_array($opts['bind'])) {
405
            $_req = $_SERVER["REQUEST_METHOD"] == 'POST' ? $_POST : $_GET;
406
            $_reqCmd = isset($_req['cmd']) ? $_req['cmd'] : '';
407
            foreach ($opts['bind'] as $cmd => $handlers) {
408
                $doRegist = (strpos($cmd, '*') !== false);
409
                if (! $doRegist) {
410
                    $_getcmd = create_function('$cmd', 'list($ret) = explode(\'.\', $cmd);return trim($ret);');
0 ignored issues
show
Security Best Practice introduced by
The use of create_function is highly discouraged, better use a closure.

create_function can pose a great security vulnerability as it is similar to eval, and could be used for arbitrary code execution. We highly recommend to use a closure instead.

// Instead of
$function = create_function('$a, $b', 'return $a + $b');

// Better use
$function = function($a, $b) { return $a + $b; }
Loading history...
411
                    $doRegist = ($_reqCmd && in_array($_reqCmd, array_map($_getcmd, explode(' ', $cmd))));
412
                }
413
                if ($doRegist) {
414
                    // for backward compatibility
415
                    if (! is_array($handlers)) {
416
                        $handlers = array($handlers);
417
                    } else {
418
                        if (count($handlers) === 2 && is_object($handlers[0])) {
419
                            $handlers = array($handlers);
420
                        }
421
                    }
422
                    foreach($handlers as $handler) {
423
                        if ($handler) {
424
                            if (is_string($handler) && strpos($handler, '.')) {
425
                                list($_domain, $_name, $_method) = array_pad(explode('.', $handler), 3, '');
426
                                if (strcasecmp($_domain, 'plugin') === 0) {
427
                                    if ($plugin = $this->getPluginInstance($_name, isset($opts['plugin'][$_name])? $opts['plugin'][$_name] : array())
428
                                            and method_exists($plugin, $_method)) {
429
                                        $this->bind($cmd, array($plugin, $_method));
430
                                    }
431
                                }
432
                            } else {
433
                                $this->bind($cmd, $handler);
434
                            }
435
                        }
436
                    }
437
                }
438
            }
439
        }
440
441
        if (!isset($opts['roots']) || !is_array($opts['roots'])) {
442
            $opts['roots'] = array();
443
        }
444
445
        // check for net volumes stored in session
446
        $netVolumes = $this->getNetVolumes();
447
        foreach ($netVolumes as $key => $root) {
448
            if (! isset($root['id'])) {
449
                // given fixed unique id
450
                if (! $root['id'] = $this->getNetVolumeUniqueId($netVolumes)) {
451
                    $this->mountErrors[] = 'Netmount Driver "'.$root['driver'].'" : Could\'t given volume id.';
452
                    continue;
453
                }
454
            }
455
            $opts['roots'][$key] = $root;
456
        }
457
458
        // "mount" volumes
459
        foreach ($opts['roots'] as $i => $o) {
460
            $class = 'elFinderVolume'.(isset($o['driver']) ? $o['driver'] : '');
461
462
            if (class_exists($class)) {
463
                $volume = new $class();
464
465
                try {
466
                    if ($this->maxArcFilesSize && (empty($o['maxArcFilesSize']) || $this->maxArcFilesSize < $o['maxArcFilesSize'])) {
467
                        $o['maxArcFilesSize'] = $this->maxArcFilesSize;
468
                    }
469
                    // pass session handler
470
                    $volume->setSession($this->session);
471
                    if ($volume->mount($o)) {
472
                        // unique volume id (ends on "_") - used as prefix to files hash
473
                        $id = $volume->id();
474
475
                        $this->volumes[$id] = $volume;
476
                        if ((!$this->default || $volume->root() !== $volume->defaultPath()) && $volume->isReadable()) {
477
                            $this->default = $this->volumes[$id];
478
                        }
479
                    } else {
480
                        $this->removeNetVolume($i, $volume);
481
                        $this->mountErrors[] = 'Driver "'.$class.'" : '.implode(' ', $volume->error());
482
                    }
483
                } catch (Exception $e) {
484
                    $this->removeNetVolume($i, $volume);
485
                    $this->mountErrors[] = 'Driver "'.$class.'" : '.$e->getMessage();
486
                }
487
            } else {
488
                $this->mountErrors[] = 'Driver "'.$class.'" does not exist';
489
            }
490
        }
491
492
        // if at least one readable volume - ii desu >_<
493
        $this->loaded = !empty($this->default);
494
495
        // restore error handler for now
496
        restore_error_handler();
497
    }
498
499
    /**
500
     * Return elFinder session wrapper instance
501
     *
502
     * @return  object  elFinderSessionInterface
503
     **/
504
    public function getSession() {
505
        return $this->session;
506
    }
507
508
    /**
509
     * Return true if fm init correctly
510
     *
511
     * @return bool
512
     * @author Dmitry (dio) Levashov
513
     **/
514
    public function loaded() {
515
        return $this->loaded;
516
    }
517
518
    /**
519
     * Return version (api) number
520
     *
521
     * @return string
522
     * @author Dmitry (dio) Levashov
523
     **/
524
    public function version() {
525
        return $this->version;
526
    }
527
528
    /**
529
     * Add handler to elFinder command
530
     *
531
     * @param  string  command name
532
     * @param  string|array  callback name or array(object, method)
533
     * @return elFinder
534
     * @author Dmitry (dio) Levashov
535
     **/
536
    public function bind($cmd, $handler) {
537
        $allCmds = array_keys($this->commands);
538
        $cmds = array();
539
        foreach(explode(' ', $cmd) as $_cmd) {
540
            if ($_cmd !== '') {
541
                if ($all = strpos($_cmd, '*') !== false) {
0 ignored issues
show
$all is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
542
                    list(, $sub) = array_pad(explode('.', $_cmd), 2, '');
543
                    if ($sub) {
544
                        $sub = str_replace('\'', '\\\'', $sub);
545
                        $addSub = create_function('$cmd', 'return $cmd . \'.\' . trim(\'' . $sub . '\');');
0 ignored issues
show
Security Best Practice introduced by
The use of create_function is highly discouraged, better use a closure.

create_function can pose a great security vulnerability as it is similar to eval, and could be used for arbitrary code execution. We highly recommend to use a closure instead.

// Instead of
$function = create_function('$a, $b', 'return $a + $b');

// Better use
$function = function($a, $b) { return $a + $b; }
Loading history...
546
                        $cmds = array_merge($cmds, array_map($addSub, $allCmds));
547
                    } else {
548
                        $cmds = array_merge($cmds, $allCmds);
549
                    }
550
                } else {
551
                    $cmds[] = $_cmd;
552
                }
553
            }
554
        }
555
        $cmds = array_unique($cmds);
556
557
        foreach ($cmds as $cmd) {
558
            if (!isset($this->listeners[$cmd])) {
559
                $this->listeners[$cmd] = array();
560
            }
561
562
            if (is_callable($handler)) {
563
                $this->listeners[$cmd][] = $handler;
564
            }
565
        }
566
567
        return $this;
568
    }
569
570
    /**
571
     * Remove event (command exec) handler
572
     *
573
     * @param  string  command name
574
     * @param  string|array  callback name or array(object, method)
575
     * @return elFinder
576
     * @author Dmitry (dio) Levashov
577
     **/
578
    public function unbind($cmd, $handler) {
579
        if (!empty($this->listeners[$cmd])) {
580
            foreach ($this->listeners[$cmd] as $i => $h) {
581
                if ($h === $handler) {
582
                    unset($this->listeners[$cmd][$i]);
583
                    return $this;
584
                }
585
            }
586
        }
587
        return $this;
588
    }
589
590
    /**
591
     * Return true if command exists
592
     *
593
     * @param  string  command name
594
     * @return bool
595
     * @author Dmitry (dio) Levashov
596
     **/
597
    public function commandExists($cmd) {
598
        return $this->loaded && isset($this->commands[$cmd]) && method_exists($this, $cmd);
599
    }
600
601
    /**
602
     * Return root - file's owner (public func of volume())
603
     *
604
     * @param  string  file hash
605
     * @return elFinderStorageDriver
606
     * @author Naoki Sawada
607
     */
608
    public function getVolume($hash) {
609
        return $this->volume($hash);
610
    }
611
612
    /**
613
     * Return command required arguments info
614
     *
615
     * @param  string  command name
616
     * @return array
617
     * @author Dmitry (dio) Levashov
618
     **/
619
    public function commandArgsList($cmd) {
620
        return $this->commandExists($cmd) ? $this->commands[$cmd] : array();
621
    }
622
623
    private function session_expires() {
624
625
        if (! $last = $this->session->get(':LAST_ACTIVITY')) {
626
            $this->session->set(':LAST_ACTIVITY', time());
627
            return false;
628
        }
629
630
        if ( ($this->timeout > 0) && (time() - $last > $this->timeout) ) {
631
            return true;
632
        }
633
634
        $this->session->set(':LAST_ACTIVITY', time());
635
        return false;
636
    }
637
638
    /**
639
     * Exec command and return result
640
     *
641
     * @param  string  $cmd  command name
642
     * @param  array   $args command arguments
643
     * @return array
644
     * @author Dmitry (dio) Levashov
645
     **/
646
    public function exec($cmd, $args) {
647
        // set error handler of WARNING, NOTICE
648
        set_error_handler('elFinder::phpErrorHandler', E_WARNING | E_NOTICE | E_USER_WARNING | E_USER_NOTICE);
649
650
        if (!$this->loaded) {
651
            return array('error' => $this->error(self::ERROR_CONF, self::ERROR_CONF_NO_VOL));
652
        }
653
654
        if ($this->session_expires()) {
655
            return array('error' => $this->error(self::ERROR_SESSION_EXPIRES));
656
        }
657
658
        if (!$this->commandExists($cmd)) {
659
            return array('error' => $this->error(self::ERROR_UNKNOWN_CMD));
660
        }
661
662
        if (!empty($args['mimes']) && is_array($args['mimes'])) {
663
            foreach ($this->volumes as $id => $v) {
664
                $this->volumes[$id]->setMimesFilter($args['mimes']);
665
            }
666
        }
667
668
        // detect destination dirHash and volume
669
        $dstVolume = false;
670
        $dst = ! empty($args['target'])? $args['target'] : (! empty($args['dst'])? $args['dst'] : '');
671
        if ($dst) {
672
            $dstVolume = $this->volume($dst);
673
        } else if (isset($args['targets']) && is_array($args['targets']) && isset($args['targets'][0])) {
674
            $dst = $args['targets'][0];
675
            $dstVolume = $this->volume($dst);
676
            if (($_stat = $dstVolume->file($dst)) && ! empty($_stat['phash'])) {
677
                $dst = $_stat['phash'];
678
            } else {
679
                $dst = '';
680
            }
681
        }
682
683
        // call pre handlers for this command
684
        $args['sessionCloseEarlier'] = isset($this->sessionUseCmds[$cmd])? false : $this->sessionCloseEarlier;
685
        if (!empty($this->listeners[$cmd.'.pre'])) {
686
            foreach ($this->listeners[$cmd.'.pre'] as $handler) {
687
                call_user_func_array($handler, array($cmd, &$args, $this, $dstVolume));
688
            }
689
        }
690
691
        // unlock session data for multiple access
692
        if ($this->sessionCloseEarlier && $args['sessionCloseEarlier']) {
693
            $this->session->close();
694
            // deprecated property
695
            elFinder::$sessionClosed = true;
0 ignored issues
show
Deprecated Code introduced by
The property elFinder::$sessionClosed has been deprecated.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
696
        }
697
698
        if (substr(PHP_OS,0,3) === 'WIN') {
699
            // set time out
700
            elFinder::extendTimeLimit(300);
701
        }
702
703
        try {
704
            $result = $this->$cmd($args);
705
        } catch (Exception $e) {
706
            $result = array(
707
                'error' => htmlspecialchars($e->getMessage()),
708
                'sync' => true
709
            );
710
        }
711
712
        // check change dstDir
713
        $changeDst = false;
714
        if ($dst && $dstVolume && (! empty($result['added']) || ! empty($result['removed']))) {
715
            $changeDst = true;
716
        }
717
718
        foreach ($this->volumes as $volume) {
719
            if (isset($result['removed'])) {
720
                $result['removed'] = array_merge($result['removed'], $volume->removed());
721
                if (! $changeDst && $dst && $dstVolume && $volume === $dstVolume) {
722
                    $changeDst = true;
723
                }
724
            }
725
            if (isset($result['added'])) {
726
                $result['added'] = array_merge($result['added'], $volume->added());
727
                if (! $changeDst && $dst && $dstVolume && $volume === $dstVolume) {
728
                    $changeDst = true;
729
                }
730
            }
731
            $volume->resetResultStat();
732
        }
733
734
        // dstDir is changed
735
        if ($changeDst) {
736
            if ($dstDir = $dstVolume->dir($dst)) {
737
                if (! isset($result['changed'])) {
738
                    $result['changed'] = array();
739
                }
740
                $result['changed'][] = $dstDir;
741
            }
742
        }
743
744
        // call handlers for this command
745
        if (!empty($this->listeners[$cmd])) {
746
            foreach ($this->listeners[$cmd] as $handler) {
747
                if (call_user_func_array($handler,array($cmd, &$result, $args, $this, $dstVolume))) {
748
                    // handler return true to force sync client after command completed
749
                    $result['sync'] = true;
750
                }
751
            }
752
        }
753
754
        // replace removed files info with removed files hashes
755
        if (!empty($result['removed'])) {
756
            $removed = array();
757
            foreach ($result['removed'] as $file) {
758
                $removed[] = $file['hash'];
759
            }
760
            $result['removed'] = array_unique($removed);
761
        }
762
        // remove hidden files and filter files by mimetypes
763
        if (!empty($result['added'])) {
764
            $result['added'] = $this->filter($result['added']);
765
        }
766
        // remove hidden files and filter files by mimetypes
767
        if (!empty($result['changed'])) {
768
            $result['changed'] = $this->filter($result['changed']);
769
        }
770
771
        if ($this->debug || !empty($args['debug'])) {
772
            $result['debug'] = array(
773
                'connector' => 'php',
774
                'phpver'    => PHP_VERSION,
775
                'time'      => $this->utime() - $this->time,
776
                'memory'    => (function_exists('memory_get_peak_usage') ? ceil(memory_get_peak_usage()/1024).'Kb / ' : '').ceil(memory_get_usage()/1024).'Kb / '.ini_get('memory_limit'),
777
                'upload'    => $this->uploadDebug,
778
                'volumes'   => array(),
779
                'mountErrors' => $this->mountErrors,
780
                'phpErrors' => elFinder::$phpErrors
781
            );
782
            elFinder::$phpErrors = array();
783
784
            foreach ($this->volumes as $id => $volume) {
785
                $result['debug']['volumes'][] = $volume->debug();
786
            }
787
        }
788
789
        foreach ($this->volumes as $volume) {
790
            $volume->umount();
791
        }
792
793
        if (!empty($result['callback'])) {
794
            $result['callback']['json'] = json_encode($result);
795
            $this->callback($result['callback']);
796
        } else {
797
            return $result;
798
        }
799
        //TODO: Add return statement here
800
    }
801
802
    /**
803
     * Return file real path
804
     *
805
     * @param  string  $hash  file hash
806
     * @return string
807
     * @author Dmitry (dio) Levashov
808
     **/
809
    public function realpath($hash)    {
810
        if (($volume = $this->volume($hash)) == false) {
811
            return false;
812
        }
813
        return $volume->realpath($hash);
814
    }
815
816
    /**
817
     * Return network volumes config.
818
     *
819
     * @return array
820
     * @author Dmitry (dio) Levashov
821
     */
822
    protected function getNetVolumes() {
823
        if ($data = $this->session->get('netvolume', array())) {
824
            return $data;
825
        }
826
        return array();
827
    }
828
829
    /**
830
     * Save network volumes config.
831
     *
832
     * @param  array  $volumes  volumes config
833
     * @return void
834
     * @author Dmitry (dio) Levashov
835
     */
836
    protected function saveNetVolumes($volumes) {
837
        $this->session->set('netvolume', $volumes);
838
    }
839
840
    /**
841
     * Remove netmount volume
842
     *
843
     * @param string $key netvolume key
844
     * @param object $volume volume driver instance
845
     * @return bool
846
     */
847
    protected function removeNetVolume($key, $volume) {
848
        $netVolumes = $this->getNetVolumes();
849
        $res = true;
850
        if (is_object($volume) && method_exists($volume, 'netunmount')) {
851
            $res = $volume->netunmount($netVolumes, $key);
852
        }
853
        if ($res) {
854
            if (is_string($key) && isset($netVolumes[$key])) {
855
                unset($netVolumes[$key]);
856
                $this->saveNetVolumes($netVolumes);
857
                return true;
858
            }
859
        }
860
        return false;
861
    }
862
863
    /**
864
     * Get plugin instance & set to $this->plugins
865
     *
866
     * @param  string $name   Plugin name (dirctory name)
867
     * @param  array  $opts   Plugin options (optional)
868
     * @return object | bool Plugin object instance Or false
869
     * @author Naoki Sawada
870
     */
871
    protected function getPluginInstance($name, $opts = array()) {
872
        $key = strtolower($name);
873
        if (! isset($this->plugins[$key])) {
874
            $class = 'elFinderPlugin' . $name;
875
            // to try auto load
876
            if (! class_exists($class)) {
877
                $p_file = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . $name . DIRECTORY_SEPARATOR . 'plugin.php';
878
                if (is_file($p_file)) {
879
                    include_once $p_file;
880
                }
881
            }
882
            if (class_exists($class, false)) {
883
                $this->plugins[$key] = new $class($opts);
884
            } else {
885
                $this->plugins[$key] = false;
886
            }
887
        }
888
        return $this->plugins[$key];
889
    }
890
891
    /***************************************************************************/
892
    /*                                 commands                                */
893
    /***************************************************************************/
894
895
    /**
896
     * Normalize error messages
897
     *
898
     * @return array
899
     * @author Dmitry (dio) Levashov
900
     **/
901
    public function error() {
902
        $errors = array();
903
904
        foreach (func_get_args() as $msg) {
905
            if (is_array($msg)) {
906
                $errors = array_merge($errors, $msg);
907
            } else {
908
                $errors[] = $msg;
909
            }
910
        }
911
912
        return count($errors) ? $errors : array(self::ERROR_UNKNOWN);
913
    }
914
915
    protected function netmount($args) {
916
        $options  = array();
917
        $protocol = $args['protocol'];
918
919
        if ($protocol === 'netunmount') {
920
            if (! empty($args['user']) && $volume = $this->volume($args['user'])) {
921
                if ($this->removeNetVolume($args['host'], $volume)) {
922
                    return array('removed' => array(array('hash' => $volume->root())));
923
                }
924
            }
925
            return array('sync' => true, 'error' => $this->error(self::ERROR_NETUNMOUNT));
926
        }
927
928
        $driver   = isset(self::$netDrivers[$protocol]) ? self::$netDrivers[$protocol] : '';
929
        $class    = 'elFinderVolume'.$driver;
930
931
        if (!class_exists($class)) {
932
            return array('error' => $this->error(self::ERROR_NETMOUNT, $args['host'], self::ERROR_NETMOUNT_NO_DRIVER));
933
        }
934
935
        if (!$args['path']) {
936
            $args['path'] = '/';
937
        }
938
939
        foreach ($args as $k => $v) {
940
            if ($k != 'options' && $k != 'protocol' && $v) {
941
                $options[$k] = $v;
942
            }
943
        }
944
945
        if (is_array($args['options'])) {
946
            foreach ($args['options'] as $key => $value) {
947
                $options[$key] = $value;
948
            }
949
        }
950
951
        $volume = new $class();
952
953
        // pass session handler
954
        $volume->setSession($this->session);
955
956
        if (method_exists($volume, 'netmountPrepare')) {
957
            $options = $volume->netmountPrepare($options);
958
            if (isset($options['exit'])) {
959
                if ($options['exit'] === 'callback') {
960
                    $this->callback($options['out']);
961
                }
962
                return $options;
963
            }
964
        }
965
966
        $netVolumes = $this->getNetVolumes();
967
968
        if (! isset($options['id'])) {
969
            // given fixed unique id
970
            if (! $options['id'] = $this->getNetVolumeUniqueId($netVolumes)) {
971
                return array('error' => $this->error(self::ERROR_NETMOUNT, $args['host'], 'Could\'t given volume id.'));
972
            }
973
        }
974
975
        // load additional volume root options
976
        if (! empty($this->optionsNetVolumes['*'])) {
977
            $options = array_merge($options, $this->optionsNetVolumes['*']);
978
        }
979
        if (! empty($this->optionsNetVolumes[$protocol])) {
980
            $options = array_merge($options, $this->optionsNetVolumes[$protocol]);
981
        }
982
983
        if (! $key =  $volume->netMountKey) {
984
            $key = md5($protocol . '-' . serialize($options));
985
        }
986
        $options['netkey'] = $key;
987
988
        if ($volume->mount($options)) {
989
            $options['driver'] = $driver;
990
            $netVolumes[$key]  = $options;
991
            $this->saveNetVolumes($netVolumes);
992
            $rootstat = $volume->file($volume->root());
993
            return array('added' => array($rootstat));
994
        } else {
995
            $this->removeNetVolume(null, $volume);
996
            return array('error' => $this->error(self::ERROR_NETMOUNT, $args['host'], implode(' ', $volume->error())));
997
        }
998
    }
999
1000
    /**
1001
     * "Open" directory
1002
     * Return array with following elements
1003
     *  - cwd          - opened dir info
1004
     *  - files        - opened dir content [and dirs tree if $args[tree]]
1005
     *  - api          - api version (if $args[init])
1006
     *  - uplMaxSize   - if $args[init]
1007
     *  - error        - on failed
1008
     *
1009
     * @param  array  command arguments
1010
     * @return array
1011
     * @author Dmitry (dio) Levashov
1012
     **/
1013
    protected function open($args) {
1014
        $target = $args['target'];
1015
        $init   = !empty($args['init']);
1016
        $tree   = !empty($args['tree']);
1017
        $volume = $this->volume($target);
1018
        $cwd    = $volume ? $volume->dir($target) : false;
1019
        $hash   = $init ? 'default folder' : '#'.$target;
1020
        $sleep  = 0;
0 ignored issues
show
$sleep is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1021
        $compare = '';
1022
1023
        // on init request we can get invalid dir hash -
1024
        // dir which can not be opened now, but remembered by client,
1025
        // so open default dir
1026
        if ((!$cwd || !$cwd['read']) && $init) {
1027
            $volume = $this->default;
1028
            $cwd    = $volume->dir($volume->defaultPath());
1029
        }
1030
1031
        if (!$cwd) {
1032
            return array('error' => $this->error(self::ERROR_OPEN, $hash, self::ERROR_DIR_NOT_FOUND));
1033
        }
1034
        if (!$cwd['read']) {
1035
            return array('error' => $this->error(self::ERROR_OPEN, $hash, self::ERROR_PERM_DENIED));
1036
        }
1037
1038
        $files = array();
1039
1040
        // get other volume root
1041
        if ($tree) {
1042
            foreach ($this->volumes as $id => $v) {
1043
                $files[] = $v->file($v->root());
1044
            }
1045
        }
1046
1047
        // get current working directory files list
1048
        if (($ls = $volume->scandir($cwd['hash'])) === false) {
1049
            return array('error' => $this->error(self::ERROR_OPEN, $cwd['name'], $volume->error()));
1050
        }
1051
        // long polling mode
1052
        if ($args['compare']) {
1053
            $sleep = max(1, (int)$volume->getOption('lsPlSleep'));
1054
            $standby = (int)$volume->getOption('plStandby');
1055
            if ($standby > 0 && $sleep > $standby) {
1056
                $standby = $sleep;
1057
            }
1058
            $limit = max(0, floor($standby / $sleep)) + 1;
1059
            do {
1060
                elFinder::extendTimeLimit(30 + $sleep);
1061
                $_mtime = 0;
1062
                foreach($ls as $_f) {
1063
                    $_mtime = max($_mtime, $_f['ts']);
1064
                }
1065
                $compare = strval(count($ls)).':'.strval($_mtime);
1066
                if ($compare !== $args['compare']) {
1067
                    break;
1068
                }
1069
                if (--$limit) {
1070
                    sleep($sleep);
1071
                    $volume->clearstatcache();
1072
                    if (($ls = $volume->scandir($cwd['hash'])) === false) {
1073
                        break;
1074
                    }
1075
                }
1076
            } while($limit);
1077
            if ($ls === false) {
1078
                return array('error' => $this->error(self::ERROR_OPEN, $cwd['name'], $volume->error()));
1079
            }
1080
        }
1081
1082
        if ($ls) {
1083
            if ($files) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $files 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...
1084
                $files = array_merge($files, $ls);
1085
            } else {
1086
                $files = $ls;
1087
            }
1088
        }
1089
1090
        $result = array(
1091
            'cwd'     => $cwd,
1092
            'options' => $volume->options($cwd['hash']),
1093
            'files'   => $files
1094
        );
1095
1096
        if ($compare) {
1097
            $result['cwd']['compare'] = $compare;
1098
        }
1099
1100
        if (!empty($args['init'])) {
1101
            $result['api'] = $this->version;
1102
            $result['uplMaxSize'] = ini_get('upload_max_filesize');
1103
            $result['uplMaxFile'] = ini_get('max_file_uploads');
1104
            $result['netDrivers'] = array_keys(self::$netDrivers);
1105
            if ($volume) {
1106
                $result['cwd']['root'] = $volume->root();
1107
            }
1108
        }
1109
1110
        return $result;
1111
    }
1112
1113
    /**
1114
     * Return dir files names list
1115
     *
1116
     * @param  array  command arguments
1117
     * @return array
1118
     * @author Dmitry (dio) Levashov
1119
     **/
1120
    protected function ls($args) {
1121
        $target = $args['target'];
1122
        $intersect = isset($args['intersect'])? $args['intersect'] : array();
1123
1124
        if (($volume = $this->volume($target)) == false
1125
        || ($list = $volume->ls($target, $intersect)) === false) {
1126
            return array('error' => $this->error(self::ERROR_OPEN, '#'.$target));
1127
        }
1128
        return array('list' => $list);
1129
    }
1130
1131
    /**
1132
     * Return subdirs for required directory
1133
     *
1134
     * @param  array  command arguments
1135
     * @return array
1136
     * @author Dmitry (dio) Levashov
1137
     **/
1138
    protected function tree($args) {
1139
        $target = $args['target'];
1140
1141
        if (($volume = $this->volume($target)) == false
1142
        || ($tree = $volume->tree($target)) == false) {
1143
            return array('error' => $this->error(self::ERROR_OPEN, '#'.$target));
1144
        }
1145
1146
        return array('tree' => $tree);
1147
    }
1148
1149
    /**
1150
     * Return parents dir for required directory
1151
     *
1152
     * @param  array  command arguments
1153
     * @return array
1154
     * @author Dmitry (dio) Levashov
1155
     **/
1156
    protected function parents($args) {
1157
        $target = $args['target'];
1158
1159
        if (($volume = $this->volume($target)) == false
1160
        || ($tree = $volume->parents($target)) == false) {
1161
            return array('error' => $this->error(self::ERROR_OPEN, '#'.$target));
1162
        }
1163
1164
        return array('tree' => $tree);
1165
    }
1166
1167
    /**
1168
     * Return new created thumbnails list
1169
     *
1170
     * @param  array  command arguments
1171
     * @return array
1172
     * @author Dmitry (dio) Levashov
1173
     **/
1174
    protected function tmb($args) {
1175
1176
        $result  = array('images' => array());
1177
        $targets = $args['targets'];
1178
1179
        foreach ($targets as $target) {
1180
            if (($volume = $this->volume($target)) != false
1181
            && (($tmb = $volume->tmb($target)) != false)) {
1182
                $result['images'][$target] = $tmb;
1183
            }
1184
        }
1185
        return $result;
1186
    }
1187
1188
    /**
1189
     * Download files/folders as an archive file
1190
     *
1191
     * 1st: Return srrsy contains download archive file info
1192
     * 2nd: Return array contains opened file pointer, root itself and required headers
1193
     *
1194
     * @param  array  command arguments
1195
     * @return array
1196
     * @author Naoki Sawada
1197
     **/
1198
    protected function zipdl($args) {
0 ignored issues
show
zipdl uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1199
        $targets = $args['targets'];
1200
        $download = !empty($args['download']);
1201
        $h404    = 'HTTP/1.x 404 Not Found';
1202
1203
        if (!$download) {
1204
            //1st: Return srrsy contains download archive file info
1205
            $error = array(self::ERROR_ARCHIVE);
1206
            if (($volume = $this->volume($targets[0])) !== false) {
1207
                if ($dlres = $volume->zipdl($targets)) {
1208
                    $path = $dlres['path'];
1209
                    register_shutdown_function(create_function('$f', 'connection_status() && is_file($f) && unlink($f);'), $path);
0 ignored issues
show
Security Best Practice introduced by
The use of create_function is highly discouraged, better use a closure.

create_function can pose a great security vulnerability as it is similar to eval, and could be used for arbitrary code execution. We highly recommend to use a closure instead.

// Instead of
$function = create_function('$a, $b', 'return $a + $b');

// Better use
$function = function($a, $b) { return $a + $b; }
Loading history...
1210
                    if (count($targets) === 1) {
1211
                        $name = basename($volume->path($targets[0]));
1212
                    } else {
1213
                        $name = $dlres['prefix'].'_Files';
1214
                    }
1215
                    $name .= '.'.$dlres['ext'];
1216
                    $result = array(
1217
                        'zipdl' => array(
1218
                            'file' => basename($path),
1219
                            'name' => $name,
1220
                            'mime' => $dlres['mime']
1221
                        )
1222
                    );
1223
                    return $result;
1224
                }
1225
                $error = array_merge($error, $volume->error());
1226
            }
1227
            return array('error' => $error);
1228
        } else {
1229
            // 2nd: Return array contains opened file pointer, root itself and required headers
1230
            if (count($targets) !== 4 || ($volume = $this->volume($targets[0])) == false) {
1231
                return array('error' => 'File not found', 'header' => $h404, 'raw' => true);
1232
            }
1233
            $file = $targets[1];
1234
            $path = $volume->getTempPath().DIRECTORY_SEPARATOR.$file;
1235
            register_shutdown_function(create_function('$f', 'is_file($f) && unlink($f);'), $path);
0 ignored issues
show
Security Best Practice introduced by
The use of create_function is highly discouraged, better use a closure.

create_function can pose a great security vulnerability as it is similar to eval, and could be used for arbitrary code execution. We highly recommend to use a closure instead.

// Instead of
$function = create_function('$a, $b', 'return $a + $b');

// Better use
$function = function($a, $b) { return $a + $b; }
Loading history...
1236
            if (!is_readable($path)) {
1237
                return array('error' => 'File not found', 'header' => $h404, 'raw' => true);
1238
            }
1239
            $name = $targets[2];
1240
            $mime = $targets[3];
1241
1242
            $filenameEncoded = rawurlencode($name);
1243
            if (strpos($filenameEncoded, '%') === false) { // ASCII only
1244
                $filename = 'filename="'.$name.'"';
1245
            } else {
1246
                $ua = $_SERVER['HTTP_USER_AGENT'];
1247
                if (preg_match('/MSIE [4-8]/', $ua)) { // IE < 9 do not support RFC 6266 (RFC 2231/RFC 5987)
1248
                    $filename = 'filename="'.$filenameEncoded.'"';
1249
                } elseif (strpos($ua, 'Chrome') === false && strpos($ua, 'Safari') !== false && preg_match('#Version/[3-5]#', $ua)) { // Safari < 6
1250
                    $filename = 'filename="'.str_replace('"', '', $name).'"';
1251
                } else { // RFC 6266 (RFC 2231/RFC 5987)
0 ignored issues
show
Unused Code Comprehensibility introduced by
36% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1252
                    $filename = 'filename*=UTF-8\'\''.$filenameEncoded;
1253
                }
1254
            }
1255
1256
            $fp = fopen($path, 'rb');
1257
            $file = fstat($fp);
1258
            $result = array(
1259
                'pointer' => $fp,
1260
                'header'  => array(
1261
                    'Content-Type: '.$mime,
1262
                    'Content-Disposition: attachment; '.$filename,
1263
                    'Content-Transfer-Encoding: binary',
1264
                    'Content-Length: '.$file['size'],
1265
                    'Accept-Ranges: none',
1266
                    'Connection: close'
1267
                )
1268
            );
1269
            return $result;
1270
        }
1271
    }
1272
1273
    /**
1274
     * Required to output file in browser when volume URL is not set
1275
     * Return array contains opened file pointer, root itself and required headers
1276
     *
1277
     * @param  array  command arguments
1278
     * @return array
1279
     * @author Dmitry (dio) Levashov
1280
     **/
1281
    protected function file($args) {
0 ignored issues
show
file uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1282
        $target   = $args['target'];
1283
        $download = !empty($args['download']);
1284
        $h403     = 'HTTP/1.x 403 Access Denied';
1285
        $h404     = 'HTTP/1.x 404 Not Found';
1286
1287
        if (($volume = $this->volume($target)) == false) {
1288
            return array('error' => 'File not found', 'header' => $h404, 'raw' => true);
1289
        }
1290
1291
        if (($file = $volume->file($target)) == false) {
1292
            return array('error' => 'File not found', 'header' => $h404, 'raw' => true);
1293
        }
1294
1295
        if (!$file['read']) {
1296
            return array('error' => 'Access denied', 'header' => $h403, 'raw' => true);
1297
        }
1298
1299
        if (($fp = $volume->open($target)) == false) {
1300
            return array('error' => 'File not found', 'header' => $h404, 'raw' => true);
1301
        }
1302
1303
        // allow change MIME type by 'file.pre' callback functions
1304
        $mime = isset($args['mime'])? $args['mime'] : $file['mime'];
1305
        if ($download) {
1306
            $disp = 'attachment';
1307
        } else {
1308
            $dispInlineRegex = $volume->getOption('dispInlineRegex');
1309
            $inlineRegex = false;
1310
            if ($dispInlineRegex) {
1311
                $inlineRegex = '#' . str_replace('#', '\\#', $dispInlineRegex) . '#';
1312
                try {
1313
                    preg_match($inlineRegex, '');
1314
                } catch(Exception $e) {
1315
                    $inlineRegex = false;
1316
                }
1317
            }
1318
            if (!$inlineRegex) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $inlineRegex of type string|false is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1319
                $inlineRegex = '#^(?:(?:image|text)|application/x-shockwave-flash$)#';
1320
            }
1321
            $disp  = preg_match($inlineRegex, $mime)? 'inline' : 'attachment';
1322
        }
1323
1324
        $filenameEncoded = rawurlencode($file['name']);
1325
        if (strpos($filenameEncoded, '%') === false) { // ASCII only
1326
            $filename = 'filename="'.$file['name'].'"';
1327
        } else {
1328
            $ua = $_SERVER['HTTP_USER_AGENT'];
1329
            if (preg_match('/MSIE [4-8]/', $ua)) { // IE < 9 do not support RFC 6266 (RFC 2231/RFC 5987)
1330
                $filename = 'filename="'.$filenameEncoded.'"';
1331
            } elseif (strpos($ua, 'Chrome') === false && strpos($ua, 'Safari') !== false && preg_match('#Version/[3-5]#', $ua)) { // Safari < 6
1332
                $filename = 'filename="'.str_replace('"', '', $file['name']).'"';
1333
            } else { // RFC 6266 (RFC 2231/RFC 5987)
0 ignored issues
show
Unused Code Comprehensibility introduced by
36% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1334
                $filename = 'filename*=UTF-8\'\''.$filenameEncoded;
1335
            }
1336
        }
1337
1338
        $result = array(
1339
            'volume'  => $volume,
1340
            'pointer' => $fp,
1341
            'info'    => $file,
1342
            'header'  => array(
1343
                'Content-Type: '.$mime,
1344
                'Content-Disposition: '.$disp.'; '.$filename,
1345
                'Content-Transfer-Encoding: binary',
1346
                'Content-Length: '.$file['size'],
1347
                'Connection: close'
1348
            )
1349
        );
1350
        if (isset($file['url']) && $file['url'] && $file['url'] != 1) {
1351
            $result['header'][] = 'Content-Location: '.$file['url'];
1352
        }
1353
        return $result;
1354
    }
1355
1356
    /**
1357
     * Count total files size
1358
     *
1359
     * @param  array  command arguments
1360
     * @return array
1361
     * @author Dmitry (dio) Levashov
1362
     **/
1363
    protected function size($args) {
1364
        $size = 0;
1365
1366
        foreach ($args['targets'] as $target) {
1367
            if (($volume = $this->volume($target)) == false
1368
            || ($file = $volume->file($target)) == false
1369
            || !$file['read']) {
1370
                return array('error' => $this->error(self::ERROR_OPEN, '#'.$target));
1371
            }
1372
1373
            $size += $volume->size($target);
1374
        }
1375
        return array('size' => $size);
1376
    }
1377
1378
    /**
1379
     * Create directory
1380
     *
1381
     * @param  array  command arguments
1382
     * @return array
1383
     * @author Dmitry (dio) Levashov
1384
     **/
1385
    protected function mkdir($args) {
1386
        $target = $args['target'];
1387
        $name   = $args['name'];
1388
        $dirs   = $args['dirs'];
1389
        if ($name === '' && !$dirs) {
1390
            return array('error' => $this->error(self::ERROR_INV_PARAMS, 'mkdir'));
1391
        }
1392
1393
        if (($volume = $this->volume($target)) == false) {
1394
            return array('error' => $this->error(self::ERROR_MKDIR, $name, self::ERROR_TRGDIR_NOT_FOUND, '#'.$target));
1395
        }
1396
        if ($dirs) {
1397
            sort($dirs);
1398
            $reset = null;
1399
            $mkdirs = array();
1400
            foreach($dirs as $dir) {
1401
                $tgt =& $mkdirs;
1402
                $_names = explode('/', trim($dir, '/'));
1403
                foreach($_names as $_key => $_name) {
1404
                    if (! isset($tgt[$_name])) {
1405
                        $tgt[$_name] = array();
1406
                    }
1407
                    $tgt =& $tgt[$_name];
1408
                }
1409
                $tgt =& $reset;
1410
            }
1411
            return ($res = $this->ensureDirsRecursively($volume, $target, $mkdirs)) === false
0 ignored issues
show
$mkdirs is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1412
                ? array('error' => $this->error(self::ERROR_MKDIR, $name, $volume->error()))
1413
                : array('added' => $res['stats'], 'hashes' => $res['hashes']);
1414
        } else {
1415
            return ($dir = $volume->mkdir($target, $name)) == false
1416
                ? array('error' => $this->error(self::ERROR_MKDIR, $name, $volume->error()))
1417
                : array('added' => array($dir));
1418
        }
1419
    }
1420
1421
    /**
1422
     * Create empty file
1423
     *
1424
     * @param  array  command arguments
1425
     * @return array
1426
     * @author Dmitry (dio) Levashov
1427
     **/
1428
    protected function mkfile($args) {
1429
        $target = $args['target'];
1430
        $name   = $args['name'];
1431
1432
        if (($volume = $this->volume($target)) == false) {
1433
            return array('error' => $this->error(self::ERROR_MKFILE, $name, self::ERROR_TRGDIR_NOT_FOUND, '#'.$target));
1434
        }
1435
1436
        return ($file = $volume->mkfile($target, $args['name'])) == false
1437
            ? array('error' => $this->error(self::ERROR_MKFILE, $name, $volume->error()))
1438
            : array('added' => array($file));
1439
    }
1440
1441
    /**
1442
     * Rename file
1443
     *
1444
     * @param  array  $args
1445
     * @return array
1446
     * @author Dmitry (dio) Levashov
1447
     **/
1448
    protected function rename($args) {
1449
        $target = $args['target'];
1450
        $name   = $args['name'];
1451
1452
        if (($volume = $this->volume($target)) == false
1453
        ||  ($rm  = $volume->file($target)) == false) {
1454
            return array('error' => $this->error(self::ERROR_RENAME, '#'.$target, self::ERROR_FILE_NOT_FOUND));
1455
        }
1456
        $rm['realpath'] = $volume->realpath($target);
1457
1458
        return ($file = $volume->rename($target, $name)) == false
1459
            ? array('error' => $this->error(self::ERROR_RENAME, $rm['name'], $volume->error()))
1460
            : array('added' => array($file), 'removed' => array($rm));
1461
    }
1462
1463
    /**
1464
     * Duplicate file - create copy with "copy %d" suffix
1465
     *
1466
     * @param array  $args  command arguments
1467
     * @return array
1468
     * @author Dmitry (dio) Levashov
1469
     **/
1470
    protected function duplicate($args) {
1471
        $targets = is_array($args['targets']) ? $args['targets'] : array();
1472
        $result  = array('added' => array());
1473
        $suffix  = empty($args['suffix']) ? 'copy' : $args['suffix'];
1474
1475
        foreach ($targets as $target) {
1476
            if (($volume = $this->volume($target)) == false
1477
            || ($src = $volume->file($target)) == false) {
1478
                $result['warning'] = $this->error(self::ERROR_COPY, '#'.$target, self::ERROR_FILE_NOT_FOUND);
1479
                break;
1480
            }
1481
1482
            if (($file = $volume->duplicate($target, $suffix)) == false) {
1483
                $result['warning'] = $this->error($volume->error());
1484
                break;
1485
            }
1486
1487
            $result['added'][] = $file;
1488
        }
1489
1490
        return $result;
1491
    }
1492
1493
    /**
1494
     * Remove dirs/files
1495
     *
1496
     * @param array  command arguments
1497
     * @return array
1498
     * @author Dmitry (dio) Levashov
1499
     **/
1500
    protected function rm($args) {
1501
        $targets = is_array($args['targets']) ? $args['targets'] : array();
1502
        $result  = array('removed' => array());
1503
1504
        foreach ($targets as $target) {
1505
            if (($volume = $this->volume($target)) == false) {
1506
                $result['warning'] = $this->error(self::ERROR_RM, '#'.$target, self::ERROR_FILE_NOT_FOUND);
1507
                return $result;
1508
            }
1509
            if (!$volume->rm($target)) {
1510
                $result['warning'] = $this->error($volume->error());
1511
                return $result;
1512
            }
1513
        }
1514
1515
        return $result;
1516
    }
1517
1518
    /**
1519
    * Get remote contents
1520
    *
1521
    * @param  string   $url     target url
1522
    * @param  int      $timeout timeout (sec)
1523
    * @param  int      $redirect_max redirect max count
1524
    * @param  string   $ua
1525
    * @param  resource $fp
1526
    * @return string or bool(false)
1527
    * @retval string contents
1528
    * @retval false  error
1529
    * @author Naoki Sawada
1530
    **/
1531
    protected function get_remote_contents( &$url, $timeout = 30, $redirect_max = 5, $ua = 'Mozilla/5.0', $fp = null ) {
1532
        $method = (function_exists('curl_exec') && !ini_get('safe_mode') && !ini_get('open_basedir'))? 'curl_get_contents' : 'fsock_get_contents';
1533
        return $this->$method( $url, $timeout, $redirect_max, $ua, $fp );
1534
    }
1535
1536
    /**
1537
     * Get remote contents with cURL
1538
     *
1539
     * @param  string   $url     target url
1540
     * @param  int      $timeout timeout (sec)
1541
     * @param  int      $redirect_max redirect max count
1542
     * @param  string   $ua
1543
     * @param  resource $outfp
1544
     * @return string or bool(false)
1545
     * @retval string contents
1546
     * @retval false  error
1547
     * @author Naoki Sawada
1548
     **/
1549
     protected function curl_get_contents( &$url, $timeout, $redirect_max, $ua, $outfp ){
1550
        $ch = curl_init();
1551
        curl_setopt( $ch, CURLOPT_URL, $url );
1552
        curl_setopt( $ch, CURLOPT_HEADER, false );
1553
        if ($outfp) {
1554
            curl_setopt( $ch, CURLOPT_FILE, $outfp );
1555
        } else {
1556
            curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
1557
            curl_setopt( $ch, CURLOPT_BINARYTRANSFER, true );
1558
        }
1559
        curl_setopt( $ch, CURLOPT_LOW_SPEED_LIMIT, 1 );
1560
        curl_setopt( $ch, CURLOPT_LOW_SPEED_TIME, $timeout );
1561
        curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false );
1562
        curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 1);
1563
        curl_setopt( $ch, CURLOPT_MAXREDIRS, $redirect_max);
1564
        curl_setopt( $ch, CURLOPT_USERAGENT, $ua);
1565
        $result = curl_exec( $ch );
1566
        $url = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
1567
        curl_close( $ch );
1568
        return $outfp? $outfp : $result;
1569
    }
1570
1571
    /**
1572
     * Get remote contents with fsockopen()
1573
     *
1574
     * @param  string   $url          url
1575
     * @param  int      $timeout      timeout (sec)
1576
     * @param  int      $redirect_max redirect max count
1577
     * @param  string   $ua
1578
     * @param  resource $outfp
1579
     * @return string or bool(false)
1580
     * @retval string contents
1581
     * @retval false  error
1582
     * @author Naoki Sawada
1583
     */
1584
    protected function fsock_get_contents( &$url, $timeout, $redirect_max, $ua, $outfp ) {
1585
1586
        $connect_timeout = 3;
1587
        $connect_try = 3;
1588
        $method = 'GET';
1589
        $readsize = 4096;
1590
        $ssl = '';
1591
1592
        $getSize = null;
1593
        $headers = '';
1594
1595
        $arr = parse_url($url);
1596
        if (!$arr){
1597
            // Bad request
1598
            return false;
1599
        }
1600
        if ($arr['scheme'] === 'https') {
1601
            $ssl = 'ssl://';
1602
        }
1603
1604
        // query
1605
        $arr['query'] = isset($arr['query']) ? '?'.$arr['query'] : '';
1606
        // port
1607
        $arr['port'] = isset($arr['port']) ? $arr['port'] : ($ssl? 443 : 80);
1608
1609
        $url_base = $arr['scheme'].'://'.$arr['host'].':'.$arr['port'];
1610
        $url_path = isset($arr['path']) ? $arr['path'] : '/';
1611
        $uri = $url_path.$arr['query'];
1612
1613
        $query = $method.' '.$uri." HTTP/1.0\r\n";
1614
        $query .= "Host: ".$arr['host']."\r\n";
1615
        $query .= "Accept: */*\r\n";
1616
        $query .= "Connection: close\r\n";
1617
        if (!empty($ua)) $query .= "User-Agent: ".$ua."\r\n";
1618
        if (!is_null($getSize)) $query .= 'Range: bytes=0-' . ($getSize - 1) . "\r\n";
1619
1620
        $query .= $headers;
1621
1622
        $query .= "\r\n";
1623
1624
        $fp = $connect_try_count = 0;
1625
        while( !$fp && $connect_try_count < $connect_try ) {
1626
1627
            $errno = 0;
1628
            $errstr = "";
1629
            $fp =  fsockopen(
1630
                $ssl.$arr['host'],
1631
                $arr['port'],
1632
                $errno,$errstr,$connect_timeout);
1633
            if ($fp) break;
1634
            $connect_try_count++;
1635
            if (connection_aborted()) {
1636
                exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method fsock_get_contents() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
1637
            }
1638
            sleep(1); // wait 1sec
1639
        }
1640
1641
        $fwrite = 0;
0 ignored issues
show
$fwrite is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1642
        for ($written = 0; $written < strlen($query); $written += $fwrite) {
1643
            $fwrite = fwrite($fp, substr($query, $written));
1644
            if (!$fwrite) {
1645
                break;
1646
            }
1647
        }
1648
1649
        $response = '';
0 ignored issues
show
$response is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1650
1651
        if ($timeout) {
1652
            socket_set_timeout($fp, $timeout);
1653
        }
1654
1655
        $_response = '';
1656
        $header = '';
1657
        while($_response !== "\r\n"){
1658
            $_response = fgets($fp, $readsize);
1659
            $header .= $_response;
1660
        };
1661
1662
        $rccd = array_pad(explode(' ',$header,2), 2, ''); // array('HTTP/1.1','200')
0 ignored issues
show
Unused Code Comprehensibility introduced by
86% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1663
        $rc = (int)$rccd[1];
1664
1665
        $ret = false;
1666
        // Redirect
1667
        switch ($rc) {
1668
            case 307: // Temporary Redirect
1669
            case 303: // See Other
1670
            case 302: // Moved Temporarily
1671
            case 301: // Moved Permanently
1672
                $matches = array();
1673
                if (preg_match('/^Location: (.+?)(#.+)?$/im',$header,$matches) && --$redirect_max > 0) {
1674
                    $_url = $url;
1675
                    $url = trim($matches[1]);
1676
                    $hash = isset($matches[2])? trim($matches[2]) : '';
0 ignored issues
show
$hash is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1677
                    if (!preg_match('/^https?:\//',$url)) { // no scheme
1678
                        if ($url{0} != '/') { // Relative path
1679
                            // to Absolute path
1680
                            $url = substr($url_path,0,strrpos($url_path,'/')).'/'.$url;
1681
                        }
1682
                        // add sheme,host
1683
                        $url = $url_base.$url;
1684
                    }
1685
                    if ($_url !== $url) {
1686
                        fclose($fp);
1687
                        return $this->fsock_get_contents( $url, $timeout, $redirect_max, $ua, $outfp );
1688
                    }
1689
                }
1690
                break;
1691
            case 200:
1692
                $ret = true;
1693
        }
1694
        if (! $ret) {
1695
            fclose($fp);
1696
            return false;
1697
        }
1698
1699
        $body = '';
1700
        if (!$outfp) {
1701
            $outfp = fopen('php://temp', 'rwb');
1702
            $body = true;
1703
        }
1704
        while(fwrite($outfp, fread($fp, $readsize))) {
1705
            if ($timeout) {
1706
                $_status = socket_get_status($fp);
1707
                if ($_status['timed_out']) {
1708
                    fclose($outfp);
1709
                    fclose($fp);
1710
                    return false; // Request Time-out
1711
                }
1712
            }
1713
        }
1714
        if ($body) {
1715
            rewind($outfp);
1716
            $body = stream_get_contents($outfp);
1717
            fclose($outfp);
1718
            $outfp = null;
1719
        }
1720
1721
        fclose($fp);
1722
1723
        return $outfp? $outfp : $body; // Data
0 ignored issues
show
Bug Compatibility introduced by
The expression $outfp ? $outfp : $body; of type boolean|string adds the type boolean to the return on line 1723 which is incompatible with the return type documented by elFinder::fsock_get_contents of type string.
Loading history...
1724
    }
1725
1726
    /**
1727
     * Parse Data URI scheme
1728
     *
1729
     * @param  string $str
1730
     * @param  array  $extTable
1731
     * @return array
1732
     * @author Naoki Sawada
1733
     */
1734
    protected function parse_data_scheme( $str, $extTable ) {
1735
        $data = $name = '';
1736
        if ($fp = fopen('data://'.substr($str, 5), 'rb')) {
1737
            if ($data = stream_get_contents($fp)) {
1738
                $meta = stream_get_meta_data($fp);
1739
                $ext = isset($extTable[$meta['mediatype']])? '.' . $extTable[$meta['mediatype']] : '';
1740
                $name = substr(md5($data), 0, 8) . $ext;
1741
            }
1742
            fclose($fp);
1743
        }
1744
        return array($data, $name);
1745
    }
1746
1747
    /**
1748
     * Detect file type extension by local path
1749
     *
1750
     * @param  string $path Local path
1751
     * @return string file type extension with dot
1752
     * @author Naoki Sawada
1753
     */
1754
    protected function detectFileExtension($path) {
1755
        static $type, $finfo;
1756
        if (!$type) {
1757
            $keys = array_keys($this->volumes);
1758
            $volume = $this->volumes[$keys[0]];
1759
1760
            if (class_exists('finfo', false)) {
1761
                $tmpFileInfo = explode(';', finfo_file(finfo_open(FILEINFO_MIME), __FILE__));
1762
            } else {
1763
                $tmpFileInfo = false;
1764
            }
1765
            $regexp = '/text\/x\-(php|c\+\+)/';
1766
            if ($tmpFileInfo && preg_match($regexp, array_shift($tmpFileInfo))) {
1767
                $type = 'finfo';
1768
                $finfo = finfo_open(FILEINFO_MIME);
1769
            } elseif (function_exists('mime_content_type')
1770
                    && preg_match($regexp, array_shift(explode(';', mime_content_type(__FILE__))))) {
0 ignored issues
show
explode(';', mime_content_type(__FILE__)) cannot be passed to array_shift() as the parameter $array expects a reference.
Loading history...
1771
                $type = 'mime_content_type';
1772
            } elseif (function_exists('getimagesize')) {
1773
                $type = 'getimagesize';
1774
            } else {
1775
                $type = 'none';
1776
            }
1777
        }
1778
1779
        $mime = '';
1780
        if ($type === 'finfo') {
1781
            $mime = finfo_file($finfo, $path);
1782
        } elseif ($type === 'mime_content_type') {
1783
            $mime = mime_content_type($path);
1784
        } elseif ($type === 'getimagesize') {
1785
            if ($img = getimagesize($path)) {
1786
                $mime = $img['mime'];
1787
            }
1788
        }
1789
1790
        if ($mime) {
1791
            $mime = explode(';', $mime);
1792
            $mime = trim($mime[0]);
1793
1794
            if (in_array($mime, array('application/x-empty', 'inode/x-empty'))) {
1795
                // finfo return this mime for empty files
1796
                $mime = 'text/plain';
1797
            } elseif ($mime == 'application/x-zip') {
1798
                // http://elrte.org/redmine/issues/163
1799
                $mime = 'application/zip';
1800
            }
1801
        }
1802
1803
        $ext = $mime? $volume->getExtentionByMime($mime) : '';
0 ignored issues
show
The variable $volume does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1804
        return $ext? ('.' . $ext) : '';
1805
    }
1806
1807
    /**
1808
     * Get temporary directory path
1809
     *
1810
     * @param  string $volumeTempPath
1811
     * @return string
1812
     * @author Naoki Sawada
1813
     */
1814
    private function getTempDir($volumeTempPath = null) {
1815
        $testDirs = array();
1816
        if ($this->uploadTempPath) {
1817
            $testDirs[] = rtrim(realpath($this->uploadTempPath), DIRECTORY_SEPARATOR);
1818
        }
1819
        if ($volumeTempPath) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $volumeTempPath of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1820
            $testDirs[] = rtrim(realpath($volumeTempPath), DIRECTORY_SEPARATOR);
1821
        }
1822
        if (function_exists('sys_get_temp_dir')) {
1823
            $testDirs[] = sys_get_temp_dir();
1824
        }
1825
        $tempDir = '';
1826
        foreach($testDirs as $testDir) {
1827
            if (!$testDir || !is_dir($testDir)) continue;
1828
            if (is_writable($testDir)) {
1829
                $tempDir = $testDir;
1830
                $gc = time() - 3600;
1831
                foreach(glob($tempDir . DIRECTORY_SEPARATOR .'ELF*') as $cf) {
1832
                    if (filemtime($cf) < $gc) {
1833
                        unlink($cf);
1834
                    }
1835
                }
1836
                break;
1837
            }
1838
        }
1839
        return $tempDir;
1840
    }
1841
1842
    /**
1843
     * chmod
1844
     *
1845
     * @param array  command arguments
1846
     * @return array
1847
     * @author David Bartle
1848
     **/
1849
    protected function chmod($args) {
1850
        $targets = $args['targets'];
1851
        $mode    = intval((string)$args['mode'], 8);
1852
1853
        if (!is_array($targets)) {
1854
            $targets = array($targets);
1855
        }
1856
1857
        $result = array();
1858
1859
        if (($volume = $this->volume($targets[0])) == false) {
1860
            $result['error'] = $this->error(self::ERROR_CONF_NO_VOL);
1861
            return $result;
1862
        }
1863
1864
        $files = array();
1865
        $errors = array();
1866
        foreach($targets as $target) {
1867
            $file = $volume->chmod($target, $mode);
1868
            if ($file) {
1869
                $files = array_merge($files, is_array($file)? $file : array($file));
1870
            } else {
1871
                $errors = array_merge($errors, $volume->error());
1872
            }
1873
        }
1874
1875
        if ($files) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $files 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...
1876
            $result['changed'] = $files;
1877
            if ($errors) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $errors 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...
1878
                $result['warning'] = $this->error($errors);
1879
            }
1880
        } else {
1881
            $result['error'] = $this->error($errors);
1882
        }
1883
1884
        return $result;
1885
    }
1886
1887
    /**
1888
     * Check chunked upload files
1889
     *
1890
     * @param string $tmpname uploaded temporary file path
1891
     * @param string $chunk uploaded chunk file name
1892
     * @param string $cid uploaded chunked file id
1893
     * @param string $tempDir temporary dirctroy path
1894
     * @param null $volume
1895
     * @return array or (empty, empty)
1896
     * @author Naoki Sawada
1897
     */
1898
    private function checkChunkedFile($tmpname, $chunk, $cid, $tempDir, $volume = null) {
0 ignored issues
show
checkChunkedFile uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1899
        if (preg_match('/^(.+)(\.\d+_(\d+))\.part$/s', $chunk, $m)) {
1900
            $fname = $m[1];
1901
            $encname = md5($cid . '_' . $fname);
1902
            $base = $tempDir . DIRECTORY_SEPARATOR . 'ELF' . $encname;
1903
            $clast = intval($m[3]);
1904
            if (is_null($tmpname)) {
1905
                ignore_user_abort(true);
1906
                sleep(10); // wait 10 sec
1907
                // chunked file upload fail
1908
                foreach(glob($base . '*') as $cf) {
1909
                    unlink($cf);
1910
                }
1911
                ignore_user_abort(false);
1912
                return;
1913
            }
1914
1915
            $range = isset($_POST['range'])? trim($_POST['range']) : '';
1916
            if ($range && preg_match('/^(\d+),(\d+),(\d+)$/', $range, $ranges)) {
1917
                $start = $ranges[1];
1918
                $len   = $ranges[2];
1919
                $size  = $ranges[3];
1920
                $tmp = $base . '.part';
1921
                $csize = filesize($tmpname);
1922
1923
                $tmpExists = is_file($tmp);
1924
                if (!$tmpExists) {
1925
                    // check upload max size
1926
                    $uploadMaxSize = $volume->getUploadMaxSize();
0 ignored issues
show
The method getUploadMaxSize cannot be called on $volume (of type null).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
1927
                    if ($uploadMaxSize > 0 && $size > $uploadMaxSize) {
1928
                        return array(self::ERROR_UPLOAD_FILE_SIZE, false);
1929
                    }
1930
                    // make temp file
1931
                    $ok = false;
1932
                    if ($fp = fopen($tmp, 'wb')) {
1933
                        flock($fp, LOCK_EX);
1934
                        $ok = ftruncate($fp, $size);
1935
                        flock($fp, LOCK_UN);
1936
                        fclose($fp);
1937
                        touch($base);
1938
                    }
1939
                    if (!$ok) {
1940
                        unlink($tmp);
1941
                        return array(self::ERROR_UPLOAD_TEMP, false);
1942
                    }
1943
                } else {
1944
                    // wait until makeing temp file (for anothor session)
1945
                    $cnt = 1200; // Time limit 120 sec
1946
                    while(!is_file($base) && --$cnt) {
1947
                        usleep(100000); // wait 100ms
1948
                    }
1949
                    if (!$cnt) {
1950
                        return array(self::ERROR_UPLOAD_TEMP, false);
1951
                    }
1952
                }
1953
1954
                // check size info
1955
                if ($len != $csize || $start + $len > $size || ($tmpExists && $size != filesize($tmp))) {
1956
                    return array(self::ERROR_UPLOAD_TEMP, false);
1957
                }
1958
1959
                // write chunk data
1960
                $writelen = 0;
0 ignored issues
show
$writelen is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1961
                $src = fopen($tmpname, 'rb');
1962
                $fp = fopen($tmp, 'cb');
1963
                fseek($fp, $start);
1964
                $writelen = stream_copy_to_stream($src, $fp, $len);
1965
                fclose($fp);
1966
                fclose($src);
1967
                if ($writelen != $len) {
1968
                    return array(self::ERROR_UPLOAD_TEMP, false);
1969
                }
1970
1971
                // write counts
1972
                file_put_contents($base, "\0", FILE_APPEND | LOCK_EX);
1973
1974
                if (filesize($base) >= $clast + 1) {
1975
                    // Completion
1976
                    unlink($base);
1977
                    return array($tmp, $fname);
1978
                }
1979
            } else {
1980
                // old way
1981
                $part = $base . $m[2];
1982
                if (move_uploaded_file($tmpname, $part)) {
1983
                    chmod($part, 0600);
1984
                    if ($clast < count(glob($base . '*'))) {
1985
                        $parts = array();
1986
                        for ($i = 0; $i <= $clast; $i++) {
1987
                            $name = $base . '.' . $i . '_' . $clast;
1988
                            if (is_readable($name)) {
1989
                                $parts[] = $name;
1990
                            } else {
1991
                                $parts = null;
1992
                                break;
1993
                            }
1994
                        }
1995
                        if ($parts) {
1996
                            if (!is_file($base)) {
1997
                                touch($base);
1998
                                if ($resfile = tempnam($tempDir, 'ELF')) {
1999
                                    $target = fopen($resfile, 'wb');
2000
                                    foreach($parts as $f) {
2001
                                        $fp = fopen($f, 'rb');
2002
                                        while (!feof($fp)) {
2003
                                            fwrite($target, fread($fp, 8192));
2004
                                        }
2005
                                        fclose($fp);
2006
                                        unlink($f);
2007
                                    }
2008
                                    fclose($target);
2009
                                    unlink($base);
2010
                                    return array($resfile, $fname);
2011
                                }
2012
                                unlink($base);
2013
                            }
2014
                        }
2015
                    }
2016
                }
2017
            }
2018
        }
2019
        return array('', '');
2020
    }
2021
2022
    /**
2023
     * Save uploaded files
2024
     *
2025
     * @param  array
2026
     * @return array
2027
     * @author Dmitry (dio) Levashov
2028
     **/
2029
    protected function upload($args) {
0 ignored issues
show
upload uses the super-global variable $GLOBALS which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
upload uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
2030
        $ngReg  = '/[\/\\?*:|"<>]/';
2031
        $target = $args['target'];
2032
        $volume = $this->volume($target);
2033
        $files  = isset($args['FILES']['upload']) && is_array($args['FILES']['upload']) ? $args['FILES']['upload'] : array();
2034
        $header = empty($args['html']) ? array() : array('header' => 'Content-Type: text/html; charset=utf-8');
2035
        $result = array_merge(array('added' => array()), $header);
2036
        $paths  = $args['upload_path']? $args['upload_path'] : array();
2037
        $chunk  = $args['chunk']? $args['chunk'] : '';
2038
        $cid    = $args['cid']? (int)$args['cid'] : '';
2039
        $mtimes = $args['mtime']? $args['mtime'] : array();
2040
2041
        $renames = $hashes = array();
2042
        $suffix = '~';
2043
        if ($args['renames'] && is_array($args['renames'])) {
2044
            $renames = array_flip($args['renames']);
2045
            if (is_string($args['suffix']) && ! preg_match($ngReg, $args['suffix'])) {
2046
                $suffix = $args['suffix'];
2047
            }
2048
        }
2049
        if ($args['hashes'] && is_array($args['hashes'])) {
2050
            $hashes = array_flip($args['hashes']);
2051
        }
2052
2053
        if (!$volume) {
2054
            return array_merge(array('error' => $this->error(self::ERROR_UPLOAD, self::ERROR_TRGDIR_NOT_FOUND, '#'.$target)), $header);
2055
        }
2056
2057
        // regist Shutdown function
2058
        $GLOBALS['elFinderTempFiles'] = array();
2059
//         if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
57% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
2060
//             $shutdownfunc = function(){ // <- Parse error on PHP < 5.3 ;-(
2061
//                 foreach(array_keys($GLOBALS['elFinderTempFiles']) as $f){
2062
//                     unlink($f);
2063
//                 }
2064
//             };
2065
//         } else {
2066
            $shutdownfunc = create_function('', '
0 ignored issues
show
Security Best Practice introduced by
The use of create_function is highly discouraged, better use a closure.

create_function can pose a great security vulnerability as it is similar to eval, and could be used for arbitrary code execution. We highly recommend to use a closure instead.

// Instead of
$function = create_function('$a, $b', 'return $a + $b');

// Better use
$function = function($a, $b) { return $a + $b; }
Loading history...
2067
                foreach(array_keys($GLOBALS[\'elFinderTempFiles\']) as $f){
2068
                    is_file($f) && unlink($f);
2069
                }
2070
            ');
2071
//        }
2072
        register_shutdown_function($shutdownfunc);
2073
2074
        // file extentions table by MIME
2075
        $extTable = array_flip(array_unique($volume->getMimeTable()));
2076
2077
        if (empty($files)) {
2078
2079
            //--- This part is unnecessary code from 2.1.7 START ---//
2080
            if (!$args['upload'] && $args['name'] && is_array($args['name'])) {
2081
                $error = '';
2082
                $result['name'] = array();
2083
                foreach($args['name'] as $_i => $_name) {
2084
                    if (!$volume->isUploadableByName($_name)) {
2085
                        $error = $this->error(self::ERROR_UPLOAD_FILE, $_name, self::ERROR_UPLOAD_FILE_MIME);
2086
                        break;
2087
                    }
2088
                    $result['name'][$_i] = preg_replace($ngReg, '_', $_name);
2089
                }
2090
                if ($error) {
2091
                    $result['error'] = $error;
2092
                    return $result;
2093
                }
2094
                $result = array_merge_recursive($result, $this->ls($args));
2095
                if (empty($result['list'])) {
2096
                    $result['name'] = array();
2097
                } else {
2098
                    // It is using the old(<=2.1.6) JavaScript in the new(>2.1.6) back-end?
2099
                    unset($result['list']['exists'], $result['list']['hashes']);
2100
                    $result['name'] = array_merge(array_intersect($result['name'], $result['list']));
2101
                }
2102
                return $result;
2103
            }
2104
            //--- This part is unnessesaly code from 2.1.7 END ---//
2105
2106
            if (isset($args['upload']) && is_array($args['upload']) && ($tempDir = $this->getTempDir($volume->getTempPath()))) {
2107
                $names = array();
2108
                foreach($args['upload'] as $i => $url) {
2109
                    // check chunked file upload commit
2110
                    if ($args['chunk']) {
2111
                        if ($url === 'chunkfail' && $args['mimes'] === 'chunkfail') {
2112
                            $this->checkChunkedFile(null, $chunk, $cid, $tempDir);
2113
                            if (preg_match('/^(.+)(\.\d+_(\d+))\.part$/s', $chunk, $m)) {
2114
                                $result['warning'] = $this->error(self::ERROR_UPLOAD_FILE, $m[1], self::ERROR_UPLOAD_TRANSFER);
2115
                            }
2116
                            return $result;
2117
                        } else {
2118
                            $tmpfname = $tempDir . '/' . $args['chunk'];
2119
                            $files['tmp_name'][$i] = $tmpfname;
2120
                            $files['name'][$i] = $url;
2121
                            $files['error'][$i] = 0;
2122
                            $GLOBALS['elFinderTempFiles'][$tmpfname] = true;
2123
                            break;
2124
                        }
2125
                    }
2126
2127
                    $tmpfname = $tempDir . DIRECTORY_SEPARATOR . 'ELF_FATCH_' . md5($url.microtime(true));
2128
2129
                    $_name = '';
2130
                    // check is data:
2131
                    if (substr($url, 0, 5) === 'data:') {
2132
                        list($data, $args['name'][$i]) = $this->parse_data_scheme($url, $extTable);
2133
                    } else {
2134
                        $fp = fopen($tmpfname, 'wb');
2135
                        $data = $this->get_remote_contents($url, 30, 5, 'Mozilla/5.0', $fp);
2136
                        $_POST['overwrite'] = false;
2137
                        $_name = preg_replace('~^.*?([^/#?]+)(?:\?.*)?(?:#.*)?$~', '$1', rawurldecode($url));
2138
                        // Check `Content-Disposition` response header
2139
                        if ($data && ($headers = get_headers($url, true)) && !empty($headers['Content-Disposition'])) {
2140
                            if (preg_match('/filename\*?=(?:([a-zA-Z0-9_-]+?)\'\')?"?([a-z0-9_.~%-]+)"?/i', $headers['Content-Disposition'], $m)) {
2141
                                $_name = rawurldecode($m[2]);
2142
                                if ($m[1] && strtoupper($m[1]) !== 'UTF-8' && function_exists('mb_convert_encoding')) {
2143
                                    $_name = mb_convert_encoding($_name, 'UTF-8', $m[1]);
2144
                                }
2145
                            }
2146
                        }
2147
                    }
2148
                    if ($data) {
2149
                        if (isset($args['name'][$i])) {
2150
                            $_name = $args['name'][$i];
2151
                        }
2152
                        if ($_name) {
2153
                            $_ext = '';
2154
                            if (preg_match('/(\.[a-z0-9]{1,7})$/', $_name, $_match)) {
2155
                                $_ext = $_match[1];
2156
                            }
2157
                            if ((is_resource($data) && fclose($data)) || file_put_contents($tmpfname, $data)) {
2158
                                $GLOBALS['elFinderTempFiles'][$tmpfname] = true;
2159
                                $_name = preg_replace($ngReg, '_', $_name);
2160
                                list($_a, $_b) = array_pad(explode('.', $_name, 2), 2, '');
2161
                                if ($_b === '') {
2162
                                    if ($_ext) {
2163
                                        rename($tmpfname, $tmpfname . $_ext);
2164
                                        $tmpfname = $tmpfname . $_ext;
2165
                                    }
2166
                                    $_b = $this->detectFileExtension($tmpfname);
2167
                                    $_name = $_a.$_b;
2168
                                } else {
2169
                                    $_b = '.'.$_b;
2170
                                }
2171
                                if (isset($names[$_name])) {
2172
                                    $_name = $_a.'_'.$names[$_name]++.$_b;
2173
                                } else {
2174
                                    $names[$_name] = 1;
2175
                                }
2176
                                $files['tmp_name'][$i] = $tmpfname;
2177
                                $files['name'][$i] = $_name;
2178
                                $files['error'][$i] = 0;
2179
                            } else {
2180
                                 unlink($tmpfname);
2181
                            }
2182
                        }
2183
                    }
2184
                }
2185
            }
2186
            if (empty($files)) {
2187
                return array_merge(array('error' => $this->error(self::ERROR_UPLOAD, self::ERROR_UPLOAD_NO_FILES)), $header);
2188
            }
2189
        }
2190
2191
        $addedDirs = array();
2192
        foreach ($files['name'] as $i => $name) {
2193
            if (($error = $files['error'][$i]) > 0) {
2194
                $result['warning'] = $this->error(self::ERROR_UPLOAD_FILE, $name, $error == UPLOAD_ERR_INI_SIZE || $error == UPLOAD_ERR_FORM_SIZE ? self::ERROR_UPLOAD_FILE_SIZE : self::ERROR_UPLOAD_TRANSFER);
2195
                $this->uploadDebug = 'Upload error code: '.$error;
2196
                break;
2197
            }
2198
2199
            $tmpname = $files['tmp_name'][$i];
2200
            $path = ($paths && isset($paths[$i]))? $paths[$i] : '';
2201
            $mtime = isset($mtimes[$i])? $mtimes[$i] : 0;
2202
            if ($name === 'blob') {
2203
                if ($chunk) {
2204
                    if ($tempDir = $this->getTempDir($volume->getTempPath())) {
2205
                        list($tmpname, $name) = $this->checkChunkedFile($tmpname, $chunk, $cid, $tempDir, $volume);
2206
                        if ($tmpname) {
2207
                            if ($name === false) {
2208
                                preg_match('/^(.+)(\.\d+_(\d+))\.part$/s', $chunk, $m);
2209
                                $result['error'] = $this->error(self::ERROR_UPLOAD_FILE, $m[1], $tmpname);
2210
                                $result['_chunkfailure'] = true;
2211
                                $this->uploadDebug = 'Upload error: ' . $tmpname;
2212
                            } else if ($name) {
2213
                                $result['_chunkmerged'] = basename($tmpname);
2214
                                $result['_name'] = $name;
2215
                                $result['_mtime'] = $mtime;
2216
                            }
2217
                        }
2218
                    } else {
2219
                        $result['error'] = $this->error(self::ERROR_UPLOAD_FILE, $chunk, self::ERROR_UPLOAD_TRANSFER);
2220
                        $this->uploadDebug = 'Upload error: unable open tmp file';
2221
                    }
2222
                    return $result;
2223
                } else {
2224
                    // for form clipboard with Google Chrome
2225
                    $type = $files['type'][$i];
2226
                    $ext = isset($extTable[$type])? '.' . $extTable[$type] : '';
2227
                    $name = substr(md5(basename($tmpname)), 0, 8) . $ext;
2228
                }
2229
            }
2230
2231
            // do hook function 'upload.presave'
2232
            if (! empty($this->listeners['upload.presave'])) {
2233
                foreach($this->listeners['upload.presave'] as $handler) {
2234
                    call_user_func_array($handler, array(&$path, &$name, $tmpname, $this, $volume));
2235
                }
2236
            }
2237
2238
            if ($mtime) {
2239
                touch($tmpname, $mtime);
2240
            }
2241
2242
            if (($fp = fopen($tmpname, 'rb')) == false) {
2243
                $result['warning'] = $this->error(self::ERROR_UPLOAD_FILE, $name, self::ERROR_UPLOAD_TRANSFER);
2244
                $this->uploadDebug = 'Upload error: unable open tmp file';
2245
                if (! is_uploaded_file($tmpname)) {
2246
                    if ( unlink($tmpname)) unset($GLOBALS['elFinderTempFiles'][$tmpfname]);
2247
                    continue;
2248
                }
2249
                break;
2250
            }
2251
            $rnres = array();
2252
            if ($path !== '' && $path !== $target) {
2253
                if ($dir = $volume->dir($path)) {
2254
                    $_target = $path;
2255
                    if (! isset($addedDirs[$path])) {
2256
                        $addedDirs[$path] = true;
2257
                        $result['added'][] =$dir;
2258
                    }
2259
                } else {
2260
                    $result['error'] = $this->error(self::ERROR_UPLOAD, self::ERROR_TRGDIR_NOT_FOUND, 'hash@'.$path);
2261
                    break;
2262
                }
2263
            } else {
2264
                $_target = $target;
2265
                // file rename for backup
2266
                if (isset($renames[$name])) {
2267
                    $dir = $volume->realpath($_target);
2268
                    if (isset($hashes[$name])) {
2269
                        $hash = $hashes[$name];
2270
                    } else {
2271
                        $hash = $volume->getHash($dir, $name);
2272
                    }
2273
                    $rnres = $this->rename(array('target' => $hash, 'name' => $volume->uniqueName($dir, $name, $suffix, true, 0)));
2274
                    if (!empty($rnres['error'])) {
2275
                        $result['warning'] = $rnres['error'];
2276
                        break;
2277
                    }
2278
                }
2279
            }
2280
            if (! $_target || ($file = $volume->upload($fp, $_target, $name, $tmpname, $hashes)) === false) {
2281
                $result['warning'] = $this->error(self::ERROR_UPLOAD_FILE, $name, $volume->error());
2282
                fclose($fp);
2283
                if (! is_uploaded_file($tmpname)) {
2284
                    if ( unlink($tmpname)) unset($GLOBALS['elFinderTempFiles'][$tmpname]);;
2285
                    continue;
2286
                }
2287
                break;
2288
            }
2289
2290
            is_resource($fp) && fclose($fp);
2291
            if (! is_uploaded_file($tmpname)){
2292
                clearstatcache();
2293
                if (!is_file($tmpname) ||  unlink($tmpname)) {
2294
                    unset($GLOBALS['elFinderTempFiles'][$tmpname]);
2295
                }
2296
            }
2297
            $result['added'][] = $file;
2298
            if ($rnres) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $rnres 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...
2299
                $result = array_merge_recursive($result, $rnres);
2300
            }
2301
        }
2302
        if ($GLOBALS['elFinderTempFiles']) {
2303
            foreach(array_keys($GLOBALS['elFinderTempFiles']) as $_temp) {
2304
                 unlink($_temp);
2305
            }
2306
        }
2307
        $result['removed'] = $volume->removed();
2308
2309
        if (!empty($args['node'])) {
2310
            $result['callback'] = array(
2311
                'node' => $args['node'],
2312
                'bind' => 'upload'
2313
            );
2314
        }
2315
        return $result;
2316
    }
2317
2318
    /**
2319
     * Copy/move files into new destination
2320
     *
2321
     * @param  array  command arguments
2322
     * @return array
2323
     * @author Dmitry (dio) Levashov
2324
     **/
2325
    protected function paste($args) {
2326
        $dst     = $args['dst'];
2327
        $targets = is_array($args['targets']) ? $args['targets'] : array();
2328
        $cut     = !empty($args['cut']);
2329
        $error   = $cut ? self::ERROR_MOVE : self::ERROR_COPY;
2330
        $result  = array('changed' => array(), 'added' => array(), 'removed' => array());
2331
2332
        if (($dstVolume = $this->volume($dst)) == false) {
2333
            return array('error' => $this->error($error, '#'.$targets[0], self::ERROR_TRGDIR_NOT_FOUND, '#'.$dst));
2334
        }
2335
2336
        $hashes = $renames = array();
2337
        $suffix = '~';
2338
        if (!empty($args['renames'])) {
2339
            $renames = array_flip($args['renames']);
2340
            if (is_string($args['suffix']) && ! preg_match('/[\/\\?*:|"<>]/', $args['suffix'])) {
2341
                $suffix = $args['suffix'];
2342
            }
2343
        }
2344
        if (!empty($args['hashes'])) {
2345
            $hashes = array_flip($args['hashes']);
2346
        }
2347
2348
        foreach ($targets as $target) {
2349
            if (($srcVolume = $this->volume($target)) == false) {
2350
                $result['warning'] = $this->error($error, '#'.$target, self::ERROR_FILE_NOT_FOUND);
2351
                break;
2352
            }
2353
2354
            $rnres = array();
2355
            if ($renames) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $renames 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...
2356
                $file = $srcVolume->file($target);
2357
                if (isset($renames[$file['name']])) {
2358
                    $dir = $dstVolume->realpath($dst);
2359
                    if (isset($hashes[$file['name']])) {
2360
                        $hash = $hashes[$file['name']];
2361
                    } else {
2362
                        $hash = $dstVolume->getHash($dir, $file['name']);
2363
                    }
2364
                    $rnres = $this->rename(array('target' => $hash, 'name' => $dstVolume->uniqueName($dir, $file['name'], $suffix, true, 0)));
2365
                    if (!empty($rnres['error'])) {
2366
                        $result['warning'] = $rnres['error'];
2367
                        break;
2368
                    }
2369
                }
2370
            }
2371
2372
            if (($file = $dstVolume->paste($srcVolume, $target, $dst, $cut, $hashes)) == false) {
2373
                $result['warning'] = $this->error($dstVolume->error());
2374
                break;
2375
            }
2376
2377
            $dirChange = ! empty($file['dirChange']);
2378
            unset($file['dirChange']);
2379
            if ($dirChange) {
2380
                $result['changed'][] = $file;
2381
            } else {
2382
                $result['added'][] = $file;
2383
            }
2384
            if ($rnres) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $rnres 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...
2385
                $result = array_merge_recursive($result, $rnres);
2386
            }
2387
        }
2388
        return $result;
2389
    }
2390
2391
    /**
2392
     * Return file content
2393
     *
2394
     * @param  array  $args  command arguments
2395
     * @return array
2396
     * @author Dmitry (dio) Levashov
2397
     **/
2398
    protected function get($args) {
2399
        $target = $args['target'];
2400
        $volume = $this->volume($target);
2401
2402
        if (!$volume || ($file = $volume->file($target)) == false) {
2403
            return array('error' => $this->error(self::ERROR_OPEN, '#'.$target, self::ERROR_FILE_NOT_FOUND));
2404
        }
2405
2406
        if (($content = $volume->getContents($target)) === false) {
2407
            return array('error' => $this->error(self::ERROR_OPEN, $volume->path($target), $volume->error()));
2408
        }
2409
2410
        if ($args['conv'] && function_exists('mb_detect_encoding') && function_exists('mb_convert_encoding')) {
2411
            $mime = isset($file['mime'])? $file['mime'] : '';
2412
            if ($mime && strtolower(substr($mime, 0, 4)) === 'text') {
2413
                if ($enc = mb_detect_encoding ( $content , mb_detect_order(), true)) {
2414
                    if (strtolower($enc) !== 'utf-8') {
2415
                        $content = mb_convert_encoding($content, 'UTF-8', $enc);
2416
                    }
2417
                }
2418
            }
2419
        }
2420
2421
        $json = json_encode($content);
2422
2423
        if ($json === false || strlen($json) < strlen($content)) {
2424
            if ($args['conv']) {
2425
                return array('error' => $this->error(self::ERROR_CONV_UTF8,self::ERROR_NOT_UTF8_CONTENT, $volume->path($target)));
2426
            } else {
2427
                return array('doconv' => true);
2428
            }
2429
        }
2430
2431
        return array('content' => $content);
2432
    }
2433
2434
    /**
2435
     * Save content into text file
2436
     *
2437
     * @param $args
2438
     * @return array
2439
     * @author Dmitry (dio) Levashov
2440
     */
2441
    protected function put($args) {
2442
        $target = $args['target'];
2443
2444
        if (($volume = $this->volume($target)) == false
2445
        || ($file = $volume->file($target)) == false) {
2446
            return array('error' => $this->error(self::ERROR_SAVE, '#'.$target, self::ERROR_FILE_NOT_FOUND));
2447
        }
2448
2449
        if (($file = $volume->putContents($target, $args['content'])) == false) {
2450
            return array('error' => $this->error(self::ERROR_SAVE, $volume->path($target), $volume->error()));
2451
        }
2452
2453
        return array('changed' => array($file));
2454
    }
2455
2456
    /**
2457
     * Extract files from archive
2458
     *
2459
     * @param  array  $args  command arguments
2460
     * @return array
2461
     * @author Dmitry (dio) Levashov,
2462
     * @author Alexey Sukhotin
2463
     **/
2464
    protected function extract($args) {
2465
        $target = $args['target'];
2466
        $mimes  = !empty($args['mimes']) && is_array($args['mimes']) ? $args['mimes'] : array();
0 ignored issues
show
$mimes is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
2467
        $error  = array(self::ERROR_EXTRACT, '#'.$target);
0 ignored issues
show
$error is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
2468
        $makedir = isset($args['makedir'])? (bool)$args['makedir'] : null;
2469
2470
        if (($volume = $this->volume($target)) == false
2471
        || ($file = $volume->file($target)) == false) {
2472
            return array('error' => $this->error(self::ERROR_EXTRACT, '#'.$target, self::ERROR_FILE_NOT_FOUND));
2473
        }
2474
2475
        return ($file = $volume->extract($target, $makedir))
2476
            ? array('added' => isset($file['read'])? array($file) : $file)
2477
            : array('error' => $this->error(self::ERROR_EXTRACT, $volume->path($target), $volume->error()));
2478
    }
2479
2480
    /**
2481
     * Create archive
2482
     *
2483
     * @param  array  $args  command arguments
2484
     * @return array
2485
     * @author Dmitry (dio) Levashov,
2486
     * @author Alexey Sukhotin
2487
     **/
2488
    protected function archive($args) {
2489
        $type    = $args['type'];
0 ignored issues
show
$type is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
2490
        $targets = isset($args['targets']) && is_array($args['targets']) ? $args['targets'] : array();
2491
        $name    = isset($args['name'])? $args['name'] : '';
2492
2493
        if (($volume = $this->volume($targets[0])) == false) {
2494
            return $this->error(self::ERROR_ARCHIVE, self::ERROR_TRGDIR_NOT_FOUND);
2495
        }
2496
2497
        return ($file = $volume->archive($targets, $args['type'], $name))
2498
            ? array('added' => array($file))
2499
            : array('error' => $this->error(self::ERROR_ARCHIVE, $volume->error()));
2500
    }
2501
2502
    /**
2503
     * Search files
2504
     *
2505
     * @param  array  $args  command arguments
2506
     * @return array
2507
     * @author Dmitry Levashov
2508
     **/
2509
    protected function search($args) {
2510
        $q      = trim($args['q']);
2511
        $mimes  = !empty($args['mimes']) && is_array($args['mimes']) ? $args['mimes'] : array();
2512
        $target = !empty($args['target'])? $args['target'] : null;
2513
        $result = array();
2514
        $errors = array();
2515
2516
        if ($target) {
2517
            if ($volume = $this->volume($target)) {
2518
                $result = $volume->search($q, $mimes, $target);
2519
                $errors = array_merge($errors, $volume->error());
2520
            }
2521
        } else {
2522
            foreach ($this->volumes as $volume) {
2523
                $result = array_merge($result, $volume->search($q, $mimes));
2524
                $errors = array_merge($errors, $volume->error());
2525
            }
2526
        }
2527
2528
        $result = array('files' => $result);
2529
        if ($errors) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $errors 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...
2530
            $result['warning'] = $errors;
2531
        }
2532
        return $result;
2533
    }
2534
2535
    /**
2536
     * Return file info (used by client "places" ui)
2537
     *
2538
     * @param  array  $args  command arguments
2539
     * @return array
2540
     * @author Dmitry Levashov
2541
     **/
2542
    protected function info($args) {
2543
        $files = array();
2544
        $sleep = 0;
0 ignored issues
show
$sleep is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
2545
        $compare = null;
2546
        // long polling mode
2547
        if ($args['compare'] && count($args['targets']) === 1) {
2548
            $compare = intval($args['compare']);
2549
            $hash = $args['targets'][0];
2550
            if ($volume = $this->volume($hash)) {
2551
                $standby = (int)$volume->getOption('plStandby');
2552
                $_compare = false;
2553
                if (($syncCheckFunc = $volume->getOption('syncCheckFunc')) && is_callable($syncCheckFunc)) {
2554
                    $_compare = call_user_func_array($syncCheckFunc, array($volume->realpath($hash), $standby, $compare, $volume, $this));
2555
                }
2556
                if ($_compare !== false) {
2557
                    $compare = $_compare;
2558
                } else {
2559
                    $sleep = max(1, (int)$volume->getOption('tsPlSleep'));
2560
                    $limit = max(1, $standby / $sleep) + 1;
2561
                    do {
2562
                        elFinder::extendTimeLimit(30 + $sleep);
2563
                        $volume->clearstatcache();
2564
                        if (($info = $volume->file($hash)) != false) {
2565
                            if ($info['ts'] != $compare) {
2566
                                $compare = $info['ts'];
2567
                                break;
2568
                            }
2569
                        } else {
2570
                            $compare = 0;
2571
                            break;
2572
                        }
2573
                        if (--$limit) {
2574
                            sleep($sleep);
2575
                        }
2576
                    } while($limit);
2577
                }
2578
            }
2579
        } else {
2580
            foreach ($args['targets'] as $hash) {
2581
                if (($volume = $this->volume($hash)) != false
2582
                && ($info = $volume->file($hash)) != false) {
2583
                    $info['path'] = $volume->path($hash);
2584
                    $files[] = $info;
2585
                }
2586
            }
2587
        }
2588
2589
        $result = array('files' => $files);
2590
        if (!is_null($compare)) {
2591
            $result['compare'] = strval($compare);
2592
        }
2593
        return $result;
2594
    }
2595
2596
    /**
2597
     * Return image dimensions
2598
     *
2599
     * @param  array  $args  command arguments
2600
     * @return array
2601
     * @author Dmitry (dio) Levashov
2602
     **/
2603
    protected function dim($args) {
2604
        $target = $args['target'];
2605
2606
        if (($volume = $this->volume($target)) != false) {
2607
            $dim = $volume->dimensions($target);
2608
            return $dim ? array('dim' => $dim) : array();
2609
        }
2610
        return array();
2611
    }
2612
2613
    /**
2614
     * Resize image
2615
     *
2616
     * @param  array  command arguments
2617
     * @return array
2618
     * @author Dmitry (dio) Levashov
2619
     * @author Alexey Sukhotin
2620
     **/
2621
    protected function resize($args) {
2622
        $target = $args['target'];
2623
        $width  = $args['width'];
2624
        $height = $args['height'];
2625
        $x      = (int)$args['x'];
2626
        $y      = (int)$args['y'];
2627
        $mode   = $args['mode'];
2628
        $bg     = null;
2629
        $degree = (int)$args['degree'];
2630
        $quality= (int)$args['quality'];
2631
2632
        if (($volume = $this->volume($target)) == false
2633
        || ($file = $volume->file($target)) == false) {
2634
            return array('error' => $this->error(self::ERROR_RESIZE, '#'.$target, self::ERROR_FILE_NOT_FOUND));
2635
        }
2636
2637
        return ($file = $volume->resize($target, $width, $height, $x, $y, $mode, $bg, $degree, $quality))
2638
            ? array('changed' => array($file))
2639
            : array('error' => $this->error(self::ERROR_RESIZE, $volume->path($target), $volume->error()));
2640
    }
2641
2642
    /**
2643
    * Return content URL
2644
    *
2645
    * @param  array  $args  command arguments
2646
    * @return array
2647
    * @author Naoki Sawada
2648
    **/
2649
    protected function url($args) {
2650
        $target = $args['target'];
2651
        $options = isset($args['options'])? $args['options'] : array();
2652
        if (($volume = $this->volume($target)) != false) {
2653
            $url = $volume->getContentUrl($target, $options);
2654
            return $url ? array('url' => $url) : array();
2655
        }
2656
        return array();
2657
    }
2658
2659
    /**
2660
     * Output callback result with JavaScript that control elFinder
2661
     * or HTTP redirect to callbackWindowURL
2662
     *
2663
     * @param  array  command arguments
2664
     * @author Naoki Sawada
2665
     */
2666
    protected function callback($args) {
2667
        $checkReg = '/[^a-zA-Z0-9;._-]/';
2668
        $node = (isset($args['node']) && !preg_match($checkReg, $args['node']))? $args['node'] : '';
2669
        $json = (isset($args['json']) && json_decode($args['json']))? $args['json'] : '{}';
2670
        $bind  = (isset($args['bind']) && !preg_match($checkReg, $args['bind']))? $args['bind'] : '';
2671
        $done = (!empty($args['done']));
2672
2673
        while( ob_get_level() ) {
2674
            if (! ob_end_clean()) {
2675
                break;
2676
            }
2677
        }
2678
2679
        if ($done || ! $this->callbackWindowURL) {
2680
            $script = '';
2681
            if ($node) {
2682
                $script .= '
2683
                    var w = window.opener || window.parent || window;
2684
                    try {
2685
                        var elf = w.document.getElementById(\''.$node.'\').elfinder;
2686
                        if (elf) {
2687
                            var data = '.$json.';
2688
                            if (data.error) {
2689
                                elf.error(data.error);
2690
                            } else {
2691
                                data.warning && elf.error(data.warning);
2692
                                data.removed && data.removed.length && elf.remove(data);
2693
                                data.added   && data.added.length   && elf.add(data);
2694
                                data.changed && data.changed.length && elf.change(data);';
2695
                if ($bind) {
2696
                    $script .= '
2697
                                elf.trigger(\''.$bind.'\', data);';
2698
                }
2699
                $script .= '
2700
                                data.sync && elf.sync();
2701
                            }
2702
                        }
2703
                    } catch(e) {
2704
                        // for CORS
2705
                        w.postMessage && w.postMessage(JSON.stringify({bind:\''.$bind.'\',data:'.$json.'}), \'*\');
2706
                    }';
2707
            }
2708
            $script .= 'window.close();';
2709
2710
            $out = '<!DOCTYPE html><html><head><meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><script>'.$script.'</script></head><body><a href="#" onlick="window.close();return false;">Close this window</a></body></html>';
2711
2712
            header('Content-Type: text/html; charset=utf-8');
2713
            header('Content-Length: '.strlen($out));
2714
            header('Cache-Control: private');
2715
            header('Pragma: no-cache');
2716
2717
            echo $out;
2718
2719
        } else {
2720
            $url = $this->callbackWindowURL;
2721
            $url .= ((strpos($url, '?') === false)? '?' : '&')
2722
                 . '&node=' . rawurlencode($node)
2723
                 . (($json !== '{}')? ('&json=' . rawurlencode($json)) : '')
2724
                 . ($bind? ('&bind=' .  rawurlencode($bind)) : '')
2725
                 . '&done=1';
2726
2727
            header('Location: ' . $url);
2728
2729
        }
2730
        exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method callback() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
2731
    }
2732
2733
    /**
2734
     * PHP error handler, catch error types only E_WARNING | E_NOTICE | E_USER_WARNING | E_USER_NOTICE
2735
     *
2736
     * @param int    $errno
2737
     * @param string $errstr
2738
     * @param string $errfile
2739
     * @param int    $errline
2740
     * @return void|boolean
2741
     */
2742
    public static function phpErrorHandler($errno, $errstr, $errfile, $errline) {
2743
        static $base = null;
2744
2745
        if (is_null($base)) {
2746
            $base = dirname(__FILE__) . DIRECTORY_SEPARATOR;
2747
        }
2748
2749
        if (! (error_reporting() & $errno)) {
2750
            return;
2751
        }
2752
2753
        $errfile = str_replace($base, '', $errfile);
2754
2755
        $proc = false;
2756
        switch ($errno) {
2757
            case E_WARNING:
2758
            case E_USER_WARNING:
2759
                elFinder::$phpErrors[] = "WARNING: $errstr in $errfile line $errline.";
2760
                $proc = true;
2761
                break;
2762
2763
            case E_NOTICE:
2764
            case E_USER_NOTICE:
2765
                elFinder::$phpErrors[] = "NOTICE: $errstr in $errfile line $errline.";
2766
                $proc = true;
2767
                break;
2768
        }
2769
2770
        return $proc;
2771
    }
2772
2773
    /***************************************************************************/
2774
    /*                                   utils                                 */
2775
    /***************************************************************************/
2776
2777
    /**
2778
     * Return root - file's owner
2779
     *
2780
     * @param  string  file hash
2781
     * @return elFinderStorageDriver
2782
     * @author Dmitry (dio) Levashov
2783
     **/
2784
    protected function volume($hash) {
2785
        foreach ($this->volumes as $id => $v) {
2786
            if (strpos(''.$hash, $id) === 0) {
2787
                return $this->volumes[$id];
2788
            }
2789
        }
2790
        return false;
2791
    }
2792
2793
    /**
2794
     * Return files info array
2795
     *
2796
     * @param  array  $data  one file info or files info
2797
     * @return array
2798
     * @author Dmitry (dio) Levashov
2799
     **/
2800
    protected function toArray($data) {
2801
        return isset($data['hash']) || !is_array($data) ? array($data) : $data;
2802
    }
2803
2804
    /**
2805
     * Return fils hashes list
2806
     *
2807
     * @param  array  $files  files info
2808
     * @return array
2809
     * @author Dmitry (dio) Levashov
2810
     **/
2811
    protected function hashes($files) {
2812
        $ret = array();
2813
        foreach ($files as $file) {
2814
            $ret[] = $file['hash'];
2815
        }
2816
        return $ret;
2817
    }
2818
2819
    /**
2820
     * Remove from files list hidden files and files with required mime types
2821
     *
2822
     * @param  array  $files  files info
2823
     * @return array
2824
     * @author Dmitry (dio) Levashov
2825
     **/
2826
    protected function filter($files) {
2827
        $exists = array();
2828
        foreach ($files as $i => $file) {
2829
            if (isset($exists[$file['hash']]) || !empty($file['hidden']) || !$this->default->mimeAccepted($file['mime'])) {
2830
                unset($files[$i]);
2831
            }
2832
            $exists[$file['hash']] = true;
2833
        }
2834
        return array_values($files);
2835
    }
2836
2837
    protected function utime() {
2838
        $time = explode(" ", microtime());
2839
        return (double)$time[1] + (double)$time[0];
2840
    }
2841
2842
    /**
2843
     * Return Network mount volume unique ID
2844
     *
2845
     * @param  array   $netVolumes  Saved netvolumes array
2846
     * @param  string  $prefix      Id prefix
2847
     * @return string|false
2848
     * @author Naoki Sawada
2849
     **/
2850
    protected function getNetVolumeUniqueId($netVolumes = null, $prefix = 'nm') {
2851
        $id = false;
0 ignored issues
show
$id is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
2852
        if (is_null($netVolumes)) {
2853
            $netVolumes = $this->getNetVolumes();
2854
        }
2855
        $ids = array();
2856
        foreach($netVolumes as $vOps) {
2857
            if (isset($vOps['id']) && strpos($vOps['id'], $prefix) === 0) {
2858
                $ids[$vOps['id']] = true;
2859
            }
2860
        }
2861
        if (! $ids) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $ids 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...
2862
            $id = $prefix.'1';
2863
        } else {
2864
            $i = 0;
2865
            while(isset($ids[$prefix.++$i]) && $i < 10000);
2866
            $id = $prefix.$i;
2867
            if (isset($ids[$id])) {
2868
                $id = false;
2869
            }
2870
        }
2871
        return $id;
2872
    }
2873
2874
    /**
2875
     * Ensure directories recursively
2876
     *
2877
     * @param  object  $volume  Volume object
2878
     * @param  string  $target  Target hash
2879
     * @param  string  $dirs    Array of directory tree to ensure
2880
     * @param  string  $path    Relative path form target hash
2881
     * @return array|false      array('stats' => array([stat of maked directory]), 'hashes' => array('[path]' => '[hash]'))
2882
     * @author Naoki Sawada
2883
     **/
2884
    protected function ensureDirsRecursively($volume, $target, $dirs, $path = '') {
2885
        $res = array('stats' => array(), 'hashes' => array());
2886
        foreach($dirs as $name => $sub) {
0 ignored issues
show
The expression $dirs of type string is not traversable.
Loading history...
2887
            $name = (string)$name;
2888
            if ((($parent = $volume->realpath($target)) && ($dir = $volume->dir($volume->getHash($parent, $name)))) || ($dir = $volume->mkdir($target, $name))) {
2889
                $_path = $path . '/' . $name;
2890
                $res['stats'][] = $dir;
2891
                $res['hashes'][$_path] = $dir['hash'];
2892
                if (count($sub)) {
2893
                    if ($subRes = $this->ensureDirsRecursively($volume, $dir['hash'], $sub, $_path)) {
2894
                        $res = array_merge_recursive($res, $subRes);
2895
                    } else {
2896
                        return false;
2897
                    }
2898
                }
2899
            } else {
2900
                return false;
2901
            }
2902
        }
2903
        return $res;
2904
    }
2905
2906
    /***************************************************************************/
2907
    /*                           static  utils                                 */
2908
    /***************************************************************************/
2909
2910
    /**
2911
     * Return Is Animation Gif
2912
     *
2913
     * @param  string $path server local path of target image
2914
     * @return bool
2915
     */
2916
    public static function isAnimationGif($path) {
2917
        list($width, $height, $type, $attr) = getimagesize($path);
0 ignored issues
show
The assignment to $width is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
The assignment to $height is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
The assignment to $attr is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
2918
        switch ($type) {
2919
            case IMAGETYPE_GIF:
2920
                break;
2921
            default:
2922
                return false;
2923
        }
2924
2925
        $imgcnt = 0;
2926
        $fp = fopen($path, 'rb');
2927
        fread($fp, 4);
2928
        $c = fread($fp,1);
2929
        if (ord($c) != 0x39) {  // GIF89a
2930
            return false;
2931
        }
2932
2933
        while (!feof($fp)) {
2934
            do {
2935
                $c = fread($fp, 1);
2936
            } while(ord($c) != 0x21 && !feof($fp));
2937
2938
            if (feof($fp)) {
2939
                break;
2940
            }
2941
2942
            $c2 = fread($fp,2);
2943
            if (bin2hex($c2) == "f904") {
2944
                $imgcnt++;
2945
            }
2946
2947
            if (feof($fp)) {
2948
                break;
2949
            }
2950
        }
2951
2952
        if ($imgcnt > 1) {
2953
            return true;
2954
        } else {
2955
            return false;
2956
        }
2957
    }
2958
2959
    /**
2960
     * Return Is seekable stream resource
2961
     *
2962
     * @param resource $resource
2963
     * @return bool
2964
     */
2965
    public static function isSeekableStream($resource) {
2966
        $metadata = stream_get_meta_data($resource);
2967
        return $metadata['seekable'];
2968
    }
2969
2970
    /**
2971
     * Rewind stream resource
2972
     *
2973
     * @param resource $resource
2974
     * @return void
2975
     */
2976
    public static function rewind($resource) {
2977
        self::isSeekableStream($resource) && rewind($resource);
2978
    }
2979
2980
    /**
2981
     * serialize and base64_encode of session data (If needed)
2982
     *
2983
     * @deprecated
2984
     * @param  mixed $var target variable
2985
     * @author Naoki Sawada
2986
     * @return mixed|string
2987
     */
2988
    public static function sessionDataEncode($var) {
2989
        if (self::$base64encodeSessionData) {
2990
            $var = base64_encode(serialize($var));
2991
        }
2992
        return $var;
2993
    }
2994
2995
    /**
2996
     * base64_decode and unserialize of session data  (If needed)
2997
     *
2998
     * @deprecated
2999
     * @param  mixed $var target variable
3000
     * @param  bool $checkIs data type for check (array|string|object|int)
3001
     * @author Naoki Sawada
3002
     * @return bool|mixed
3003
     */
3004
    public static function sessionDataDecode(&$var, $checkIs = null) {
3005
        if (self::$base64encodeSessionData) {
3006
            $data = unserialize(base64_decode($var));
3007
        } else {
3008
            $data = $var;
3009
        }
3010
        $chk = true;
3011
        if ($checkIs) {
3012
            switch ($checkIs) {
3013
                case 'array':
3014
                    $chk = is_array($data);
3015
                    break;
3016
                case 'string':
3017
                    $chk = is_string($data);
3018
                    break;
3019
                case 'object':
3020
                    $chk = is_object($data);
3021
                    break;
3022
                case 'int':
3023
                    $chk = is_int($data);
3024
                    break;
3025
            }
3026
        }
3027
        if (!$chk) {
3028
            unset($var);
3029
            return false;
3030
        }
3031
        return $data;
3032
    }
3033
3034
    /**
3035
     * Call session_write_close() if session is restarted
3036
     *
3037
     * @deprecated
3038
     * @return void
3039
     */
3040
    public static function sessionWrite() {
3041
        if (session_id()) {
3042
            session_write_close();
3043
        }
3044
    }
3045
3046
    /**
3047
     * Return elFinder static variable
3048
     *
3049
     * @param $key
3050
     * @return mixed|null
3051
     */
3052
    public static function getStaticVar($key) {
3053
        return isset(elFinder::$$key)? elFinder::$$key : null;
3054
    }
3055
3056
    /**
3057
     * Extend PHP execution time limit
3058
     *
3059
     * @param Int $time
3060
     * @return void
3061
     */
3062
    public static function extendTimeLimit($time = null) {
3063
        static $defLimit = null;
3064
        if (is_null($defLimit)) {
3065
            $defLimit = ini_get('max_execution_time');
3066
        }
3067
        if ($defLimit != 0) {
3068
            $time = is_null($time)? $defLimit : max($defLimit, $time);
3069
            set_time_limit($time);
3070
        }
3071
    }
3072
3073
} // END class
3074