Issues (661)

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.

php/elFinderVolumeFTP.class.php (22 issues)

Upgrade to new PHP Analysis Engine

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

1
<?php
2
3
elFinder::$netDrivers['ftp'] = 'FTP';
4
5
/**
6
 * Simple elFinder driver for FTP
7
 *
8
 * @author Dmitry (dio) Levashov
9
 * @author Cem (discofever)
10
 **/
11
class elFinderVolumeFTP extends elFinderVolumeDriver {
12
	
13
	/**
14
	 * Driver id
15
	 * Must be started from letter and contains [a-z0-9]
16
	 * Used as part of volume id
17
	 *
18
	 * @var string
19
	 **/
20
	protected $driverId = 'f';
21
	
22
	/**
23
	 * FTP Connection Instance
24
	 *
25
	 * @var ftp
26
	 **/
27
	protected $connect = null;
28
	
29
	/**
30
	 * Directory for tmp files
31
	 * If not set driver will try to use tmbDir as tmpDir
32
	 *
33
	 * @var string
34
	 **/
35
	protected $tmpPath = '';
36
	
37
	/**
38
	 * Last FTP error message
39
	 *
40
	 * @var string
41
	 **/
42
	protected $ftpError = '';
43
	
44
	/**
45
	 * FTP server output list as ftp on linux
46
	 *
47
	 * @var bool
48
	 **/
49
	protected $ftpOsUnix;
50
	
51
	/**
52
	 * Tmp folder path
53
	 *
54
	 * @var string
55
	 **/
56
	protected $tmp = '';
57
	
58
	/**
59
	 * Net mount key
60
	 *
61
	 * @var string
62
	 **/
63
	public $netMountKey = '';
64
	
65
	/**
66
	 * FTP command `MLST` support
67
	 * 
68
	 * @var bool
69
	 */
70
	private $MLSTsupprt = false;
71
	
72
	/**
73
	 * Calling cacheDir() target path with non-MLST
74
	 * 
75
	 * @var string
76
	 */
77
	private $cacheDirTarget = '';
78
	
79
	/**
80
	 * Constructor
81
	 * Extend options with required fields
82
	 *
83
	 * @return void
84
	 * @author Dmitry (dio) Levashov
85
	 * @author Cem (DiscoFever)
86
	 **/
87
	public function __construct() {
88
		$opts = array(
89
			'host'          => 'localhost',
90
			'user'          => '',
91
			'pass'          => '',
92
			'port'          => 21,
93
			'mode'        	=> 'passive',
94
			'path'			=> '/',
95
			'timeout'		=> 20,
96
			'owner'         => true,
97
			'tmbPath'       => '',
98
			'tmpPath'       => '',
99
			'dirMode'       => 0755,
100
			'fileMode'      => 0644,
101
			'rootCssClass'  => 'elfinder-navbar-root-ftp'
102
			
103
		);
104
		$this->options = array_merge($this->options, $opts); 
105
		$this->options['mimeDetect'] = 'internal';
106
		$this->options['maxArcFilesSize'] = 0;     // max allowed archive files size (0 - no limit)
107
	}
108
	
109
	/**
110
	 * Prepare
111
	 * Call from elFinder::netmout() before volume->mount()
112
	 *
113
	 * @return Array
114
	 * @author Naoki Sawada
115
	 **/
116
	public function netmountPrepare($options) {
0 ignored issues
show
netmountPrepare uses the super-global variable $_REQUEST 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...
117
		if (!empty($_REQUEST['encoding']) && @iconv('UTF-8', $_REQUEST['encoding'], '') !== false) {
118
			$options['encoding'] = $_REQUEST['encoding'];
119
			if (!empty($_REQUEST['locale']) && @setlocale(LC_ALL, $_REQUEST['locale'])) {
120
				setlocale(LC_ALL, elFinder::$locale);
121
				$options['locale'] = $_REQUEST['locale'];
122
			}
123
		}
124
		$options['statOwner'] = true;
125
		$options['allowChmodReadOnly'] = true;
126
		return $options;
127
	}
128
	
129
	/*********************************************************************/
130
	/*                        INIT AND CONFIGURE                         */
131
	/*********************************************************************/
132
	
133
	/**
134
	 * Prepare FTP connection
135
	 * Connect to remote server and check if credentials are correct, if so, store the connection id in $ftp_conn
136
	 *
137
	 * @return bool
138
	 * @author Dmitry (dio) Levashov
139
	 * @author Cem (DiscoFever)
140
	 **/
141
	protected function init() {
142
		if (!$this->options['host'] 
143
		||  !$this->options['port']) {
144
			return $this->setError('Required options undefined.');
145
		}
146
		
147
		if (!$this->options['user']) {
148
			$this->options['user'] = 'anonymous';
149
			$this->options['pass'] = '';
150
		}
151
		if (!$this->options['path']) {
152
			$this->options['path'] = '/';
153
		}
154
		
155
		// make ney mount key
156
		$this->netMountKey = md5(join('-', array('ftp', $this->options['host'], $this->options['port'], $this->options['path'], $this->options['user'])));
157
158
		if (!function_exists('ftp_connect')) {
159
			return $this->setError('FTP extension not loaded.');
160
		}
161
162
		// remove protocol from host
163
		$scheme = parse_url($this->options['host'], PHP_URL_SCHEME);
164
165
		if ($scheme) {
166
			$this->options['host'] = substr($this->options['host'], strlen($scheme)+3);
167
		}
168
169
		// normalize root path
170
		$this->root = $this->options['path'] = $this->_normpath($this->options['path']);
171
		
172 View Code Duplication
		if (empty($this->options['alias'])) {
173
			$this->options['alias'] = $this->options['user'].'@'.$this->options['host'];
174
			// $num = elFinder::$volumesCnt-1;
175
			// $this->options['alias'] = $this->root == '/' || $this->root == '.' ? 'FTP folder '.$num : basename($this->root);
176
		}
177
178
		$this->rootName = $this->options['alias'];
179
		$this->options['separator'] = '/';
180
		
181
		if (is_null($this->options['syncChkAsTs'])) {
182
			$this->options['syncChkAsTs'] = true;
183
		}
184
		
185
		return $this->connect();
186
		
187
	}
188
189
190
	/**
191
	 * Configure after successfull mount.
192
	 *
193
	 * @return void
194
	 * @author Dmitry (dio) Levashov
195
	 **/
196
	protected function configure() {
197
		parent::configure();
198
		
199 View Code Duplication
		if (!empty($this->options['tmpPath'])) {
200
			if ((is_dir($this->options['tmpPath']) || @mkdir($this->options['tmpPath'], 0755, true)) && is_writable($this->options['tmpPath'])) {
201
				$this->tmp = $this->options['tmpPath'];
202
			}
203
		}
204
		
205
		if (!$this->tmp && $this->tmbPath) {
206
			$this->tmp = $this->tmbPath;
207
		}
208
		
209
		if (!$this->tmp) {
210
			$this->disabled[] = 'mkfile';
211
			$this->disabled[] = 'paste';
212
			$this->disabled[] = 'duplicate';
213
			$this->disabled[] = 'upload';
214
			$this->disabled[] = 'edit';
215
			$this->disabled[] = 'archive';
216
			$this->disabled[] = 'extract';
217
		}
218
		
219
		// echo $this->tmp;
220
		
221
	}
222
	
223
	/**
224
	 * Connect to ftp server
225
	 *
226
	 * @return bool
227
	 * @author Dmitry (dio) Levashov
228
	 **/
229
	protected function connect() {
230 View Code Duplication
		if (!($this->connect = ftp_connect($this->options['host'], $this->options['port'], $this->options['timeout']))) {
231
			return $this->setError('Unable to connect to FTP server '.$this->options['host']);
232
		}
233 View Code Duplication
		if (!ftp_login($this->connect, $this->options['user'], $this->options['pass'])) {
234
			$this->umount();
235
			return $this->setError('Unable to login into '.$this->options['host']);
236
		}
237
		
238
		// try switch utf8 mode
239
		if ($this->encoding) {
240
			@ftp_exec($this->connect, 'OPTS UTF8 OFF');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
241
		} else {
242
			@ftp_exec($this->connect, 'OPTS UTF8 ON' );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
243
		}
244
		
245
		// switch off extended passive mode - may be usefull for some servers
246
		@ftp_exec($this->connect, 'epsv4 off' );
247
		// enter passive mode if required
248
		ftp_pasv($this->connect, $this->options['mode'] == 'passive');
249
250
		// enter root folder
251
		if (!ftp_chdir($this->connect, $this->root) 
252
		|| $this->root != ftp_pwd($this->connect)) {
253
			$this->umount();
254
			return $this->setError('Unable to open root folder.');
255
		}
256
		
257
		// check for MLST support
258
		$features = ftp_raw($this->connect, 'FEAT');
259
		if (!is_array($features)) {
260
			$this->umount();
261
			return $this->setError('Server does not support command FEAT.');
262
		}
263
264
		foreach ($features as $feat) {
265
			if (strpos(trim($feat), 'MLST') === 0) {
266
				$this->MLSTsupprt = true;
267
				break;
268
			}
269
		}
270
		
271
		return true;
272
	}
273
	
274
	/*********************************************************************/
275
	/*                               FS API                              */
276
	/*********************************************************************/
277
278
	/**
279
	 * Close opened connection
280
	 *
281
	 * @return void
282
	 * @author Dmitry (dio) Levashov
283
	 **/
284
	public function umount() {
285
		$this->connect && @ftp_close($this->connect);
286
	}
287
288
289
	/**
290
	 * Parse line from ftp_rawlist() output and return file stat (array)
291
	 *
292
	 * @param  string  $raw  line from ftp_rawlist() output
293
	 * @return array
294
	 * @author Dmitry Levashov
295
	 **/
296
	protected function parseRaw($raw, $base, $nameOnly = false) {
297
		$info = preg_split("/\s+/", $raw, 9);
298
		$stat = array();
299
300 View Code Duplication
		if (!isset($this->ftpOsUnix)) {
301
			$this->ftpOsUnix = !preg_match('/\d/', substr($info[0], 0, 1));
302
		}
303
		if (!$this->ftpOsUnix) {
304
			$info = $this->normalizeRawWindows($raw);
305
		}
306
		
307
		if (count($info) < 9 || $info[8] == '.' || $info[8] == '..') {
308
			return false;
309
		}
310
		
311
		$name = $info[8];
312
		
313
		if (preg_match('|(.+)\-\>(.+)|', $name, $m)) {
314
			$name   = trim($m[1]);
315
			// check recursive processing
316
			if ($this->cacheDirTarget && $this->_joinPath($base, $name) !== $this->cacheDirTarget) {
317
				return array();
318
			}
319
			if (!$nameOnly) {
320
				$target = trim($m[2]);
321
				if (substr($target, 0, 1) !== $this->separator) {
322
					$target = $this->getFullPath($target, $base);
323
				}
324
				$target = $this->_normpath($target);
325
				$stat['name']   = $name;
326
				$stat['target'] = $target;
327
				return $stat;
328
			}
329
		}
330
		
331
		if ($nameOnly) {
332
			return array('name' => $name);
333
		}
334
		
335
		if (is_numeric($info[5]) && !$info[6] && !$info[7]) {
336
			// by normalizeRawWindows()
337
			$stat['ts'] = $info[5];
338
		} else {
339
			$stat['ts'] = strtotime($info[5].' '.$info[6].' '.$info[7]);
340
			if (empty($stat['ts'])) {
341
				$stat['ts'] = strtotime($info[6].' '.$info[5].' '.$info[7]);
342
			}
343
		}
344
		
345
		$stat['owner'] = '';
346
		if ($this->options['statOwner']) {
347
			$stat['owner'] = $info[2];
348
			$stat['group'] = $info[3];
349
			$stat['perm']  = substr($info[0], 1);
350
			$stat['isowner'] = $stat['owner']? ($stat['owner'] == $this->options['user']) : $this->options['owner'];
351
		}
352
		
353
		$perm = $this->parsePermissions($info[0], $stat['owner']);
354
		$stat['name']  = $name;
355
		$stat['mime']  = substr(strtolower($info[0]), 0, 1) == 'd' ? 'directory' : $this->mimetype($stat['name']);
356
		$stat['size']  = $stat['mime'] == 'directory' ? 0 : $info[4];
357
		$stat['read']  = $perm['read'];
358
		$stat['write'] = $perm['write'];
359
		
360
		return $stat;
361
	}
362
	
363
	/**
364
	 * Normalize MS-DOS style FTP LIST Raw line
365
	 *
366
	 * @param  string  $raw  line from FTP LIST (MS-DOS style)
367
	 * @return array
368
	 * @author Naoki Sawada
369
	 **/
370
	protected function normalizeRawWindows($raw) {
371
		$info = array_pad(array(), 9, '');
372
		$item = preg_replace('#\s+#', ' ', trim($raw), 3);
373
		list($date, $time, $size, $name) = explode(' ', $item, 4);
374
		$format = strlen($date) === 8 ? 'm-d-yH:iA' : 'Y-m-dH:i';
375
		$dateObj = DateTime::createFromFormat($format, $date.$time);
0 ignored issues
show
The method createFromFormat() does not exist on DateTime. Did you maybe mean format()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
376
		$info[5] = strtotime($dateObj->format('Y-m-d H:i'));
377
		$info[8] = $name;
378
		if ($size === '<DIR>') {
379
			$info[4] = 0;
380
			$info[0] = 'drwxr-xr-x';
381
		} else {
382
			$info[4] = (int)$size;
383
			$info[0] = '-rw-r--r--';
384
		}
385
		return $info;
386
	}
387
	
388
	/**
389
	 * Parse permissions string. Return array(read => true/false, write => true/false)
390
	 *
391
	 * @param  string  $perm  permissions string
392
	 * @return string
393
	 * @author Dmitry (dio) Levashov
394
	 **/
395
	protected function parsePermissions($perm, $user = '') {
396
		$res   = array();
397
		$parts = array();
398
		$owner = $user? ($user == $this->options['user']) : $this->options['owner'];
399
		for ($i = 0, $l = strlen($perm); $i < $l; $i++) {
400
			$parts[] = substr($perm, $i, 1);
401
		}
402
403
		$read = ($owner && $parts[1] == 'r') || $parts[4] == 'r' || $parts[7] == 'r';
404
		
405
		return array(
406
			'read'  => $parts[0] == 'd' ? $read && (($owner && $parts[3] == 'x') || $parts[6] == 'x' || $parts[9] == 'x') : $read,
407
			'write' => ($owner && $parts[2] == 'w') || $parts[5] == 'w' || $parts[8] == 'w'
408
		);
409
	}
410
	
411
	/**
412
	 * Cache dir contents
413
	 *
414
	 * @param  string  $path  dir path
415
	 * @return void
416
	 * @author Dmitry Levashov
417
	 **/
418
 	protected function cacheDir($path) {
419
 		$this->dirsCache[$path] = array();
420
421
		$list = array();
422
		$encPath = $this->convEncIn($path);
423
		foreach (ftp_rawlist($this->connect, $encPath) as $raw) {
424
			if (($stat = $this->parseRaw($raw, $encPath))) {
425
				$list[] = $stat;
426
			}
427
		}
428
		$list = $this->convEncOut($list);
429
		$prefix = ($path === $this->separator)? $this->separator : $path . $this->separator;
430
		$targets = array();
431
		foreach($list as $stat) {
432
			$p = $prefix . $stat['name'];
433 View Code Duplication
			if (isset($stat['target'])) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
434
				// stat later
435
				$targets[$stat['name']] = $stat['target'];
436
			} else {
437
				$stat = $this->updateCache($p, $stat);
438
				if (empty($stat['hidden'])) {
439
					$this->dirsCache[$path][] = $p;
440
				}
441
			}
442
		}
443
		// stat link targets
444
		foreach($targets as $name => $target) {
445
			$stat = array();
446
			$stat['name'] = $name;
447
			$p = $prefix . $name;
448
			$cacheDirTarget = $this->cacheDirTarget;
449
			$this->cacheDirTarget = $this->convEncIn($target, true);
450
			if ($tstat = $this->stat($target)) {
451
				$stat['size']  = $tstat['size'];
452
				$stat['alias'] = $target;
453
				$stat['thash'] = $tstat['hash'];
454
				$stat['mime']  = $tstat['mime'];
455
				$stat['read']  = $tstat['read'];
456
				$stat['write']  = $tstat['write'];
457
				
458
				if (isset($tstat['ts']))      { $stat['ts']      = $tstat['ts']; }
459
				if (isset($tstat['owner']))   { $stat['owner']   = $tstat['owner']; }
460
				if (isset($tstat['group']))   { $stat['group']   = $tstat['group']; }
461
 				if (isset($tstat['perm']))    { $stat['perm']    = $tstat['perm']; }
462
 				if (isset($tstat['isowner'])) { $stat['isowner'] = $tstat['isowner']; }
463
			} else {
464
				
465
				$stat['mime']  = 'symlink-broken';
466
				$stat['read']  = false;
467
				$stat['write'] = false;
468
				$stat['size']  = 0;
469
				
470
			}
471
			$this->cacheDirTarget = $cacheDirTarget;
472
			$stat = $this->updateCache($p, $stat);
473
			if (empty($stat['hidden'])) {
474
				$this->dirsCache[$path][] = $p;
475
			}
476
		}
477
478
	}
