1<?php 2 3/** 4 * Simple elFinder driver for FTP 5 * 6 * @author Dmitry (dio) Levashov 7 * @author Cem (discofever) 8 **/ 9class elFinderVolumeFTP extends elFinderVolumeDriver 10{ 11 12 /** 13 * Driver id 14 * Must be started from letter and contains [a-z0-9] 15 * Used as part of volume id 16 * 17 * @var string 18 **/ 19 protected $driverId = 'f'; 20 21 /** 22 * FTP Connection Instance 23 * 24 * @var resource a FTP stream 25 **/ 26 protected $connect = null; 27 28 /** 29 * Directory for tmp files 30 * If not set driver will try to use tmbDir as tmpDir 31 * 32 * @var string 33 **/ 34 protected $tmpPath = ''; 35 36 /** 37 * Last FTP error message 38 * 39 * @var string 40 **/ 41 protected $ftpError = ''; 42 43 /** 44 * FTP server output list as ftp on linux 45 * 46 * @var bool 47 **/ 48 protected $ftpOsUnix; 49 50 /** 51 * FTP LIST command option 52 * 53 * @var string 54 */ 55 protected $ftpListOption = '-al'; 56 57 58 /** 59 * Is connected server Pure FTPd? 60 * 61 * @var bool 62 */ 63 protected $isPureFtpd = false; 64 65 /** 66 * Is connected server with FTPS? 67 * 68 * @var bool 69 */ 70 protected $isFTPS = false; 71 72 /** 73 * Tmp folder path 74 * 75 * @var string 76 **/ 77 protected $tmp = ''; 78 79 /** 80 * FTP command `MLST` support 81 * 82 * @var bool 83 */ 84 private $MLSTsupprt = false; 85 86 /** 87 * Calling cacheDir() target path with non-MLST 88 * 89 * @var string 90 */ 91 private $cacheDirTarget = ''; 92 93 /** 94 * Constructor 95 * Extend options with required fields 96 * 97 * @author Dmitry (dio) Levashov 98 * @author Cem (DiscoFever) 99 */ 100 public function __construct() 101 { 102 $opts = array( 103 'host' => 'localhost', 104 'user' => '', 105 'pass' => '', 106 'port' => 21, 107 'mode' => 'passive', 108 'ssl' => false, 109 'path' => '/', 110 'timeout' => 20, 111 'owner' => true, 112 'tmbPath' => '', 113 'tmpPath' => '', 114 'separator' => '/', 115 'checkSubfolders' => -1, 116 'dirMode' => 0755, 117 'fileMode' => 0644, 118 'rootCssClass' => 'elfinder-navbar-root-ftp', 119 'ftpListOption' => '-al', 120 ); 121 $this->options = array_merge($this->options, $opts); 122 $this->options['mimeDetect'] = 'internal'; 123 } 124 125 /** 126 * Prepare 127 * Call from elFinder::netmout() before volume->mount() 128 * 129 * @param $options 130 * 131 * @return array volume root options 132 * @author Naoki Sawada 133 */ 134 public function netmountPrepare($options) 135 { 136 if (!empty($_REQUEST['encoding']) && iconv('UTF-8', $_REQUEST['encoding'], '') !== false) { 137 $options['encoding'] = $_REQUEST['encoding']; 138 if (!empty($_REQUEST['locale']) && setlocale(LC_ALL, $_REQUEST['locale'])) { 139 setlocale(LC_ALL, elFinder::$locale); 140 $options['locale'] = $_REQUEST['locale']; 141 } 142 } 143 if (!empty($_REQUEST['FTPS'])) { 144 $options['ssl'] = true; 145 } 146 $options['statOwner'] = true; 147 $options['allowChmodReadOnly'] = true; 148 $options['acceptedName'] = '#^[^/\\?*:|"<>]*[^./\\?*:|"<>]$#'; 149 return $options; 150 } 151 152 /*********************************************************************/ 153 /* INIT AND CONFIGURE */ 154 /*********************************************************************/ 155 156 /** 157 * Prepare FTP connection 158 * Connect to remote server and check if credentials are correct, if so, store the connection id in $ftp_conn 159 * 160 * @return bool 161 * @author Dmitry (dio) Levashov 162 * @author Cem (DiscoFever) 163 **/ 164 protected function init() 165 { 166 if (!$this->options['host'] 167 || !$this->options['port']) { 168 return $this->setError('Required options undefined.'); 169 } 170 171 if (!$this->options['user']) { 172 $this->options['user'] = 'anonymous'; 173 $this->options['pass'] = ''; 174 } 175 if (!$this->options['path']) { 176 $this->options['path'] = '/'; 177 } 178 179 // make ney mount key 180 $this->netMountKey = md5(join('-', array('ftp', $this->options['host'], $this->options['port'], $this->options['path'], $this->options['user']))); 181 182 if (!function_exists('ftp_connect')) { 183 return $this->setError('FTP extension not loaded.'); 184 } 185 186 // remove protocol from host 187 $scheme = parse_url($this->options['host'], PHP_URL_SCHEME); 188 189 if ($scheme) { 190 $this->options['host'] = substr($this->options['host'], strlen($scheme) + 3); 191 } 192 193 // normalize root path 194 $this->root = $this->options['path'] = $this->_normpath($this->options['path']); 195 196 if (empty($this->options['alias'])) { 197 $this->options['alias'] = $this->options['user'] . '@' . $this->options['host']; 198 if (!empty($this->options['netkey'])) { 199 elFinder::$instance->updateNetVolumeOption($this->options['netkey'], 'alias', $this->options['alias']); 200 } 201 } 202 203 $this->rootName = $this->options['alias']; 204 $this->options['separator'] = '/'; 205 206 if (is_null($this->options['syncChkAsTs'])) { 207 $this->options['syncChkAsTs'] = true; 208 } 209 210 if (isset($this->options['ftpListOption'])) { 211 $this->ftpListOption = $this->options['ftpListOption']; 212 } 213 214 return $this->needOnline? $this->connect() : true; 215 216 } 217 218 219 /** 220 * Configure after successfull mount. 221 * 222 * @return void 223 * @throws elFinderAbortException 224 * @author Dmitry (dio) Levashov 225 */ 226 protected function configure() 227 { 228 parent::configure(); 229 230 if (!empty($this->options['tmpPath'])) { 231 if ((is_dir($this->options['tmpPath']) || mkdir($this->options['tmpPath'], 0755, true)) && is_writable($this->options['tmpPath'])) { 232 $this->tmp = $this->options['tmpPath']; 233 } 234 } 235 if (!$this->tmp && ($tmp = elFinder::getStaticVar('commonTempPath'))) { 236 $this->tmp = $tmp; 237 } 238 239 // fallback of $this->tmp 240 if (!$this->tmp && $this->tmbPathWritable) { 241 $this->tmp = $this->tmbPath; 242 } 243 244 if (!$this->tmp) { 245 $this->disabled[] = 'mkfile'; 246 $this->disabled[] = 'paste'; 247 $this->disabled[] = 'duplicate'; 248 $this->disabled[] = 'upload'; 249 $this->disabled[] = 'edit'; 250 $this->disabled[] = 'archive'; 251 $this->disabled[] = 'extract'; 252 } 253 254 // echo $this->tmp; 255 256 } 257 258 /** 259 * Connect to ftp server 260 * 261 * @return bool 262 * @author Dmitry (dio) Levashov 263 **/ 264 protected function connect() 265 { 266 $withSSL = empty($this->options['ssl']) ? '' : ' with SSL'; 267 if ($withSSL) { 268 if (!function_exists('ftp_ssl_connect') || !($this->connect = ftp_ssl_connect($this->options['host'], $this->options['port'], $this->options['timeout']))) { 269 return $this->setError('Unable to connect to FTP server ' . $this->options['host'] . $withSSL); 270 } 271 $this->isFTPS = true; 272 } else { 273 if (!($this->connect = ftp_connect($this->options['host'], $this->options['port'], $this->options['timeout']))) { 274 return $this->setError('Unable to connect to FTP server ' . $this->options['host']); 275 } 276 } 277 if (!ftp_login($this->connect, $this->options['user'], $this->options['pass'])) { 278 $this->umount(); 279 return $this->setError('Unable to login into ' . $this->options['host'] . $withSSL); 280 } 281 282 // try switch utf8 mode 283 if ($this->encoding) { 284 ftp_raw($this->connect, 'OPTS UTF8 OFF'); 285 } else { 286 ftp_raw($this->connect, 'OPTS UTF8 ON'); 287 } 288 289 $help = ftp_raw($this->connect, 'HELP'); 290 $this->isPureFtpd = stripos(implode(' ', $help), 'Pure-FTPd') !== false; 291 292 if (!$this->isPureFtpd) { 293 // switch off extended passive mode - may be usefull for some servers 294 // this command, for pure-ftpd, doesn't work and takes a timeout in some pure-ftpd versions 295 ftp_raw($this->connect, 'epsv4 off'); 296 } 297 // enter passive mode if required 298 $pasv = ($this->options['mode'] == 'passive'); 299 if (!ftp_pasv($this->connect, $pasv)) { 300 if ($pasv) { 301 $this->options['mode'] = 'active'; 302 } 303 } 304 305 // enter root folder 306 if (!ftp_chdir($this->connect, $this->root) 307 || $this->root != ftp_pwd($this->connect)) { 308 $this->umount(); 309 return $this->setError('Unable to open root folder.'); 310 } 311 312 // check for MLST support 313 $features = ftp_raw($this->connect, 'FEAT'); 314 if (!is_array($features)) { 315 $this->umount(); 316 return $this->setError('Server does not support command FEAT.'); 317 } 318 319 foreach ($features as $feat) { 320 if (strpos(trim($feat), 'MLST') === 0) { 321 $this->MLSTsupprt = true; 322 break; 323 } 324 } 325 326 return true; 327 } 328 329 /** 330 * Call ftp_rawlist with option prefix 331 * 332 * @param string $path 333 * 334 * @return array 335 */ 336 protected function ftpRawList($path) 337 { 338 if ($this->isPureFtpd) { 339 $path = str_replace(' ', '\ ', $path); 340 } 341 if ($this->ftpListOption) { 342 $path = $this->ftpListOption . ' ' . $path; 343 } 344 $res = ftp_rawlist($this->connect, $path); 345 if ($res === false) { 346 $res = array(); 347 } 348 return $res; 349 } 350 351 /*********************************************************************/ 352 /* FS API */ 353 /*********************************************************************/ 354 355 /** 356 * Close opened connection 357 * 358 * @return void 359 * @author Dmitry (dio) Levashov 360 **/ 361 public function umount() 362 { 363 $this->connect && ftp_close($this->connect); 364 } 365 366 367 /** 368 * Parse line from ftp_rawlist() output and return file stat (array) 369 * 370 * @param string $raw line from ftp_rawlist() output 371 * @param $base 372 * @param bool $nameOnly 373 * 374 * @return array 375 * @author Dmitry Levashov 376 */ 377 protected function parseRaw($raw, $base, $nameOnly = false) 378 { 379 static $now; 380 static $lastyear; 381 382 if (!$now) { 383 $now = time(); 384 $lastyear = date('Y') - 1; 385 } 386 387 $info = preg_split("/\s+/", $raw, 8); 388 if (isset($info[7])) { 389 list($info[7], $info[8]) = explode(' ', $info[7], 2); 390 } 391 $stat = array(); 392 393 if (!isset($this->ftpOsUnix)) { 394 $this->ftpOsUnix = !preg_match('/\d/', substr($info[0], 0, 1)); 395 } 396 if (!$this->ftpOsUnix) { 397 $info = $this->normalizeRawWindows($raw); 398 } 399 400 if (count($info) < 9 || $info[8] == '.' || $info[8] == '..') { 401 return false; 402 } 403 404 $name = $info[8]; 405 406 if (preg_match('|(.+)\-\>(.+)|', $name, $m)) { 407 $name = trim($m[1]); 408 // check recursive processing 409 if ($this->cacheDirTarget && $this->_joinPath($base, $name) !== $this->cacheDirTarget) { 410 return array(); 411 } 412 if (!$nameOnly) { 413 $target = trim($m[2]); 414 if (substr($target, 0, 1) !== $this->separator) { 415 $target = $this->getFullPath($target, $base); 416 } 417 $target = $this->_normpath($target); 418 $stat['name'] = $name; 419 $stat['target'] = $target; 420 return $stat; 421 } 422 } 423 424 if ($nameOnly) { 425 return array('name' => $name); 426 } 427 428 if (is_numeric($info[5]) && !$info[6] && !$info[7]) { 429 // by normalizeRawWindows() 430 $stat['ts'] = $info[5]; 431 } else { 432 $stat['ts'] = strtotime($info[5] . ' ' . $info[6] . ' ' . $info[7]); 433 if ($stat['ts'] && $stat['ts'] > $now && strpos($info[7], ':') !== false) { 434 $stat['ts'] = strtotime($info[5] . ' ' . $info[6] . ' ' . $lastyear . ' ' . $info[7]); 435 } 436 if (empty($stat['ts'])) { 437 $stat['ts'] = strtotime($info[6] . ' ' . $info[5] . ' ' . $info[7]); 438 if ($stat['ts'] && $stat['ts'] > $now && strpos($info[7], ':') !== false) { 439 $stat['ts'] = strtotime($info[6] . ' ' . $info[5] . ' ' . $lastyear . ' ' . $info[7]); 440 } 441 } 442 } 443 444 if ($this->options['statOwner']) { 445 $stat['owner'] = $info[2]; 446 $stat['group'] = $info[3]; 447 $stat['perm'] = substr($info[0], 1); 448 // 449 // if not exists owner in LS ftp ==> isowner = true 450 // if is defined as option : 'owner' => true isowner = true 451 // 452 // if exist owner in LS ftp and 'owner' => False isowner = result of owner(file) == user(logged with ftp) 453 // 454 $stat['isowner'] = isset($stat['owner']) ? ($this->options['owner'] ? true : ($stat['owner'] == $this->options['user'])) : true; 455 } 456 457 $owner_computed = isset($stat['isowner']) ? $stat['isowner'] : $this->options['owner']; 458 $perm = $this->parsePermissions($info[0], $owner_computed); 459 $stat['name'] = $name; 460 $stat['mime'] = substr(strtolower($info[0]), 0, 1) == 'd' ? 'directory' : $this->mimetype($stat['name'], true); 461 $stat['size'] = $stat['mime'] == 'directory' ? 0 : $info[4]; 462 $stat['read'] = $perm['read']; 463 $stat['write'] = $perm['write']; 464 465 return $stat; 466 } 467 468 /** 469 * Normalize MS-DOS style FTP LIST Raw line 470 * 471 * @param string $raw line from FTP LIST (MS-DOS style) 472 * 473 * @return array 474 * @author Naoki Sawada 475 **/ 476 protected function normalizeRawWindows($raw) 477 { 478 $info = array_pad(array(), 9, ''); 479 $item = preg_replace('#\s+#', ' ', trim($raw), 3); 480 list($date, $time, $size, $name) = explode(' ', $item, 4); 481 $format = strlen($date) === 8 ? 'm-d-yH:iA' : 'Y-m-dH:i'; 482 $dateObj = DateTime::createFromFormat($format, $date . $time); 483 $info[5] = strtotime($dateObj->format('Y-m-d H:i')); 484 $info[8] = $name; 485 if ($size === '<DIR>') { 486 $info[4] = 0; 487 $info[0] = 'drwxr-xr-x'; 488 } else { 489 $info[4] = (int)$size; 490 $info[0] = '-rw-r--r--'; 491 } 492 return $info; 493 } 494 495 /** 496 * Parse permissions string. Return array(read => true/false, write => true/false) 497 * 498 * @param string $perm permissions string 'rwx' + 'rwx' + 'rwx' 499 * ^ ^ ^ 500 * | | +-> others 501 * | +---------> group 502 * +-----------------> owner 503 * The isowner parameter is computed by the caller. 504 * If the owner parameter in the options is true, the user is the actual owner of all objects even if che user used in the ftp Login 505 * is different from the file owner id. 506 * If the owner parameter is false to understand if the user is the file owner we compare the ftp user with the file owner id. 507 * @param Boolean $isowner . Tell if the current user is the owner of the object. 508 * 509 * @return array 510 * @author Dmitry (dio) Levashov 511 * @author Ugo Vierucci 512 */ 513 protected function parsePermissions($perm, $isowner = true) 514 { 515 $res = array(); 516 $parts = array(); 517 for ($i = 0, $l = strlen($perm); $i < $l; $i++) { 518 $parts[] = substr($perm, $i, 1); 519 } 520 521 $read = ($isowner && $parts[1] == 'r') || $parts[4] == 'r' || $parts[7] == 'r'; 522 523 return array( 524 'read' => $parts[0] == 'd' ? $read && (($isowner && $parts[3] == 'x') || $parts[6] == 'x' || $parts[9] == 'x') : $read, 525 'write' => ($isowner && $parts[2] == 'w') || $parts[5] == 'w' || $parts[8] == 'w' 526 ); 527 } 528 529 /** 530 * Cache dir contents 531 * 532 * @param string $path dir path 533 * 534 * @return void 535 * @author Dmitry Levashov 536 **/ 537 protected function cacheDir($path) 538 { 539 $this->dirsCache[$path] = array(); 540 $hasDir = false; 541 542 $list = array(); 543 $encPath = $this->convEncIn($path); 544 foreach ($this->ftpRawList($encPath) as $raw) { 545 if (($stat = $this->parseRaw($raw, $encPath))) { 546 $list[] = $stat; 547 } 548 } 549 $list = $this->convEncOut($list); 550 $prefix = ($path === $this->separator) ? $this->separator : $path . $this->separator; 551 $targets = array(); 552 foreach ($list as $stat) { 553 $p = $prefix . $stat['name']; 554 if (isset($stat['target'])) { 555 // stat later 556 $targets[$stat['name']] = $stat['target']; 557 } else { 558 $stat = $this->updateCache($p, $stat); 559 if (empty($stat['hidden'])) { 560 if (!$hasDir && $stat['mime'] === 'directory') { 561 $hasDir = true; 562 } 563 $this->dirsCache[$path][] = $p; 564 } 565 } 566 } 567 // stat link targets 568 foreach ($targets as $name => $target) { 569 $stat = array(); 570 $stat['name'] = $name; 571 $p = $prefix . $name; 572 $cacheDirTarget = $this->cacheDirTarget; 573 $this->cacheDirTarget = $this->convEncIn($target, true); 574 if ($tstat = $this->stat($target)) { 575 $stat['size'] = $tstat['size']; 576 $stat['alias'] = $target; 577 $stat['thash'] = $tstat['hash']; 578 $stat['mime'] = $tstat['mime']; 579 $stat['read'] = $tstat['read']; 580 $stat['write'] = $tstat['write']; 581 582 if (isset($tstat['ts'])) { 583 $stat['ts'] = $tstat['ts']; 584 } 585 if (isset($tstat['owner'])) { 586 $stat['owner'] = $tstat['owner']; 587 } 588 if (isset($tstat['group'])) { 589 $stat['group'] = $tstat['group']; 590 } 591 if (isset($tstat['perm'])) { 592 $stat['perm'] = $tstat['perm']; 593 } 594 if (isset($tstat['isowner'])) { 595 $stat['isowner'] = $tstat['isowner']; 596 } 597 } else { 598 599 $stat['mime'] = 'symlink-broken'; 600 $stat['read'] = false; 601 $stat['write'] = false; 602 $stat['size'] = 0; 603 604 } 605 $this->cacheDirTarget = $cacheDirTarget; 606 $stat = $this->updateCache($p, $stat); 607 if (empty($stat['hidden'])) { 608 if (!$hasDir && $stat['mime'] === 'directory') { 609 $hasDir = true; 610 } 611 $this->dirsCache[$path][] = $p; 612 } 613 } 614 615 if (isset($this->sessionCache['subdirs'])) { 616 $this->sessionCache['subdirs'][$path] = $hasDir; 617 } 618 } 619 620 /** 621 * Return ftp transfer mode for file 622 * 623 * @param string $path file path 624 * 625 * @return string 626 * @author Dmitry (dio) Levashov 627 **/ 628 protected function ftpMode($path) 629 { 630 return strpos($this->mimetype($path), 'text/') === 0 ? FTP_ASCII : FTP_BINARY; 631 } 632 633 /*********************** paths/urls *************************/ 634 635 /** 636 * Return parent directory path 637 * 638 * @param string $path file path 639 * 640 * @return string 641 * @author Naoki Sawada 642 **/ 643 protected function _dirname($path) 644 { 645 $parts = explode($this->separator, trim($path, $this->separator)); 646 array_pop($parts); 647 return $this->separator . join($this->separator, $parts); 648 } 649 650 /** 651 * Return file name 652 * 653 * @param string $path file path 654 * 655 * @return string 656 * @author Naoki Sawada 657 **/ 658 protected function _basename($path) 659 { 660 $parts = explode($this->separator, trim($path, $this->separator)); 661 return array_pop($parts); 662 } 663 664 /** 665 * Join dir name and file name and retur full path 666 * 667 * @param string $dir 668 * @param string $name 669 * 670 * @return string 671 * @author Dmitry (dio) Levashov 672 **/ 673 protected function _joinPath($dir, $name) 674 { 675 return rtrim($dir, $this->separator) . $this->separator . $name; 676 } 677 678 /** 679 * Return normalized path, this works the same as os.path.normpath() in Python 680 * 681 * @param string $path path 682 * 683 * @return string 684 * @author Troex Nevelin 685 **/ 686 protected function _normpath($path) 687 { 688 if (empty($path)) { 689 $path = '.'; 690 } 691 // path must be start with / 692 $path = preg_replace('|^\.\/?|', $this->separator, $path); 693 $path = preg_replace('/^([^\/])/', "/$1", $path); 694 695 if ($path[0] === $this->separator) { 696 $initial_slashes = true; 697 } else { 698 $initial_slashes = false; 699 } 700 701 if (($initial_slashes) 702 && (strpos($path, '//') === 0) 703 && (strpos($path, '///') === false)) { 704 $initial_slashes = 2; 705 } 706 707 $initial_slashes = (int)$initial_slashes; 708 709 $comps = explode($this->separator, $path); 710 $new_comps = array(); 711 foreach ($comps as $comp) { 712 if (in_array($comp, array('', '.'))) { 713 continue; 714 } 715 716 if (($comp != '..') 717 || (!$initial_slashes && !$new_comps) 718 || ($new_comps && (end($new_comps) == '..'))) { 719 array_push($new_comps, $comp); 720 } elseif ($new_comps) { 721 array_pop($new_comps); 722 } 723 } 724 $comps = $new_comps; 725 $path = implode($this->separator, $comps); 726 if ($initial_slashes) { 727 $path = str_repeat($this->separator, $initial_slashes) . $path; 728 } 729 730 return $path ? $path : '.'; 731 } 732 733 /** 734 * Return file path related to root dir 735 * 736 * @param string $path file path 737 * 738 * @return string 739 * @author Dmitry (dio) Levashov 740 **/ 741 protected function _relpath($path) 742 { 743 if ($path === $this->root) { 744 return ''; 745 } else { 746 if (strpos($path, $this->root) === 0) { 747 return ltrim(substr($path, strlen($this->root)), $this->separator); 748 } else { 749 // for link 750 return $path; 751 } 752 } 753 } 754 755 /** 756 * Convert path related to root dir into real path 757 * 758 * @param string $path file path 759 * 760 * @return string 761 * @author Dmitry (dio) Levashov 762 **/ 763 protected function _abspath($path) 764 { 765 if ($path === $this->separator) { 766 return $this->root; 767 } else { 768 if ($path[0] === $this->separator) { 769 // for link 770 return $path; 771 } else { 772 return $this->_joinPath($this->root, $path); 773 } 774 } 775 } 776 777 /** 778 * Return fake path started from root dir 779 * 780 * @param string $path file path 781 * 782 * @return string 783 * @author Dmitry (dio) Levashov 784 **/ 785 protected function _path($path) 786 { 787 return $this->rootName . ($path == $this->root ? '' : $this->separator . $this->_relpath($path)); 788 } 789 790 /** 791 * Return true if $path is children of $parent 792 * 793 * @param string $path path to check 794 * @param string $parent parent path 795 * 796 * @return bool 797 * @author Dmitry (dio) Levashov 798 **/ 799 protected function _inpath($path, $parent) 800 { 801 return $path == $parent || strpos($path, rtrim($parent, $this->separator) . $this->separator) === 0; 802 } 803 804 /***************** file stat ********************/ 805 /** 806 * Return stat for given path. 807 * Stat contains following fields: 808 * - (int) size file size in b. required 809 * - (int) ts file modification time in unix time. required 810 * - (string) mime mimetype. required for folders, others - optionally 811 * - (bool) read read permissions. required 812 * - (bool) write write permissions. required 813 * - (bool) locked is object locked. optionally 814 * - (bool) hidden is object hidden. optionally 815 * - (string) alias for symlinks - link target path relative to root path. optionally 816 * - (string) target for symlinks - link target path. optionally 817 * If file does not exists - returns empty array or false. 818 * 819 * @param string $path file path 820 * 821 * @return array|false 822 * @author Dmitry (dio) Levashov 823 **/ 824 protected function _stat($path) 825 { 826 $outPath = $this->convEncOut($path); 827 if (isset($this->cache[$outPath])) { 828 return $this->convEncIn($this->cache[$outPath]); 829 } else { 830 $this->convEncIn(); 831 } 832 if (!$this->MLSTsupprt) { 833 if ($path === $this->root) { 834 $res = array( 835 'name' => $this->root, 836 'mime' => 'directory', 837 'dirs' => -1 838 ); 839 if ($this->needOnline && (($this->ARGS['cmd'] === 'open' && $this->ARGS['target'] === $this->encode($this->root)) || $this->isMyReload())) { 840 $check = array( 841 'ts' => true, 842 'dirs' => true, 843 ); 844 $ts = 0; 845 foreach ($this->ftpRawList($path) as $str) { 846 $info = preg_split('/\s+/', $str, 9); 847 if ($info[8] === '.') { 848 $info[8] = 'root'; 849 if ($stat = $this->parseRaw(join(' ', $info), $path)) { 850 unset($stat['name']); 851 $res = array_merge($res, $stat); 852 if ($res['ts']) { 853 $ts = 0; 854 unset($check['ts']); 855 } 856 } 857 } 858 if ($check && ($stat = $this->parseRaw($str, $path))) { 859 if (isset($stat['ts']) && !empty($stat['ts'])) { 860 $ts = max($ts, $stat['ts']); 861 } 862 if (isset($stat['dirs']) && $stat['mime'] === 'directory') { 863 $res['dirs'] = 1; 864 unset($stat['dirs']); 865 } 866 if (!$check) { 867 break; 868 } 869 } 870 } 871 if ($ts) { 872 $res['ts'] = $ts; 873 } 874 $this->cache[$outPath] = $res; 875 } 876 return $res; 877 } 878 879 $pPath = $this->_dirname($path); 880 if ($this->_inPath($pPath, $this->root)) { 881 $outPPpath = $this->convEncOut($pPath); 882 if (!isset($this->dirsCache[$outPPpath])) { 883 $parentSubdirs = null; 884 if (isset($this->sessionCache['subdirs']) && isset($this->sessionCache['subdirs'][$outPPpath])) { 885 $parentSubdirs = $this->sessionCache['subdirs'][$outPPpath]; 886 } 887 $this->cacheDir($outPPpath); 888 if ($parentSubdirs) { 889 $this->sessionCache['subdirs'][$outPPpath] = $parentSubdirs; 890 } 891 } 892 } 893 894 $stat = $this->convEncIn(isset($this->cache[$outPath]) ? $this->cache[$outPath] : array()); 895 if (!$this->mounted) { 896 // dispose incomplete cache made by calling `stat` by 'startPath' option 897 $this->cache = array(); 898 } 899 return $stat; 900 } 901 $raw = ftp_raw($this->connect, 'MLST ' . $path); 902 if (is_array($raw) && count($raw) > 1 && substr(trim($raw[0]), 0, 1) == 2) { 903 $parts = explode(';', trim($raw[1])); 904 array_pop($parts); 905 $parts = array_map('strtolower', $parts); 906 $stat = array(); 907 $mode = ''; 908 foreach ($parts as $part) { 909 910 list($key, $val) = explode('=', $part, 2); 911 912 switch ($key) { 913 case 'type': 914 if (strpos($val, 'dir') !== false) { 915 $stat['mime'] = 'directory'; 916 } else if (strpos($val, 'link') !== false) { 917 $stat['mime'] = 'symlink'; 918 break(2); 919 } else { 920 $stat['mime'] = $this->mimetype($path); 921 } 922 break; 923 924 case 'size': 925 $stat['size'] = $val; 926 break; 927 928 case 'modify': 929 $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)); 930 $stat['ts'] = $ts; 931 break; 932 933 case 'unix.mode': 934 $mode = strval($val); 935 break; 936 937 case 'unix.uid': 938 $stat['owner'] = $val; 939 break; 940 941 case 'unix.gid': 942 $stat['group'] = $val; 943 break; 944 945 case 'perm': 946 $val = strtolower($val); 947 $stat['read'] = (int)preg_match('/e|l|r/', $val); 948 $stat['write'] = (int)preg_match('/w|m|c/', $val); 949 if (!preg_match('/f|d/', $val)) { 950 $stat['locked'] = 1; 951 } 952 break; 953 } 954 } 955 956 if (empty($stat['mime'])) { 957 return array(); 958 } 959 960 // do not use MLST to get stat of symlink 961 if ($stat['mime'] === 'symlink') { 962 $this->MLSTsupprt = false; 963 $res = $this->_stat($path); 964 $this->MLSTsupprt = true; 965 return $res; 966 } 967 968 if ($stat['mime'] === 'directory') { 969 $stat['size'] = 0; 970 } 971 972 if ($mode) { 973 $stat['perm'] = ''; 974 if ($mode[0] === '0') { 975 $mode = substr($mode, 1); 976 } 977 978 $perm = array(); 979 for ($i = 0; $i <= 2; $i++) { 980 $perm[$i] = array(false, false, false); 981 $n = isset($mode[$i]) ? $mode[$i] : 0; 982 983 if ($n - 4 >= 0) { 984 $perm[$i][0] = true; 985 $n = $n - 4; 986 $stat['perm'] .= 'r'; 987 } else { 988 $stat['perm'] .= '-'; 989 } 990 991 if ($n - 2 >= 0) { 992 $perm[$i][1] = true; 993 $n = $n - 2; 994 $stat['perm'] .= 'w'; 995 } else { 996 $stat['perm'] .= '-'; 997 } 998 999 if ($n - 1 == 0) { 1000 $perm[$i][2] = true; 1001 $stat['perm'] .= 'x'; 1002 } else { 1003 $stat['perm'] .= '-'; 1004 } 1005 } 1006 1007 $stat['perm'] = trim($stat['perm']); 1008 // 1009 // if not exists owner in LS ftp ==> isowner = true 1010 // if is defined as option : 'owner' => true isowner = true 1011 // 1012 // if exist owner in LS ftp and 'owner' => False isowner = result of owner(file) == user(logged with ftp) 1013 1014 $owner_computed = isset($stat['owner']) ? ($this->options['owner'] ? true : ($stat['owner'] == $this->options['user'])) : true; 1015 1016 $read = ($owner_computed && $perm[0][0]) || $perm[1][0] || $perm[2][0]; 1017 1018 $stat['read'] = $stat['mime'] == 'directory' ? $read && (($owner_computed && $perm[0][2]) || $perm[1][2] || $perm[2][2]) : $read; 1019 $stat['write'] = ($owner_computed && $perm[0][1]) || $perm[1][1] || $perm[2][1]; 1020 1021 if ($this->options['statOwner']) { 1022 $stat['isowner'] = $owner_computed; 1023 } else { 1024 unset($stat['owner'], $stat['group'], $stat['perm']); 1025 } 1026 } 1027 1028 return $stat; 1029 1030 } 1031 1032 return array(); 1033 } 1034 1035 /** 1036 * Return true if path is dir and has at least one childs directory 1037 * 1038 * @param string $path dir path 1039 * 1040 * @return bool 1041 * @author Dmitry (dio) Levashov 1042 **/ 1043 protected function _subdirs($path) 1044 { 1045 1046 foreach ($this->ftpRawList($path) as $str) { 1047 $info = preg_split('/\s+/', $str, 9); 1048 if (!isset($this->ftpOsUnix)) { 1049 $this->ftpOsUnix = !preg_match('/\d/', substr($info[0], 0, 1)); 1050 } 1051 if (!$this->ftpOsUnix) { 1052 $info = $this->normalizeRawWindows($str); 1053 } 1054 $name = isset($info[8]) ? trim($info[8]) : ''; 1055 if ($name && $name !== '.' && $name !== '..' && substr(strtolower($info[0]), 0, 1) === 'd') { 1056 return true; 1057 } 1058 } 1059 return false; 1060 } 1061 1062 /** 1063 * Return object width and height 1064 * Ususaly used for images, but can be realize for video etc... 1065 * 1066 * @param string $path file path 1067 * @param string $mime file mime type 1068 * 1069 * @return string|false 1070 * @throws ImagickException 1071 * @throws elFinderAbortException 1072 * @author Dmitry (dio) Levashov 1073 */ 1074 protected function _dimensions($path, $mime) 1075 { 1076 $ret = false; 1077 if ($imgsize = $this->getImageSize($path, $mime)) { 1078 $ret = array('dim' => $imgsize['dimensions']); 1079 if (!empty($imgsize['url'])) { 1080 $ret['url'] = $imgsize['url']; 1081 } 1082 } 1083 return $ret; 1084 } 1085 1086 /******************** file/dir content *********************/ 1087 1088 /** 1089 * Return files list in directory. 1090 * 1091 * @param string $path dir path 1092 * 1093 * @return array 1094 * @author Dmitry (dio) Levashov 1095 * @author Cem (DiscoFever) 1096 **/ 1097 protected function _scandir($path) 1098 { 1099 $files = array(); 1100 1101 foreach ($this->ftpRawList($path) as $str) { 1102 if (($stat = $this->parseRaw($str, $path, true))) { 1103 $files[] = $this->_joinPath($path, $stat['name']); 1104 } 1105 } 1106 1107 return $files; 1108 } 1109 1110 /** 1111 * Open file and return file pointer 1112 * 1113 * @param string $path file path 1114 * @param string $mode 1115 * 1116 * @return false|resource 1117 * @throws elFinderAbortException 1118 * @internal param bool $write open file for writing 1119 * @author Dmitry (dio) Levashov 1120 */ 1121 protected function _fopen($path, $mode = 'rb') 1122 { 1123 // try ftp stream wrapper 1124 if ($this->options['mode'] === 'passive' && ini_get('allow_url_fopen')) { 1125 $url = ($this->isFTPS ? 'ftps' : 'ftp') . '://' . $this->options['user'] . ':' . $this->options['pass'] . '@' . $this->options['host'] . ':' . $this->options['port'] . $path; 1126 if (strtolower($mode[0]) === 'w') { 1127 $context = stream_context_create(array('ftp' => array('overwrite' => true))); 1128 $fp = fopen($url, $mode, false, $context); 1129 } else { 1130 $fp = fopen($url, $mode); 1131 } 1132 if ($fp) { 1133 return $fp; 1134 } 1135 } 1136 1137 if ($this->tmp) { 1138 $local = $this->getTempFile($path); 1139 $fp = fopen($local, 'wb'); 1140 $ret = ftp_nb_fget($this->connect, $fp, $path, FTP_BINARY); 1141 while ($ret === FTP_MOREDATA) { 1142 elFinder::extendTimeLimit(); 1143 $ret = ftp_nb_continue($this->connect); 1144 } 1145 if ($ret === FTP_FINISHED) { 1146 fclose($fp); 1147 $fp = fopen($local, $mode); 1148 return $fp; 1149 } 1150 fclose($fp); 1151 is_file($local) && unlink($local); 1152 } 1153 1154 return false; 1155 } 1156 1157 /** 1158 * Close opened file 1159 * 1160 * @param resource $fp file pointer 1161 * @param string $path 1162 * 1163 * @return void 1164 * @author Dmitry (dio) Levashov 1165 */ 1166 protected function _fclose($fp, $path = '') 1167 { 1168 is_resource($fp) && fclose($fp); 1169 if ($path) { 1170 unlink($this->getTempFile($path)); 1171 } 1172 } 1173 1174 /******************** file/dir manipulations *************************/ 1175 1176 /** 1177 * Create dir and return created dir path or false on failed 1178 * 1179 * @param string $path parent dir path 1180 * @param string $name new directory name 1181 * 1182 * @return string|bool 1183 * @author Dmitry (dio) Levashov 1184 **/ 1185 protected function _mkdir($path, $name) 1186 { 1187 $path = $this->_joinPath($path, $name); 1188 if (ftp_mkdir($this->connect, $path) === false) { 1189 return false; 1190 } 1191 1192 $this->options['dirMode'] && ftp_chmod($this->connect, $this->options['dirMode'], $path); 1193 return $path; 1194 } 1195 1196 /** 1197 * Create file and return it's path or false on failed 1198 * 1199 * @param string $path parent dir path 1200 * @param string $name new file name 1201 * 1202 * @return string|bool 1203 * @author Dmitry (dio) Levashov 1204 **/ 1205 protected function _mkfile($path, $name) 1206 { 1207 if ($this->tmp) { 1208 $path = $this->_joinPath($path, $name); 1209 $local = $this->getTempFile(); 1210 $res = touch($local) && ftp_put($this->connect, $path, $local, FTP_ASCII); 1211 unlink($local); 1212 return $res ? $path : false; 1213 } 1214 return false; 1215 } 1216 1217 /** 1218 * Create symlink. FTP driver does not support symlinks. 1219 * 1220 * @param string $target link target 1221 * @param string $path symlink path 1222 * @param string $name 1223 * 1224 * @return bool 1225 * @author Dmitry (dio) Levashov 1226 */ 1227 protected function _symlink($target, $path, $name) 1228 { 1229 return false; 1230 } 1231 1232 /** 1233 * Copy file into another file 1234 * 1235 * @param string $source source file path 1236 * @param string $targetDir target directory path 1237 * @param string $name new file name 1238 * 1239 * @return bool 1240 * @author Dmitry (dio) Levashov 1241 **/ 1242 protected function _copy($source, $targetDir, $name) 1243 { 1244 $res = false; 1245 1246 if ($this->tmp) { 1247 $local = $this->getTempFile(); 1248 $target = $this->_joinPath($targetDir, $name); 1249 1250 if (ftp_get($this->connect, $local, $source, FTP_BINARY) 1251 && ftp_put($this->connect, $target, $local, $this->ftpMode($target))) { 1252 $res = $target; 1253 } 1254 unlink($local); 1255 } 1256 1257 return $res; 1258 } 1259 1260 /** 1261 * Move file into another parent dir. 1262 * Return new file path or false. 1263 * 1264 * @param string $source source file path 1265 * @param $targetDir 1266 * @param string $name file name 1267 * 1268 * @return bool|string 1269 * @internal param string $target target dir path 1270 * @author Dmitry (dio) Levashov 1271 */ 1272 protected function _move($source, $targetDir, $name) 1273 { 1274 $target = $this->_joinPath($targetDir, $name); 1275 return ftp_rename($this->connect, $source, $target) ? $target : false; 1276 } 1277 1278 /** 1279 * Remove file 1280 * 1281 * @param string $path file path 1282 * 1283 * @return bool 1284 * @author Dmitry (dio) Levashov 1285 **/ 1286 protected function _unlink($path) 1287 { 1288 return ftp_delete($this->connect, $path); 1289 } 1290 1291 /** 1292 * Remove dir 1293 * 1294 * @param string $path dir path 1295 * 1296 * @return bool 1297 * @author Dmitry (dio) Levashov 1298 **/ 1299 protected function _rmdir($path) 1300 { 1301 return ftp_rmdir($this->connect, $path); 1302 } 1303 1304 /** 1305 * Create new file and write into it from file pointer. 1306 * Return new file path or false on error. 1307 * 1308 * @param resource $fp file pointer 1309 * @param string $dir target dir path 1310 * @param string $name file name 1311 * @param array $stat file stat (required by some virtual fs) 1312 * 1313 * @return bool|string 1314 * @author Dmitry (dio) Levashov 1315 **/ 1316 protected function _save($fp, $dir, $name, $stat) 1317 { 1318 $path = $this->_joinPath($dir, $name); 1319 return ftp_fput($this->connect, $path, $fp, $this->ftpMode($path)) 1320 ? $path 1321 : false; 1322 } 1323 1324 /** 1325 * Get file contents 1326 * 1327 * @param string $path file path 1328 * 1329 * @return string|false 1330 * @throws elFinderAbortException 1331 * @author Dmitry (dio) Levashov 1332 */ 1333 protected function _getContents($path) 1334 { 1335 $contents = ''; 1336 if (($fp = $this->_fopen($path))) { 1337 while (!feof($fp)) { 1338 $contents .= fread($fp, 8192); 1339 } 1340 $this->_fclose($fp, $path); 1341 return $contents; 1342 } 1343 return false; 1344 } 1345 1346 /** 1347 * Write a string to a file 1348 * 1349 * @param string $path file path 1350 * @param string $content new file content 1351 * 1352 * @return bool 1353 * @author Dmitry (dio) Levashov 1354 **/ 1355 protected function _filePutContents($path, $content) 1356 { 1357 $res = false; 1358 1359 if ($this->tmp) { 1360 $local = $this->getTempFile(); 1361 1362 if (file_put_contents($local, $content, LOCK_EX) !== false 1363 && ($fp = fopen($local, 'rb'))) { 1364 $file = $this->stat($this->convEncOut($path, false)); 1365 if (!empty($file['thash'])) { 1366 $path = $this->decode($file['thash']); 1367 } 1368 clearstatcache(); 1369 $res = ftp_fput($this->connect, $path, $fp, $this->ftpMode($path)); 1370 fclose($fp); 1371 } 1372 file_exists($local) && unlink($local); 1373 } 1374 1375 return $res; 1376 } 1377 1378 /** 1379 * Detect available archivers 1380 * 1381 * @return void 1382 * @throws elFinderAbortException 1383 */ 1384 protected function _checkArchivers() 1385 { 1386 $this->archivers = $this->getArchivers(); 1387 return; 1388 } 1389 1390 /** 1391 * chmod availability 1392 * 1393 * @param string $path 1394 * @param string $mode 1395 * 1396 * @return bool 1397 */ 1398 protected function _chmod($path, $mode) 1399 { 1400 $modeOct = is_string($mode) ? octdec($mode) : octdec(sprintf("%04o", $mode)); 1401 return ftp_chmod($this->connect, $modeOct, $path); 1402 } 1403 1404 /** 1405 * Extract files from archive 1406 * 1407 * @param string $path archive path 1408 * @param array $arc archiver command and arguments (same as in $this->archivers) 1409 * 1410 * @return true 1411 * @throws elFinderAbortException 1412 * @author Dmitry (dio) Levashov, 1413 * @author Alexey Sukhotin 1414 */ 1415 protected function _extract($path, $arc) 1416 { 1417 $dir = $this->tempDir(); 1418 if (!$dir) { 1419 return false; 1420 } 1421 1422 $basename = $this->_basename($path); 1423 $localPath = $dir . DIRECTORY_SEPARATOR . $basename; 1424 1425 if (!ftp_get($this->connect, $localPath, $path, FTP_BINARY)) { 1426 //cleanup 1427 $this->rmdirRecursive($dir); 1428 return false; 1429 } 1430 1431 $this->unpackArchive($localPath, $arc); 1432 1433 $this->archiveSize = 0; 1434 1435 // find symlinks and check extracted items 1436 $checkRes = $this->checkExtractItems($dir); 1437 if ($checkRes['symlinks']) { 1438 $this->rmdirRecursive($dir); 1439 return $this->setError(array_merge($this->error, array(elFinder::ERROR_ARC_SYMLINKS))); 1440 } 1441 $this->archiveSize = $checkRes['totalSize']; 1442 if ($checkRes['rmNames']) { 1443 foreach ($checkRes['rmNames'] as $name) { 1444 $this->addError(elFinder::ERROR_SAVE, $name); 1445 } 1446 } 1447 1448 $filesToProcess = self::listFilesInDirectory($dir, true); 1449 1450 // no files - extract error ? 1451 if (empty($filesToProcess)) { 1452 $this->rmdirRecursive($dir); 1453 return false; 1454 } 1455 1456 // check max files size 1457 if ($this->options['maxArcFilesSize'] > 0 && $this->options['maxArcFilesSize'] < $this->archiveSize) { 1458 $this->rmdirRecursive($dir); 1459 return $this->setError(elFinder::ERROR_ARC_MAXSIZE); 1460 } 1461 1462 $extractTo = $this->extractToNewdir; // 'auto', ture or false 1463 1464 // archive contains one item - extract in archive dir 1465 $name = ''; 1466 $src = $dir . DIRECTORY_SEPARATOR . $filesToProcess[0]; 1467 if (($extractTo === 'auto' || !$extractTo) && count($filesToProcess) === 1 && is_file($src)) { 1468 $name = $filesToProcess[0]; 1469 } else if ($extractTo === 'auto' || $extractTo) { 1470 // for several files - create new directory 1471 // create unique name for directory 1472 $src = $dir; 1473 $splits = elFinder::splitFileExtention(basename($path)); 1474 $name = $splits[0]; 1475 $test = $this->_joinPath(dirname($path), $name); 1476 if ($this->stat($test)) { 1477 $name = $this->uniqueName(dirname($path), $name, '-', false); 1478 } 1479 } 1480 1481 if ($name !== '' && is_file($src)) { 1482 $result = $this->_joinPath(dirname($path), $name); 1483 1484 if (!ftp_put($this->connect, $result, $src, FTP_BINARY)) { 1485 $this->rmdirRecursive($dir); 1486 return false; 1487 } 1488 } else { 1489 $dstDir = $this->_dirname($path); 1490 $result = array(); 1491 if (is_dir($src) && $name) { 1492 $target = $this->_joinPath($dstDir, $name); 1493 $_stat = $this->_stat($target); 1494 if ($_stat) { 1495 if (!$this->options['copyJoin']) { 1496 if ($_stat['mime'] === 'directory') { 1497 $this->delTree($target); 1498 } else { 1499 $this->_unlink($target); 1500 } 1501 $_stat = false; 1502 } else { 1503 $dstDir = $target; 1504 } 1505 } 1506 if (!$_stat && (!$dstDir = $this->_mkdir($dstDir, $name))) { 1507 $this->rmdirRecursive($dir); 1508 return false; 1509 } 1510 $result[] = $dstDir; 1511 } 1512 foreach ($filesToProcess as $name) { 1513 $name = rtrim($name, DIRECTORY_SEPARATOR); 1514 $src = $dir . DIRECTORY_SEPARATOR . $name; 1515 if (is_dir($src)) { 1516 $p = dirname($name); 1517 if ($p === '.') { 1518 $p = ''; 1519 } 1520 $name = basename($name); 1521 $target = $this->_joinPath($this->_joinPath($dstDir, $p), $name); 1522 $_stat = $this->_stat($target); 1523 if ($_stat) { 1524 if (!$this->options['copyJoin']) { 1525 if ($_stat['mime'] === 'directory') { 1526 $this->delTree($target); 1527 } else { 1528 $this->_unlink($target); 1529 } 1530 $_stat = false; 1531 } 1532 } 1533 if (!$_stat && (!$target = $this->_mkdir($this->_joinPath($dstDir, $p), $name))) { 1534 $this->rmdirRecursive($dir); 1535 return false; 1536 } 1537 } else { 1538 $target = $this->_joinPath($dstDir, $name); 1539 if (!ftp_put($this->connect, $target, $src, FTP_BINARY)) { 1540 $this->rmdirRecursive($dir); 1541 return false; 1542 } 1543 } 1544 $result[] = $target; 1545 } 1546 if (!$result) { 1547 $this->rmdirRecursive($dir); 1548 return false; 1549 } 1550 } 1551 1552 is_dir($dir) && $this->rmdirRecursive($dir); 1553 1554 $this->clearcache(); 1555 return $result ? $result : false; 1556 } 1557 1558 /** 1559 * Create archive and return its path 1560 * 1561 * @param string $dir target dir 1562 * @param array $files files names list 1563 * @param string $name archive name 1564 * @param array $arc archiver options 1565 * 1566 * @return string|bool 1567 * @throws elFinderAbortException 1568 * @author Dmitry (dio) Levashov, 1569 * @author Alexey Sukhotin 1570 */ 1571 protected function _archive($dir, $files, $name, $arc) 1572 { 1573 // get current directory 1574 $cwd = getcwd(); 1575 1576 $tmpDir = $this->tempDir(); 1577 if (!$tmpDir) { 1578 return false; 1579 } 1580 1581 //download data 1582 if (!$this->ftp_download_files($dir, $files, $tmpDir)) { 1583 //cleanup 1584 $this->rmdirRecursive($tmpDir); 1585 return false; 1586 } 1587 1588 $remoteArchiveFile = false; 1589 if ($path = $this->makeArchive($tmpDir, $files, $name, $arc)) { 1590 $remoteArchiveFile = $this->_joinPath($dir, $name); 1591 if (!ftp_put($this->connect, $remoteArchiveFile, $path, FTP_BINARY)) { 1592 $remoteArchiveFile = false; 1593 } 1594 } 1595 1596 //cleanup 1597 if (!$this->rmdirRecursive($tmpDir)) { 1598 return false; 1599 } 1600 1601 return $remoteArchiveFile; 1602 } 1603 1604 /** 1605 * Create writable temporary directory and return path to it. 1606 * 1607 * @return string path to the new temporary directory or false in case of error. 1608 */ 1609 private function tempDir() 1610 { 1611 $tempPath = tempnam($this->tmp, 'elFinder'); 1612 if (!$tempPath) { 1613 $this->setError(elFinder::ERROR_CREATING_TEMP_DIR, $this->tmp); 1614 return false; 1615 } 1616 $success = unlink($tempPath); 1617 if (!$success) { 1618 $this->setError(elFinder::ERROR_CREATING_TEMP_DIR, $this->tmp); 1619 return false; 1620 } 1621 $success = mkdir($tempPath, 0700, true); 1622 if (!$success) { 1623 $this->setError(elFinder::ERROR_CREATING_TEMP_DIR, $this->tmp); 1624 return false; 1625 } 1626 return $tempPath; 1627 } 1628 1629 /** 1630 * Gets an array of absolute remote FTP paths of files and 1631 * folders in $remote_directory omitting symbolic links. 1632 * 1633 * @param $remote_directory string remote FTP path to scan for file and folders recursively 1634 * @param $targets array Array of target item. `null` is to get all of items 1635 * 1636 * @return array of elements each of which is an array of two elements: 1637 * <ul> 1638 * <li>$item['path'] - absolute remote FTP path</li> 1639 * <li>$item['type'] - either 'f' for file or 'd' for directory</li> 1640 * </ul> 1641 */ 1642 protected function ftp_scan_dir($remote_directory, $targets = null) 1643 { 1644 $buff = $this->ftpRawList($remote_directory); 1645 $items = array(); 1646 if ($targets && is_array($targets)) { 1647 $targets = array_flip($targets); 1648 } else { 1649 $targets = false; 1650 } 1651 foreach ($buff as $str) { 1652 $info = preg_split("/\s+/", $str, 9); 1653 if (!isset($this->ftpOsUnix)) { 1654 $this->ftpOsUnix = !preg_match('/\d/', substr($info[0], 0, 1)); 1655 } 1656 if (!$this->ftpOsUnix) { 1657 $info = $this->normalizeRawWindows($str); 1658 } 1659 $type = substr($info[0], 0, 1); 1660 $name = trim($info[8]); 1661 if ($name !== '.' && $name !== '..' && (!$targets || isset($targets[$name]))) { 1662 switch ($type) { 1663 case 'l' : //omit symbolic links 1664 case 'd' : 1665 $remote_file_path = $this->_joinPath($remote_directory, $name); 1666 $item = array(); 1667 $item['path'] = $remote_file_path; 1668 $item['type'] = 'd'; // normal file 1669 $items[] = $item; 1670 $items = array_merge($items, $this->ftp_scan_dir($remote_file_path)); 1671 break; 1672 default: 1673 $remote_file_path = $this->_joinPath($remote_directory, $name); 1674 $item = array(); 1675 $item['path'] = $remote_file_path; 1676 $item['type'] = 'f'; // normal file 1677 $items[] = $item; 1678 } 1679 } 1680 } 1681 return $items; 1682 } 1683 1684 /** 1685 * Downloads specified files from remote directory 1686 * if there is a directory among files it is downloaded recursively (omitting symbolic links). 1687 * 1688 * @param $remote_directory string remote FTP path to a source directory to download from. 1689 * @param array $files list of files to download from remote directory. 1690 * @param $dest_local_directory string destination folder to store downloaded files. 1691 * 1692 * @return bool true on success and false on failure. 1693 */ 1694 private function ftp_download_files($remote_directory, array $files, $dest_local_directory) 1695 { 1696 $contents = $this->ftp_scan_dir($remote_directory, $files); 1697 if (!isset($contents)) { 1698 $this->setError(elFinder::ERROR_FTP_DOWNLOAD_FILE, $remote_directory); 1699 return false; 1700 } 1701 $remoteDirLen = strlen($remote_directory); 1702 foreach ($contents as $item) { 1703 $relative_path = substr($item['path'], $remoteDirLen); 1704 $local_path = $dest_local_directory . DIRECTORY_SEPARATOR . $relative_path; 1705 switch ($item['type']) { 1706 case 'd': 1707 $success = mkdir($local_path); 1708 break; 1709 case 'f': 1710 $success = ftp_get($this->connect, $local_path, $item['path'], FTP_BINARY); 1711 break; 1712 default: 1713 $success = true; 1714 } 1715 if (!$success) { 1716 $this->setError(elFinder::ERROR_FTP_DOWNLOAD_FILE, $remote_directory); 1717 return false; 1718 } 1719 } 1720 return true; 1721 } 1722 1723 /** 1724 * Delete local directory recursively. 1725 * 1726 * @param $dirPath string to directory to be erased. 1727 * 1728 * @return bool true on success and false on failure. 1729 * @throws Exception 1730 */ 1731 private function deleteDir($dirPath) 1732 { 1733 if (!is_dir($dirPath)) { 1734 $success = unlink($dirPath); 1735 } else { 1736 $success = true; 1737 foreach (array_reverse(elFinderVolumeFTP::listFilesInDirectory($dirPath, false)) as $path) { 1738 $path = $dirPath . DIRECTORY_SEPARATOR . $path; 1739 if (is_link($path)) { 1740 unlink($path); 1741 } else if (is_dir($path)) { 1742 $success = rmdir($path); 1743 } else { 1744 $success = unlink($path); 1745 } 1746 if (!$success) { 1747 break; 1748 } 1749 } 1750 if ($success) { 1751 $success = rmdir($dirPath); 1752 } 1753 } 1754 if (!$success) { 1755 $this->setError(elFinder::ERROR_RM, $dirPath); 1756 return false; 1757 } 1758 return $success; 1759 } 1760 1761 /** 1762 * Returns array of strings containing all files and folders in the specified local directory. 1763 * 1764 * @param $dir 1765 * @param $omitSymlinks 1766 * @param string $prefix 1767 * 1768 * @return array array of files and folders names relative to the $path 1769 * or an empty array if the directory $path is empty, 1770 * <br /> 1771 * false if $path is not a directory or does not exist. 1772 * @throws Exception 1773 * @internal param string $path path to directory to scan. 1774 */ 1775 private static function listFilesInDirectory($dir, $omitSymlinks, $prefix = '') 1776 { 1777 if (!is_dir($dir)) { 1778 return false; 1779 } 1780 $excludes = array(".", ".."); 1781 $result = array(); 1782 $files = self::localScandir($dir); 1783 if (!$files) { 1784 return array(); 1785 } 1786 foreach ($files as $file) { 1787 if (!in_array($file, $excludes)) { 1788 $path = $dir . DIRECTORY_SEPARATOR . $file; 1789 if (is_link($path)) { 1790 if ($omitSymlinks) { 1791 continue; 1792 } else { 1793 $result[] = $prefix . $file; 1794 } 1795 } else if (is_dir($path)) { 1796 $result[] = $prefix . $file . DIRECTORY_SEPARATOR; 1797 $subs = elFinderVolumeFTP::listFilesInDirectory($path, $omitSymlinks, $prefix . $file . DIRECTORY_SEPARATOR); 1798 if ($subs) { 1799 $result = array_merge($result, $subs); 1800 } 1801 1802 } else { 1803 $result[] = $prefix . $file; 1804 } 1805 } 1806 } 1807 return $result; 1808 } 1809 1810} // END class 1811