479
480
	/**
481
	 * Return ftp transfer mode for file
482
	 *
483
	 * @param  string  $path  file path
484
	 * @return string
485
	 * @author Dmitry (dio) Levashov
486
	 **/
487
	protected function ftpMode($path) {
488
		return strpos($this->mimetype($path), 'text/') === 0 ? FTP_ASCII : FTP_BINARY;
489
	}
490
491
	/*********************** paths/urls *************************/
492
	
493
	/**
494
	 * Return parent directory path
495
	 *
496
	 * @param  string  $path  file path
497
	 * @return string
498
	 * @author Dmitry (dio) Levashov
499
	 **/
500
	protected function _dirname($path) {
501
		return dirname($path);
502
	}
503
504
	/**
505
	 * Return file name
506
	 *
507
	 * @param  string  $path  file path
508
	 * @return string
509
	 * @author Dmitry (dio) Levashov
510
	 **/
511
	protected function _basename($path) {
512
		return basename($path);
513
	}
514
515
	/**
516
	 * Join dir name and file name and retur full path
517
	 *
518
	 * @param  string  $dir
519
	 * @param  string  $name
520
	 * @return string
521
	 * @author Dmitry (dio) Levashov
522
	 **/
523
	protected function _joinPath($dir, $name) {
524
		return rtrim($dir, $this->separator).$this->separator.$name;
525
	}
526
	
527
	/**
528
	 * Return normalized path, this works the same as os.path.normpath() in Python
529
	 *
530
	 * @param  string  $path  path
531
	 * @return string
532
	 * @author Troex Nevelin
533
	 **/
534
	protected function _normpath($path) {
535
		if (empty($path)) {
536
			$path = '.';
537
		}
538
		// path must be start with /
539
		$path = preg_replace('|^\.\/?|', $this->separator, $path);
540
		$path = preg_replace('/^([^\/])/', "/$1", $path);
541
542
		if ($path[0] === $this->separator) {
543
			$initial_slashes = true;
544
		} else {
545
			$initial_slashes = false;
546
		}
547
			
548 View Code Duplication
		if (($initial_slashes) 
549
		&& (strpos($path, '//') === 0) 
550
		&& (strpos($path, '///') === false)) {
551
			$initial_slashes = 2;
552
		}
553
			
554
		$initial_slashes = (int) $initial_slashes;
555
556
		$comps = explode($this->separator, $path);
557
		$new_comps = array();
558 View Code Duplication
		foreach ($comps as $comp) {
559
			if (in_array($comp, array('', '.'))) {
560
				continue;
561
			}
562
				
563
			if (($comp != '..') 
564
			|| (!$initial_slashes && !$new_comps) 
565
			|| ($new_comps && (end($new_comps) == '..'))) {
566
				array_push($new_comps, $comp);
567
			} elseif ($new_comps) {
568
				array_pop($new_comps);
569
			}
570
		}
571
		$comps = $new_comps;
572
		$path = implode($this->separator, $comps);
573
		if ($initial_slashes) {
574
			$path = str_repeat($this->separator, $initial_slashes) . $path;
575
		}
576
		
577
		return $path ? $path : '.';
578
	}
579
	
580
	/**
581
	 * Return file path related to root dir
582
	 *
583
	 * @param  string  $path  file path
584
	 * @return string
585
	 * @author Dmitry (dio) Levashov
586
	 **/
587 View Code Duplication
	protected function _relpath($path) {
588
		if ($path === $this->root) {
589
			return '';
590
		} else {
591
			if (strpos($path, $this->root) === 0) {
592
				return ltrim(substr($path, strlen($this->root)), $this->separator);
593
			} else {
594
				// for link
595
				return $path;
596
			}
597
		}
598
	}
599
	
600
	/**
601
	 * Convert path related to root dir into real path
602
	 *
603
	 * @param  string  $path  file path
604
	 * @return string
605
	 * @author Dmitry (dio) Levashov
606
	 **/
607
	protected function _abspath($path) {
608
		if ($path === $this->separator) {
609
			return $this->root;
610
		} else {
611
			if ($path[0] === $this->separator) {
612
				// for link
613
				return $path;
614
			} else {
615
				return $this->_joinPath($this->root, $path);
616
			}
617
		}
618
	}
619
	
620
	/**
621
	 * Return fake path started from root dir
622
	 *
623
	 * @param  string  $path  file path
624
	 * @return string
625
	 * @author Dmitry (dio) Levashov
626
	 **/
627
	protected function _path($path) {
628
		return $this->rootName.($path == $this->root ? '' : $this->separator.$this->_relpath($path));
629
	}
630
	
631
	/**
632
	 * Return true if $path is children of $parent
633
	 *
634
	 * @param  string  $path    path to check
635
	 * @param  string  $parent  parent path
636
	 * @return bool
637
	 * @author Dmitry (dio) Levashov
638
	 **/
639
	protected function _inpath($path, $parent) {
640
		return $path == $parent || strpos($path, $parent. $this->separator) === 0;
641
	}
642
	
643
	/***************** file stat ********************/
644
	/**
645
	 * Return stat for given path.
646
	 * Stat contains following fields:
647
	 * - (int)    size    file size in b. required
648
	 * - (int)    ts      file modification time in unix time. required
649
	 * - (string) mime    mimetype. required for folders, others - optionally
650
	 * - (bool)   read    read permissions. required
651
	 * - (bool)   write   write permissions. required
652
	 * - (bool)   locked  is object locked. optionally
653
	 * - (bool)   hidden  is object hidden. optionally
654
	 * - (string) alias   for symlinks - link target path relative to root path. optionally
655
	 * - (string) target  for symlinks - link target path. optionally
656
	 *
657
	 * If file does not exists - returns empty array or false.
658
	 *
659
	 * @param  string  $path    file path 
660
	 * @return array|false
661
	 * @author Dmitry (dio) Levashov
662
	 **/
663
	protected function _stat($path) {
664
		$outPath = $this->convEncOut($path);
665
		if (isset($this->cache[$outPath])) {
666
			return $this->convEncIn($this->cache[$outPath]);
667
		} else {
668
			$this->convEncIn();
669
		}
670
		if (!$this->MLSTsupprt) {
671
			//if ($path == $this->root && (empty($this->ARGS['reload']) || !isset($this->ARGS['target']) || strpos($this->ARGS['target'], $this->id) !== 0)) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% 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...
672
			if ($path == $this->root && ! $this->isMyReload()) {
673
				return array(
674
					'name' => $this->root,
675
					'mime' => 'directory',
676
					'dirs' => $this->_subdirs($path)
677
				);
678
			}
679
			$this->cacheDir($this->convEncOut($this->_dirname($path)));
680
			return $this->convEncIn(isset($this->cache[$outPath])? $this->cache[$outPath] : array());
681
		}
682
		$raw = ftp_raw($this->connect, 'MLST ' . $path);
683
		if (is_array($raw) && count($raw) > 1 && substr(trim($raw[0]), 0, 1) == 2) {
684
			$parts = explode(';', trim($raw[1]));
685
			array_pop($parts);
686
			$parts = array_map('strtolower', $parts);
687
			$stat  = array();
688
			foreach ($parts as $part) {
689
690
				list($key, $val) = explode('=', $part);
691
692
				switch ($key) {
693
					case 'type':
694
						$stat['mime'] = strpos($val, 'dir') !== false ? 'directory' : $this->mimetype($path);
695
						break;
696
697
					case 'size':
698
						$stat['size'] = $val;
699
						break;
700
701
					case 'modify':
702
						$ts = mktime(intval(substr($val, 8, 2)), intval(substr($val, 10, 2)), intval(substr($val, 12, 2)), intval(substr($val, 4, 2)), intval(substr($val, 6, 2)), substr($val, 0, 4));
703
						$stat['ts'] = $ts;
704
						break;
705
706
					case 'unix.mode':
707
						$stat['chmod'] = $val;
708
						break;
709
710
					case 'perm':
711
						$val = strtolower($val);
712
						$stat['read']  = (int)preg_match('/e|l|r/', $val);
713
						$stat['write'] = (int)preg_match('/w|m|c/', $val);
714
						if (!preg_match('/f|d/', $val)) {
715
							$stat['locked'] = 1;
716
						}
717
						break;
718
				}
719
			}
720
			if (empty($stat['mime'])) {
721
				return array();
722
			}
723
			if ($stat['mime'] == 'directory') {
724
				$stat['size'] = 0;
725
			}
726
			
727
			if (isset($stat['chmod'])) {
728
				$stat['perm'] = '';
729
				if ($stat['chmod'][0] == 0) {
730
					$stat['chmod'] = substr($stat['chmod'], 1);
731
				}
732
733
				for ($i = 0; $i <= 2; $i++) {
734
					$perm[$i] = array(false, false, false);
735
					$n = isset($stat['chmod'][$i]) ? $stat['chmod'][$i] : 0;
736
					
737 View Code Duplication
					if ($n - 4 >= 0) {
738
						$perm[$i][0] = true;
739
						$n = $n - 4;
740
						$stat['perm'] .= 'r';
741
					} else {
742
						$stat['perm'] .= '-';
743
					}
744
					
745 View Code Duplication
					if ($n - 2 >= 0) {
746
						$perm[$i][1] = true;
747
						$n = $n - 2;
748
						$stat['perm'] .= 'w';
749
					} else {
750
						$stat['perm'] .= '-';
751
					}
752
753 View Code Duplication
					if ($n - 1 == 0) {
754
						$perm[$i][2] = true;
755
						$stat['perm'] .= 'x';
756
					} else {
757
						$stat['perm'] .= '-';
758
					}
759
					
760
					$stat['perm'] .= ' ';
761
				}
762
				
763
				$stat['perm'] = trim($stat['perm']);
764
765
				$owner = $this->options['owner'];
766
				$read = ($owner && $perm[0][0]) || $perm[1][0] || $perm[2][0];
767
768
				$stat['read']  = $stat['mime'] == 'directory' ? $read && (($owner && $perm[0][2]) || $perm[1][2] || $perm[2][2]) : $read;
769
				$stat['write'] = ($owner && $perm[0][1]) || $perm[1][1] || $perm[2][1];
770
				unset($stat['chmod']);
771
772
			}
773
			
774
			return $stat;
775
			
776
		}
777
		
778
		return array();
779
	}
780
	
781
	/**
782
	 * Return true if path is dir and has at least one childs directory
783
	 *
784
	 * @param  string  $path  dir path
785
	 * @return bool
786
	 * @author Dmitry (dio) Levashov
787
	 **/
788
	protected function _subdirs($path) {
789
		
790
		foreach (ftp_rawlist($this->connect, $path) as $str) {
791
			$info = preg_split('/\s+/', $str, 9);
792 View Code Duplication
			if (!isset($this->ftpOsUnix)) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
793
				$this->ftpOsUnix = !preg_match('/\d/', substr($info[0], 0, 1));
794
			}
795
			if (!$this->ftpOsUnix) {
796
				$info = $this->normalizeRawWindows($str);
797
			}
798
			$name = isset($info[8])? trim($info[8]) : '';
799
			if ($name && $name !== '.' && $name !== '..' && substr(strtolower($info[0]), 0, 1) === 'd') {
800
				return true;
801
			}
802
		}
803
		return false;
804
	}
805
	
806
	/**
807
	 * Return object width and height
808
	 * Ususaly used for images, but can be realize for video etc...
809
	 *
810
	 * @param  string  $path  file path
811
	 * @param  string  $mime  file mime type
812
	 * @return string|false
813
	 * @author Dmitry (dio) Levashov
814
	 **/
815 View Code Duplication
	protected function _dimensions($path, $mime) {
816
		$ret = false;
817
		if ($imgsize = $this->getImageSize($path, $mime)) {
818
			$ret = $imgsize['dimensions'];
819
		}
820
		return $ret;
821
	}
822
	
823
	/******************** file/dir content *********************/
824
		
825
	/**
826
	 * Return files list in directory.
827
	 *
828
	 * @param  string  $path  dir path
829
	 * @return array
830
	 * @author Dmitry (dio) Levashov
831
	 * @author Cem (DiscoFever)
832
	 **/
833
	protected function _scandir($path) {
834
		$files = array();
835
836
 		foreach (ftp_rawlist($this->connect, $path) as $str) {
837
 			if (($stat = $this->parseRaw($str, $path, true))) {
838
 				$files[] = $this->_joinPath($path, $stat['name']);
839
 			}
840
 		}
841
842
 		return $files;
843
	}
844
		
845
	/**
846
	 * Open file and return file pointer
847
	 *
848
	 * @param  string  $path  file path
849
	 * @param  bool    $write open file for writing
850
	 * @return resource|false
851
	 * @author Dmitry (dio) Levashov
852
	 **/
853
	protected function _fopen($path, $mode='rb') {
854
		// try ftp stream wrapper
855
		if (ini_get('allow_url_fopen')) {
856
			$url = 'ftp://'.$this->options['user'].':'.$this->options['pass'].'@'.$this->options['host'].':'.$this->options['port'].$path;
857
			if (strtolower($mode[0]) === 'w') {
858
				$context = stream_context_create(array('ftp' => array('overwrite' => true)));
859
				$fp = @fopen($url, $mode, false, $context);
860
			} else {
861
				$fp = @fopen($url, $mode);
862
			}
863
			if ($fp) {
864
				return $fp;
865
			}
866
		}
867
		
868
		if ($this->tmp) {
869
			$local = $this->getTempFile($path);
870
			$fp = @fopen($local, 'wb');
871
			if (ftp_fget($this->connect, $fp, $path, FTP_BINARY)) {
872
				fclose($fp);
873
				$fp = fopen($local, $mode);
874
				return $fp;
875
			}
876
			@fclose($fp);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
877
			is_file($local) && @unlink($local);
878
		}
879
		
880
		return false;
881
	}
882
	
883
	/**
884
	 * Close opened file
885
	 *
886
	 * @param  resource  $fp  file pointer
887
	 * @return bool
888
	 * @author Dmitry (dio) Levashov
889
	 **/
890
	protected function _fclose($fp, $path='') {
891
		@fclose($fp);
892
		if ($path) {
893
			@unlink($this->getTempFile($path));
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
894
		}
895
	}
896
	
897
	/********************  file/dir manipulations *************************/
898
	
899
	/**
900
	 * Create dir and return created dir path or false on failed
901
	 *
902
	 * @param  string  $path  parent dir path
903
	 * @param string  $name  new directory name
904
	 * @return string|bool
905
	 * @author Dmitry (dio) Levashov
906
	 **/
907
	protected function _mkdir($path, $name) {
908
		$path = $this->_joinPath($path, $name);
909
		if (ftp_mkdir($this->connect, $path) === false) {
910
			return false;
911
		} 
912
		
913
		$this->options['dirMode'] && @ftp_chmod($this->connect, $this->options['dirMode'], $path);
914
		return $path;
915
	}
916
	
917
	/**
918
	 * Create file and return it's path or false on failed
919
	 *
920
	 * @param  string  $path  parent dir path
921
	 * @param string  $name  new file name
922
	 * @return string|bool
923
	 * @author Dmitry (dio) Levashov
924
	 **/
925
	protected function _mkfile($path, $name) {
926
		if ($this->tmp) {
927
			$path = $this->_joinPath($path, $name);
928
			$local = $this->getTempFile();
929
			$res = touch($local) && ftp_put($this->connect, $path, $local, FTP_ASCII);
930
			@unlink($local);
931
			return $res ? $path : false;
932
		}
933
		return false;
934
	}
935
	
936
	/**
937
	 * Create symlink. FTP driver does not support symlinks.
938
	 *
939
	 * @param  string  $target  link target
940
	 * @param  string  $path    symlink path
941
	 * @return bool
942
	 * @author Dmitry (dio) Levashov
943
	 **/
944
	protected function _symlink($target, $path, $name) {
945
		return false;
946
	}
947
	
948
	/**
949
	 * Copy file into another file
950
	 *
951
	 * @param  string  $source     source file path
952
	 * @param  string  $targetDir  target directory path
953
	 * @param  string  $name       new file name
954
	 * @return bool
955
	 * @author Dmitry (dio) Levashov
956
	 **/
957
	protected function _copy($source, $targetDir, $name) {
958
		$res = false;
959
		
960
		if ($this->tmp) {
961
			$local  = $this->getTempFile();
962
			$target = $this->_joinPath($targetDir, $name);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->_joinPath($targetDir, $name); of type string adds the type string to the return on line 971 which is incompatible with the return type declared by the abstract method elFinderVolumeDriver::_copy of type boolean.
Loading history...
963
964 View Code Duplication
			if (ftp_get($this->connect, $local, $source, FTP_BINARY)
965
			&& ftp_put($this->connect, $target, $local, $this->ftpMode($target))) {
966
				$res = $target;
967
			}
968
			@unlink($local);
969
		}
970
		
971
		return $res;
972
	}
973
	
974
	/**
975
	 * Move file into another parent dir.
976
	 * Return new file path or false.
977
	 *
978
	 * @param  string  $source  source file path
979
	 * @param  string  $target  target dir path
980
	 * @param  string  $name    file name
981
	 * @return string|bool
982
	 * @author Dmitry (dio) Levashov
983
	 **/
984
	protected function _move($source, $targetDir, $name) {
985
		$target = $this->_joinPath($targetDir, $name);
986
		return ftp_rename($this->connect, $source, $target) ? $target : false;
987
	}
988
		
989
	/**
990
	 * Remove file
991
	 *
992
	 * @param  string  $path  file path
993
	 * @return bool
994
	 * @author Dmitry (dio) Levashov
995
	 **/
996
	protected function _unlink($path) {
997
		return ftp_delete($this->connect, $path);
998
	}
999
1000
	/**
1001
	 * Remove dir
1002
	 *
1003
	 * @param  string  $path  dir path
1004
	 * @return bool
1005
	 * @author Dmitry (dio) Levashov
1006
	 **/
1007
	protected function _rmdir($path) {
1008
		return ftp_rmdir($this->connect, $path);
1009
	}
1010
	
1011
	/**
1012
	 * Create new file and write into it from file pointer.
1013
	 * Return new file path or false on error.
1014
	 *
1015
	 * @param  resource  $fp   file pointer
1016
	 * @param  string    $dir  target dir path
1017
	 * @param  string    $name file name
1018
	 * @param  array     $stat file stat (required by some virtual fs)
1019
	 * @return bool|string
1020
	 * @author Dmitry (dio) Levashov
1021
	 **/
1022
	protected function _save($fp, $dir, $name, $stat) {
1023
		$path = $this->_joinPath($dir, $name);
1024
		return ftp_fput($this->connect, $path, $fp, $this->ftpMode($path))
1025
			? $path
1026
			: false;
1027
	}
1028
	
1029
	/**
1030
	 * Get file contents
1031
	 *
1032
	 * @param  string  $path  file path
1033
	 * @return string|false
1034
	 * @author Dmitry (dio) Levashov
1035
	 **/
1036
	protected function _getContents($path) {
1037
		$contents = '';
1038
		if (($fp = $this->_fopen($path))) {
1039
			while (!feof($fp)) {
1040
			  $contents .= fread($fp, 8192);
1041
			}
1042
			$this->_fclose($fp, $path);
1043
			return $contents;
1044
		}
1045
		return false;
1046
	}
1047
	
1048
	/**
1049
	 * Write a string to a file
1050
	 *
1051
	 * @param  string  $path     file path
1052
	 * @param  string  $content  new file content
1053
	 * @return bool
1054
	 * @author Dmitry (dio) Levashov
1055
	 **/
1056
	protected function _filePutContents($path, $content) {
1057
		$res = false;
1058
1059
		if ($this->tmp) {
1060
			$local = $this->getTempFile();
1061
			
1062 View Code Duplication
			if (@file_put_contents($local, $content, LOCK_EX) !== false
1063
			&& ($fp = @fopen($local, 'rb'))) {
1064
				clearstatcache();
1065
				$res  = ftp_fput($this->connect, $path, $fp, $this->ftpMode($path));
1066
				@fclose($fp);
1067
			}
1068
			file_exists($local) && @unlink($local);
1069
		}
1070
1071
		return $res;
1072
	}
1073
1074
	/**
1075
	 * Detect available archivers
1076
	 *
1077
	 * @return void
1078
	 **/
1079
	protected function _checkArchivers() {
1080
		$this->archivers = $this->getArchivers();
1081
		return;
1082
	}
1083
1084
	/**
1085
	 * chmod availability
1086
	 *
1087
	 * @return bool
1088
	 **/
1089
	protected function _chmod($path, $mode) {
1090
		$modeOct = is_string($mode) ? octdec($mode) : octdec(sprintf("%04o",$mode));
1091
		return @ftp_chmod($this->connect, $modeOct, $path);
1092
	}
1093
1094
	/**
1095
	 * Recursive symlinks search
1096
	 *
1097
	 * @param  string  $path  file/dir path
1098
	 * @return bool
1099
	 * @author Dmitry (dio) Levashov
1100
	 **/
1101
	protected function _findSymlinks($path) {
1102
		if (is_link($path)) {
1103
			return true;
1104
		}
1105
		if (is_dir($path)) {
1106 View Code Duplication
			foreach (scandir($path) as $name) {
1107
				if ($name != '.' && $name != '..') {
1108
					$p = $path.DIRECTORY_SEPARATOR.$name;
1109
					if (is_link($p)) {
1110
						return true;
1111
					}
1112
					if (is_dir($p) && $this->_findSymlinks($p)) {
1113
						return true;
1114
					} elseif (is_file($p)) {
1115
						$this->archiveSize += sprintf('%u', filesize($p));
0 ignored issues
show
The property archiveSize does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
1116
					}
1117
				}
1118
			}
1119
		} else {
1120
			$this->archiveSize += sprintf('%u', filesize($path));
1121
		}
1122
		
1123
		return false;
1124
	}
1125
1126
	/**
1127
	 * Extract files from archive
1128
	 *
1129
	 * @param  string  $path  archive path
1130
	 * @param  array   $arc   archiver command and arguments (same as in $this->archivers)
1131
	 * @return true
1132
	 * @author Dmitry (dio) Levashov,
1133
	 * @author Alexey Sukhotin
1134
	 **/
1135
	protected function _extract($path, $arc)
1136
	{
1137
		$dir = $this->tempDir();
1138
		if (!$dir) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $dir of type false|string 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...
1139
			return false;
1140
		}
1141
1142
		$basename = $this->_basename($path);
1143
		$localPath = $dir . DIRECTORY_SEPARATOR . $basename;
1144
1145
		if (!ftp_get($this->connect, $localPath, $path, FTP_BINARY)) {
1146
			//cleanup
1147
			$this->rmdirRecursive($dir);
1148
			return false;
1149
		}
1150
1151
		$this->unpackArchive($localPath, $arc);
1152
		
1153
		$filesToProcess = elFinderVolumeFTP::listFilesInDirectory($dir, true);
1154
		
1155
		// no files - extract error ?
1156
		if (empty($filesToProcess)) {
1157
			return false;
1158
		}
1159
		
1160
		$this->archiveSize = 0;
1161
		
1162
		// find symlinks
1163
		$symlinks = $this->_findSymlinks($dir);
1164
		
1165 View Code Duplication
		if ($symlinks) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1166
			$this->rmdirRecursive($dir);
1167
			return $this->setError(array_merge($this->error, array(elFinder::ERROR_ARC_SYMLINKS)));
1168
		}
1169
1170
		// check max files size
1171 View Code Duplication
		if ($this->options['maxArcFilesSize'] > 0 && $this->options['maxArcFilesSize'] < $this->archiveSize) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1172
			$this->rmdirRecursive($dir);
1173
			return $this->setError(elFinder::ERROR_ARC_MAXSIZE);
1174
		}
1175
		
1176
		$extractTo = $this->extractToNewdir; // 'auto', ture or false
0 ignored issues
show
Unused Code Comprehensibility introduced by
45% 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...
1177
		
1178
		// archive contains one item - extract in archive dir
1179
		$name = '';
1180
		$src = $dir . DIRECTORY_SEPARATOR . $filesToProcess[0];
1181
		if (($extractTo === 'auto' || !$extractTo) && count($filesToProcess) === 1 && is_file($src)) {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $extractTo (integer) and 'auto' (string) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
1182
			$name = $filesToProcess[0];
1183
		} else if ($extractTo === 'auto' || $extractTo) {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $extractTo (integer) and 'auto' (string) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
1184
			// for several files - create new directory
1185
			// create unique name for directory
1186
			$src = $dir;
1187
			$name = basename($path);
1188 View Code Duplication
			if (preg_match('/\.((tar\.(gz|bz|bz2|z|lzo))|cpio\.gz|ps\.gz|xcf\.(gz|bz2)|[a-z0-9]{1,4})$/i', $name, $m)) {
1189
				$name = substr($name, 0,  strlen($name)-strlen($m[0]));
1190
			}
1191
			$test = $this->_joinPath(dirname($path), $name);
1192
			if ($this->stat($test)) {
1193
				$name = $this->uniqueName(dirname($path), $name, '-', false);
1194
			}
1195
		}
1196
		
1197
		if ($name !== '' && is_file($src)) {
1198
			$result = $this->_joinPath(dirname($path), $name);
1199
1200
			if (! ftp_put($this->connect, $result, $src, FTP_BINARY)) {
1201
				$this->rmdirRecursive($dir);
1202
				return false;
1203
			}
1204
		} else {
1205
			$dstDir = $this->_dirname($path);
1206
			$result = array();
1207
			if (is_dir($src)) {
1208
				if (!$dstDir = $this->_mkdir($dstDir, $name)) {
1209
					$this->rmdirRecursive($dir);
1210
					return false;
1211
				}
1212
				$result[] = $dstDir;
1213
			}
1214
			foreach($filesToProcess as $name) {
1215
				$name = rtrim($name, DIRECTORY_SEPARATOR);
1216
				$src = $dir . DIRECTORY_SEPARATOR . $name;
1217
				if (is_dir($src)) {
1218
					$p = dirname($name);
1219
					$name = basename($name);
1220
					if (! $target = $this->_mkdir($this->_joinPath($dstDir, $p), $name)) {
1221
						$this->rmdirRecursive($dir);
1222
						return false;
1223
					}
1224 View Code Duplication
				} else {
1225
					$target = $this->_joinPath($dstDir, $name);
1226
					if (! ftp_put($this->connect, $target, $src, FTP_BINARY)) {
1227
						$this->rmdirRecursive($dir);
1228
						return false;
1229
					}
1230
				}
1231
				$result[] = $target;
1232
			}
1233
			if (!$result) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $result of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1234
				$this->rmdirRecursive($dir);
1235
				return false;
1236
			}
1237
		}
1238
		
1239
		is_dir($dir) && $this->rmdirRecursive($dir);
1240
		
1241
		$this->clearcache();
1242
		return $result? $result : false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $result ? $result : false; (string|array|false) is incompatible with the return type declared by the abstract method elFinderVolumeDriver::_extract of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
1243
	}
1244
1245
	/**
1246
	 * Create archive and return its path
1247
	 *
1248
	 * @param  string  $dir    target dir
1249
	 * @param  array   $files  files names list
1250
	 * @param  string  $name   archive name
1251
	 * @param  array   $arc    archiver options
1252
	 * @return string|bool
1253
	 * @author Dmitry (dio) Levashov,
1254
	 * @author Alexey Sukhotin
1255
	 **/
1256
	protected function _archive($dir, $files, $name, $arc)
1257
	{
1258
		// get current directory
1259
		$cwd = getcwd();
1260
1261
		$tmpDir = $this->tempDir();
1262
		if (!$tmpDir) {
1263
			return false;
1264
		}
1265
1266
		//download data
1267
		if (!$this->ftp_download_files($dir, $files, $tmpDir)) {
1268
			//cleanup
1269
			$this->rmdirRecursive($tmpDir);
1270
			return false;
1271
		}
1272
1273
		$remoteArchiveFile = false;
1274
		if ($path = $this->makeArchive($tmpDir, $files, $name, $arc)) {
1275
			$remoteArchiveFile = $this->_joinPath($dir, $name);
1276
			if (!ftp_put($this->connect, $remoteArchiveFile, $path, FTP_BINARY)) {
1277
				$remoteArchiveFile = false;
1278
			}
1279
		}
1280
1281
		//cleanup
1282
		if(!$this->rmdirRecursive($tmpDir)) {
1283
			return false;
1284
		}
1285
1286
		return $remoteArchiveFile;
1287
	}
1288
1289
	/**
1290
	 * Create writable temporary directory and return path to it.
1291
	 * @return string path to the new temporary directory or false in case of error.
1292
	 */
1293
	private function tempDir()
1294
	{
1295
		$tempPath = tempnam($this->tmp, 'elFinder');
1296
		if (!$tempPath) {
1297
			$this->setError(elFinder::ERROR_CREATING_TEMP_DIR, $this->tmp);
1298
			return false;
1299
		}
1300
		$success = unlink($tempPath);
1301
		if (!$success) {
1302
			$this->setError(elFinder::ERROR_CREATING_TEMP_DIR, $this->tmp);
1303
			return false;
1304
		}
1305
		$success = mkdir($tempPath, 0700, true);
1306
		if (!$success) {
1307
			$this->setError(elFinder::ERROR_CREATING_TEMP_DIR, $this->tmp);
1308
			return false;
1309
		}
1310
		return $tempPath;
1311
	}
1312
1313
	/**
1314
	 * Gets an array of absolute remote FTP paths of files and
1315
	 * folders in $remote_directory omitting symbolic links.
1316
	 * 
1317
	 * @param $remote_directory string remote FTP path to scan for file and folders recursively
1318
	 * @param $targets          array  Array of target item. `null` is to get all of items
1319
	 * @return array of elements each of which is an array of two elements:
1320
	 * <ul>
1321
	 * <li>$item['path'] - absolute remote FTP path</li>
1322
	 * <li>$item['type'] - either 'f' for file or 'd' for directory</li>
1323
	 * </ul>
1324
	 */
1325
	protected function ftp_scan_dir($remote_directory, $targets = null)
1326
	{
1327
		$buff = ftp_rawlist($this->connect, $remote_directory);
1328
		$items = array();
1329
		if ($targets && is_array($targets)) {
1330
			$targets = array_flip($targets);
1331
		} else {
1332
			$targets = false;
1333
		}
1334
		foreach ($buff as $str) {
1335
			$info = preg_split("/\s+/", $str, 9);
1336 View Code Duplication
			if (!isset($this->ftpOsUnix)) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1337
				$this->ftpOsUnix = !preg_match('/\d/', substr($info[0], 0, 1));
1338
			}
1339
			if (!$this->ftpOsUnix) {
1340
				$info = $this->normalizeRawWindows($str);
1341
			}
1342
			$type = substr($info[0], 0, 1);
1343
			$name = trim($info[8]);
1344
			if ($name !== '.' && $name !== '..' && (!$targets || isset($targets[$name]))) {
1345
				switch ($type) {
1346
					case 'l' : //omit symbolic links
0 ignored issues
show
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1347
					case 'd' :
0 ignored issues
show
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1348
						$remote_file_path = $this->_joinPath($remote_directory, $name);
1349
						$item = array();
1350
						$item['path'] = $remote_file_path;
1351
						$item['type'] = 'd'; // normal file
1352
						$items[] = $item;
1353
						$items = array_merge($items, $this->ftp_scan_dir($remote_file_path));
1354
						break;
1355
					default:
1356
						$remote_file_path = $this->_joinPath($remote_directory, $name);
1357
						$item = array();
1358
						$item['path'] = $remote_file_path;
1359
						$item['type'] = 'f'; // normal file
1360
						$items[] = $item;
1361
				}
1362
			}
1363
		}
1364
		return $items;
1365
	}
1366
1367
	/**
1368
	 * Downloads specified files from remote directory
1369
	 * if there is a directory among files it is downloaded recursively (omitting symbolic links).
1370
	 * 
1371
	 * @param $remote_directory string remote FTP path to a source directory to download from.
1372
	 * @param array $files list of files to download from remote directory.
1373
	 * @param $dest_local_directory string destination folder to store downloaded files.
1374
	 * @return bool true on success and false on failure.
1375
	 */
1376
	private function ftp_download_files($remote_directory, array $files, $dest_local_directory)
1377
	{
1378
		$contents = $this->ftp_scan_dir($remote_directory, $files);
1379
		if (!isset($contents)) {
1380
			$this->setError(elFinder::ERROR_FTP_DOWNLOAD_FILE, $remote_directory);
1381
			return false;
1382
		}
1383
		$remoteDirLen = strlen($remote_directory);
1384
		foreach ($contents as $item) {
1385
			$relative_path = substr($item['path'], $remoteDirLen);
1386
			$local_path = $dest_local_directory . DIRECTORY_SEPARATOR . $relative_path;
1387
			switch ($item['type']) {
1388
				case 'd':
1389
					$success = mkdir($local_path);
1390
					break;
1391
				case 'f':
1392
					$success = ftp_get($this->connect, $local_path, $item['path'], FTP_BINARY);
1393
					break;
1394
				default:
1395
					$success = true;
1396
			}
1397
			if (!$success) {
1398
				$this->setError(elFinder::ERROR_FTP_DOWNLOAD_FILE, $remote_directory);
1399
				return false;
1400
			}
1401
		}
1402
		return true;
1403
	}
1404
1405
	/**
1406
	 * Delete local directory recursively.
1407
	 * @param $dirPath string to directory to be erased.
1408
	 * @return bool true on success and false on failure.
1409
	 */
1410
	private function deleteDir($dirPath)
1411
	{
1412
		if (!is_dir($dirPath)) {
1413
			$success = unlink($dirPath);
1414
		} else {
1415
			$success = true;
1416
			foreach (array_reverse(elFinderVolumeFTP::listFilesInDirectory($dirPath, false)) as $path) {
1417
				$path = $dirPath . DIRECTORY_SEPARATOR . $path;
1418
				if(is_link($path)) {
1419
					unlink($path);
1420
				} else if (is_dir($path)) {
1421
					$success = rmdir($path);
1422
				} else {
1423
					$success = unlink($path);
1424
				}
1425
				if (!$success) {
1426
					break;
1427
				}
1428
			}
1429
			if($success) {
1430
				$success = rmdir($dirPath);
1431
			}
1432
		}
1433
		if(!$success) {
1434
			$this->setError(elFinder::ERROR_RM, $dirPath);
1435
			return false;
1436
		}
1437
		return $success;
1438
	}
1439
1440
	/**
1441
	 * Returns array of strings containing all files and folders in the specified local directory.
1442
	 * @param $dir
1443
	 * @param string $prefix
1444
	 * @internal param string $path path to directory to scan.
1445
	 * @return array array of files and folders names relative to the $path
1446
	 * or an empty array if the directory $path is empty,
1447
	 * <br />
1448
	 * false if $path is not a directory or does not exist.
1449
	 */
1450
	private static function listFilesInDirectory($dir, $omitSymlinks, $prefix = '')
1451
	{
1452
		if (!is_dir($dir)) {
1453
			return false;
1454
		}
1455
		$excludes = array(".","..");
1456
		$result = array();
1457
		$files = scandir($dir);
1458
		if(!$files) {
1459
			return array();
1460
		}
1461
		foreach($files as $file) {
1462
			if(!in_array($file, $excludes)) {
1463
				$path = $dir.DIRECTORY_SEPARATOR.$file;
1464
				if(is_link($path)) {
1465
					if($omitSymlinks) {
1466
						continue;
1467
					} else {
1468
						$result[] = $prefix.$file;
1469
					}
1470
				} else if(is_dir($path)) {
1471
					$result[] = $prefix.$file.DIRECTORY_SEPARATOR;
1472
					$subs = elFinderVolumeFTP::listFilesInDirectory($path, $omitSymlinks, $prefix.$file.DIRECTORY_SEPARATOR);
1473
					if($subs) {
1474
						$result = array_merge($result, $subs);
1475
					}
1476
					
1477
				} else {
1478
					$result[] = $prefix.$file;
1479
				}
1480
			}
1481
		}
1482
		return $result;
1483
	}
1484
1485
} // END class
1486
1487