PageRenderTime 61ms CodeModel.GetById 31ms RepoModel.GetById 0ms app.codeStats 0ms

/modules/admin/assets/connectors/elFinderVolumeLocalFileSystem.class.php

https://bitbucket.org/xamedow/cms-2.0
PHP | 762 lines | 353 code | 99 blank | 310 comment | 97 complexity | 3d2b2b70ed4d0ac25bccecbf22dd7789 MD5 | raw file
  1. <?php
  2. /**
  3. * elFinder driver for local filesystem.
  4. *
  5. * @author Dmitry (dio) Levashov
  6. * @author Troex Nevelin
  7. **/
  8. class elFinderVolumeLocalFileSystem extends elFinderVolumeDriver {
  9. /**
  10. * Driver id
  11. * Must be started from letter and contains [a-z0-9]
  12. * Used as part of volume id
  13. *
  14. * @var string
  15. **/
  16. protected $driverId = 'l';
  17. /**
  18. * Required to count total archive files size
  19. *
  20. * @var int
  21. **/
  22. protected $archiveSize = 0;
  23. /**
  24. * Canonicalized absolute pathname of $root
  25. *
  26. * @var strung
  27. */
  28. protected $aroot;
  29. /**
  30. * Constructor
  31. * Extend options with required fields
  32. *
  33. * @return void
  34. * @author Dmitry (dio) Levashov
  35. **/
  36. public function __construct() {
  37. $this->options['alias'] = ''; // alias to replace root dir name
  38. $this->options['dirMode'] = 0755; // new dirs mode
  39. $this->options['fileMode'] = 0644; // new files mode
  40. $this->options['quarantine'] = '.quarantine'; // quarantine folder name - required to check archive (must be hidden)
  41. $this->options['maxArcFilesSize'] = 0; // max allowed archive files size (0 - no limit)
  42. }
  43. /*********************************************************************/
  44. /* INIT AND CONFIGURE */
  45. /*********************************************************************/
  46. /**
  47. * Configure after successfull mount.
  48. *
  49. * @return void
  50. * @author Dmitry (dio) Levashov
  51. **/
  52. protected function configure() {
  53. $this->aroot = realpath($this->root);
  54. $root = $this->stat($this->root);
  55. if ($this->options['quarantine']) {
  56. $this->attributes[] = array(
  57. 'pattern' => '~^'.preg_quote(DIRECTORY_SEPARATOR.$this->options['quarantine']).'$~',
  58. 'read' => false,
  59. 'write' => false,
  60. 'locked' => true,
  61. 'hidden' => true
  62. );
  63. }
  64. // chek thumbnails path
  65. if ($this->options['tmbPath']) {
  66. $this->options['tmbPath'] = strpos($this->options['tmbPath'], DIRECTORY_SEPARATOR) === false
  67. // tmb path set as dirname under root dir
  68. ? $this->root.DIRECTORY_SEPARATOR.$this->options['tmbPath']
  69. // tmb path as full path
  70. : $this->_normpath($this->options['tmbPath']);
  71. }
  72. parent::configure();
  73. // if no thumbnails url - try detect it
  74. if ($root['read'] && !$this->tmbURL && $this->URL) {
  75. if (strpos($this->tmbPath, $this->root) === 0) {
  76. $this->tmbURL = $this->URL.str_replace(DIRECTORY_SEPARATOR, '/', substr($this->tmbPath, strlen($this->root)+1));
  77. if (preg_match("|[^/?&=]$|", $this->tmbURL)) {
  78. $this->tmbURL .= '/';
  79. }
  80. }
  81. }
  82. // check quarantine dir
  83. if (!empty($this->options['quarantine'])) {
  84. $this->quarantine = $this->root.DIRECTORY_SEPARATOR.$this->options['quarantine'];
  85. if ((!is_dir($this->quarantine) && !$this->_mkdir($this->root, $this->options['quarantine'])) || !is_writable($this->quarantine)) {
  86. $this->archivers['extract'] = array();
  87. $this->disabled[] = 'extract';
  88. }
  89. } else {
  90. $this->archivers['extract'] = array();
  91. $this->disabled[] = 'extract';
  92. }
  93. }
  94. /*********************************************************************/
  95. /* FS API */
  96. /*********************************************************************/
  97. /*********************** paths/urls *************************/
  98. /**
  99. * Return parent directory path
  100. *
  101. * @param string $path file path
  102. * @return string
  103. * @author Dmitry (dio) Levashov
  104. **/
  105. protected function _dirname($path) {
  106. return dirname($path);
  107. }
  108. /**
  109. * Return file name
  110. *
  111. * @param string $path file path
  112. * @return string
  113. * @author Dmitry (dio) Levashov
  114. **/
  115. protected function _basename($path) {
  116. return basename($path);
  117. }
  118. /**
  119. * Join dir name and file name and retur full path
  120. *
  121. * @param string $dir
  122. * @param string $name
  123. * @return string
  124. * @author Dmitry (dio) Levashov
  125. **/
  126. protected function _joinPath($dir, $name) {
  127. return $dir.DIRECTORY_SEPARATOR.$name;
  128. }
  129. /**
  130. * Return normalized path, this works the same as os.path.normpath() in Python
  131. *
  132. * @param string $path path
  133. * @return string
  134. * @author Troex Nevelin
  135. **/
  136. protected function _normpath($path) {
  137. if (empty($path)) {
  138. return '.';
  139. }
  140. if (strpos($path, '/') === 0) {
  141. $initial_slashes = true;
  142. } else {
  143. $initial_slashes = false;
  144. }
  145. if (($initial_slashes)
  146. && (strpos($path, '//') === 0)
  147. && (strpos($path, '///') === false)) {
  148. $initial_slashes = 2;
  149. }
  150. $initial_slashes = (int) $initial_slashes;
  151. $comps = explode('/', $path);
  152. $new_comps = array();
  153. foreach ($comps as $comp) {
  154. if (in_array($comp, array('', '.'))) {
  155. continue;
  156. }
  157. if (($comp != '..')
  158. || (!$initial_slashes && !$new_comps)
  159. || ($new_comps && (end($new_comps) == '..'))) {
  160. array_push($new_comps, $comp);
  161. } elseif ($new_comps) {
  162. array_pop($new_comps);
  163. }
  164. }
  165. $comps = $new_comps;
  166. $path = implode('/', $comps);
  167. if ($initial_slashes) {
  168. $path = str_repeat('/', $initial_slashes) . $path;
  169. }
  170. return $path ? $path : '.';
  171. }
  172. /**
  173. * Return file path related to root dir
  174. *
  175. * @param string $path file path
  176. * @return string
  177. * @author Dmitry (dio) Levashov
  178. **/
  179. protected function _relpath($path) {
  180. return $path == $this->root ? '' : substr($path, strlen($this->root)+1);
  181. }
  182. /**
  183. * Convert path related to root dir into real path
  184. *
  185. * @param string $path file path
  186. * @return string
  187. * @author Dmitry (dio) Levashov
  188. **/
  189. protected function _abspath($path) {
  190. return $path == DIRECTORY_SEPARATOR ? $this->root : $this->root.DIRECTORY_SEPARATOR.$path;
  191. }
  192. /**
  193. * Return fake path started from root dir
  194. *
  195. * @param string $path file path
  196. * @return string
  197. * @author Dmitry (dio) Levashov
  198. **/
  199. protected function _path($path) {
  200. return $this->rootName.($path == $this->root ? '' : $this->separator.$this->_relpath($path));
  201. }
  202. /**
  203. * Return true if $path is children of $parent
  204. *
  205. * @param string $path path to check
  206. * @param string $parent parent path
  207. * @return bool
  208. * @author Dmitry (dio) Levashov
  209. **/
  210. protected function _inpath($path, $parent) {
  211. $real_path = realpath($path);
  212. $real_parent = realpath($parent);
  213. if ($real_path && $real_parent) {
  214. return $real_path === $real_parent || strpos($real_path, $real_parent.DIRECTORY_SEPARATOR) === 0;
  215. }
  216. return false;
  217. }
  218. /***************** file stat ********************/
  219. /**
  220. * Return stat for given path.
  221. * Stat contains following fields:
  222. * - (int) size file size in b. required
  223. * - (int) ts file modification time in unix time. required
  224. * - (string) mime mimetype. required for folders, others - optionally
  225. * - (bool) read read permissions. required
  226. * - (bool) write write permissions. required
  227. * - (bool) locked is object locked. optionally
  228. * - (bool) hidden is object hidden. optionally
  229. * - (string) alias for symlinks - link target path relative to root path. optionally
  230. * - (string) target for symlinks - link target path. optionally
  231. *
  232. * If file does not exists - returns empty array or false.
  233. *
  234. * @param string $path file path
  235. * @return array|false
  236. * @author Dmitry (dio) Levashov
  237. **/
  238. protected function _stat($path) {
  239. $stat = array();
  240. if (!file_exists($path)) {
  241. return $stat;
  242. }
  243. //Verifies the given path is the root or is inside the root. Prevents directory traveral.
  244. if (!$this->aroot) {
  245. // for Inheritance class ( not calling parent::configure() )
  246. $this->aroot = realpath($this->root);
  247. }
  248. if (!$this->_inpath($path, $this->aroot)) {
  249. return $stat;
  250. }
  251. if ($path != $this->root && is_link($path)) {
  252. if (($target = $this->readlink($path)) == false
  253. || $target == $path) {
  254. $stat['mime'] = 'symlink-broken';
  255. $stat['read'] = false;
  256. $stat['write'] = false;
  257. $stat['size'] = 0;
  258. return $stat;
  259. }
  260. $stat['alias'] = $this->_path($target);
  261. $stat['target'] = $target;
  262. $path = $target;
  263. $lstat = lstat($path);
  264. $size = $lstat['size'];
  265. } else {
  266. $size = @filesize($path);
  267. }
  268. $dir = is_dir($path);
  269. $stat['mime'] = $dir ? 'directory' : $this->mimetype($path);
  270. $stat['ts'] = filemtime($path);
  271. $stat['read'] = is_readable($path);
  272. $stat['write'] = is_writable($path);
  273. if ($stat['read']) {
  274. $stat['size'] = $dir ? 0 : $size;
  275. }
  276. return $stat;
  277. }
  278. /**
  279. * Return true if path is dir and has at least one childs directory
  280. *
  281. * @param string $path dir path
  282. * @return bool
  283. * @author Dmitry (dio) Levashov
  284. **/
  285. protected function _subdirs($path) {
  286. if (($dir = dir($path))) {
  287. $dir = dir($path);
  288. while (($entry = $dir->read()) !== false) {
  289. $p = $dir->path.DIRECTORY_SEPARATOR.$entry;
  290. if ($entry != '.' && $entry != '..' && is_dir($p) && !$this->attr($p, 'hidden')) {
  291. $dir->close();
  292. return true;
  293. }
  294. }
  295. $dir->close();
  296. }
  297. return false;
  298. }
  299. /**
  300. * Return object width and height
  301. * Usualy used for images, but can be realize for video etc...
  302. *
  303. * @param string $path file path
  304. * @param string $mime file mime type
  305. * @return string
  306. * @author Dmitry (dio) Levashov
  307. **/
  308. protected function _dimensions($path, $mime) {
  309. clearstatcache();
  310. return strpos($mime, 'image') === 0 && ($s = @getimagesize($path)) !== false
  311. ? $s[0].'x'.$s[1]
  312. : false;
  313. }
  314. /******************** file/dir content *********************/
  315. /**
  316. * Return symlink target file
  317. *
  318. * @param string $path link path
  319. * @return string
  320. * @author Dmitry (dio) Levashov
  321. **/
  322. protected function readlink($path) {
  323. if (!($target = @readlink($path))) {
  324. return false;
  325. }
  326. if (substr($target, 0, 1) != DIRECTORY_SEPARATOR) {
  327. $target = dirname($path).DIRECTORY_SEPARATOR.$target;
  328. }
  329. if ($this->_inpath($target, $this->aroot)) {
  330. $atarget = realpath($target);
  331. return $this->_normpath($this->root.DIRECTORY_SEPARATOR.substr($atarget, strlen($this->aroot)+1));
  332. }
  333. return false;
  334. }
  335. /**
  336. * Return files list in directory.
  337. *
  338. * @param string $path dir path
  339. * @return array
  340. * @author Dmitry (dio) Levashov
  341. **/
  342. protected function _scandir($path) {
  343. $files = array();
  344. foreach (scandir($path) as $name) {
  345. if ($name != '.' && $name != '..') {
  346. $files[] = $path.DIRECTORY_SEPARATOR.$name;
  347. }
  348. }
  349. return $files;
  350. }
  351. /**
  352. * Open file and return file pointer
  353. *
  354. * @param string $path file path
  355. * @param bool $write open file for writing
  356. * @return resource|false
  357. * @author Dmitry (dio) Levashov
  358. **/
  359. protected function _fopen($path, $mode='rb') {
  360. return @fopen($path, 'r');
  361. }
  362. /**
  363. * Close opened file
  364. *
  365. * @param resource $fp file pointer
  366. * @return bool
  367. * @author Dmitry (dio) Levashov
  368. **/
  369. protected function _fclose($fp, $path='') {
  370. return @fclose($fp);
  371. }
  372. /******************** file/dir manipulations *************************/
  373. /**
  374. * Create dir and return created dir path or false on failed
  375. *
  376. * @param string $path parent dir path
  377. * @param string $name new directory name
  378. * @return string|bool
  379. * @author Dmitry (dio) Levashov
  380. **/
  381. protected function _mkdir($path, $name) {
  382. $path = $path.DIRECTORY_SEPARATOR.$name;
  383. if (@mkdir($path)) {
  384. @chmod($path, $this->options['dirMode']);
  385. return $path;
  386. }
  387. return false;
  388. }
  389. /**
  390. * Create file and return it's path or false on failed
  391. *
  392. * @param string $path parent dir path
  393. * @param string $name new file name
  394. * @return string|bool
  395. * @author Dmitry (dio) Levashov
  396. **/
  397. protected function _mkfile($path, $name) {
  398. $path = $path.DIRECTORY_SEPARATOR.$name;
  399. if (($fp = @fopen($path, 'w'))) {
  400. @fclose($fp);
  401. @chmod($path, $this->options['fileMode']);
  402. return $path;
  403. }
  404. return false;
  405. }
  406. /**
  407. * Create symlink
  408. *
  409. * @param string $source file to link to
  410. * @param string $targetDir folder to create link in
  411. * @param string $name symlink name
  412. * @return bool
  413. * @author Dmitry (dio) Levashov
  414. **/
  415. protected function _symlink($source, $targetDir, $name) {
  416. return @symlink($source, $targetDir.DIRECTORY_SEPARATOR.$name);
  417. }
  418. /**
  419. * Copy file into another file
  420. *
  421. * @param string $source source file path
  422. * @param string $targetDir target directory path
  423. * @param string $name new file name
  424. * @return bool
  425. * @author Dmitry (dio) Levashov
  426. **/
  427. protected function _copy($source, $targetDir, $name) {
  428. return copy($source, $targetDir.DIRECTORY_SEPARATOR.$name);
  429. }
  430. /**
  431. * Move file into another parent dir.
  432. * Return new file path or false.
  433. *
  434. * @param string $source source file path
  435. * @param string $target target dir path
  436. * @param string $name file name
  437. * @return string|bool
  438. * @author Dmitry (dio) Levashov
  439. **/
  440. protected function _move($source, $targetDir, $name) {
  441. $target = $targetDir.DIRECTORY_SEPARATOR.$name;
  442. return @rename($source, $target) ? $target : false;
  443. }
  444. /**
  445. * Remove file
  446. *
  447. * @param string $path file path
  448. * @return bool
  449. * @author Dmitry (dio) Levashov
  450. **/
  451. protected function _unlink($path) {
  452. return @unlink($path);
  453. }
  454. /**
  455. * Remove dir
  456. *
  457. * @param string $path dir path
  458. * @return bool
  459. * @author Dmitry (dio) Levashov
  460. **/
  461. protected function _rmdir($path) {
  462. return @rmdir($path);
  463. }
  464. /**
  465. * Create new file and write into it from file pointer.
  466. * Return new file path or false on error.
  467. *
  468. * @param resource $fp file pointer
  469. * @param string $dir target dir path
  470. * @param string $name file name
  471. * @param array $stat file stat (required by some virtual fs)
  472. * @return bool|string
  473. * @author Dmitry (dio) Levashov
  474. **/
  475. protected function _save($fp, $dir, $name, $stat) {
  476. $path = $dir.DIRECTORY_SEPARATOR.$name;
  477. if (!($target = @fopen($path, 'wb'))) {
  478. return false;
  479. }
  480. while (!feof($fp)) {
  481. fwrite($target, fread($fp, 8192));
  482. }
  483. fclose($target);
  484. @chmod($path, $this->options['fileMode']);
  485. clearstatcache();
  486. return $path;
  487. }
  488. /**
  489. * Get file contents
  490. *
  491. * @param string $path file path
  492. * @return string|false
  493. * @author Dmitry (dio) Levashov
  494. **/
  495. protected function _getContents($path) {
  496. return file_get_contents($path);
  497. }
  498. /**
  499. * Write a string to a file
  500. *
  501. * @param string $path file path
  502. * @param string $content new file content
  503. * @return bool
  504. * @author Dmitry (dio) Levashov
  505. **/
  506. protected function _filePutContents($path, $content) {
  507. if (@file_put_contents($path, $content, LOCK_EX) !== false) {
  508. clearstatcache();
  509. return true;
  510. }
  511. return false;
  512. }
  513. /**
  514. * Detect available archivers
  515. *
  516. * @return void
  517. **/
  518. protected function _checkArchivers() {
  519. $this->archivers = $this->getArchivers();
  520. return;
  521. }
  522. /**
  523. * Unpack archive
  524. *
  525. * @param string $path archive path
  526. * @param array $arc archiver command and arguments (same as in $this->archivers)
  527. * @return void
  528. * @author Dmitry (dio) Levashov
  529. * @author Alexey Sukhotin
  530. **/
  531. protected function _unpack($path, $arc) {
  532. $cwd = getcwd();
  533. $dir = $this->_dirname($path);
  534. chdir($dir);
  535. $cmd = $arc['cmd'].' '.$arc['argc'].' '.escapeshellarg($this->_basename($path));
  536. $this->procExec($cmd, $o, $c);
  537. chdir($cwd);
  538. }
  539. /**
  540. * Recursive symlinks search
  541. *
  542. * @param string $path file/dir path
  543. * @return bool
  544. * @author Dmitry (dio) Levashov
  545. **/
  546. protected function _findSymlinks($path) {
  547. if (is_link($path)) {
  548. return true;
  549. }
  550. if (is_dir($path)) {
  551. foreach (scandir($path) as $name) {
  552. if ($name != '.' && $name != '..') {
  553. $p = $path.DIRECTORY_SEPARATOR.$name;
  554. if (is_link($p) || !$this->nameAccepted($name)) {
  555. return true;
  556. }
  557. if (is_dir($p) && $this->_findSymlinks($p)) {
  558. return true;
  559. } elseif (is_file($p)) {
  560. $this->archiveSize += filesize($p);
  561. }
  562. }
  563. }
  564. } else {
  565. $this->archiveSize += filesize($path);
  566. }
  567. return false;
  568. }
  569. /**
  570. * Extract files from archive
  571. *
  572. * @param string $path archive path
  573. * @param array $arc archiver command and arguments (same as in $this->archivers)
  574. * @return true
  575. * @author Dmitry (dio) Levashov,
  576. * @author Alexey Sukhotin
  577. **/
  578. protected function _extract($path, $arc) {
  579. if ($this->quarantine) {
  580. $dir = $this->quarantine.DIRECTORY_SEPARATOR.str_replace(' ', '_', microtime()).basename($path);
  581. $archive = $dir.DIRECTORY_SEPARATOR.basename($path);
  582. if (!@mkdir($dir)) {
  583. return false;
  584. }
  585. chmod($dir, 0777);
  586. // copy in quarantine
  587. if (!copy($path, $archive)) {
  588. return false;
  589. }
  590. // extract in quarantine
  591. $this->_unpack($archive, $arc);
  592. unlink($archive);
  593. // get files list
  594. $ls = array();
  595. foreach (scandir($dir) as $i => $name) {
  596. if ($name != '.' && $name != '..') {
  597. $ls[] = $name;
  598. }
  599. }
  600. // no files - extract error ?
  601. if (empty($ls)) {
  602. return false;
  603. }
  604. $this->archiveSize = 0;
  605. // find symlinks
  606. $symlinks = $this->_findSymlinks($dir);
  607. // remove arc copy
  608. $this->remove($dir);
  609. if ($symlinks) {
  610. return $this->setError(elFinder::ERROR_ARC_SYMLINKS);
  611. }
  612. // check max files size
  613. if ($this->options['maxArcFilesSize'] > 0 && $this->options['maxArcFilesSize'] < $this->archiveSize) {
  614. return $this->setError(elFinder::ERROR_ARC_MAXSIZE);
  615. }
  616. // archive contains one item - extract in archive dir
  617. if (count($ls) == 1) {
  618. $this->_unpack($path, $arc);
  619. $result = dirname($path).DIRECTORY_SEPARATOR.$ls[0];
  620. } else {
  621. // for several files - create new directory
  622. // create unique name for directory
  623. $name = basename($path);
  624. if (preg_match('/\.((tar\.(gz|bz|bz2|z|lzo))|cpio\.gz|ps\.gz|xcf\.(gz|bz2)|[a-z0-9]{1,4})$/i', $name, $m)) {
  625. $name = substr($name, 0, strlen($name)-strlen($m[0]));
  626. }
  627. $test = dirname($path).DIRECTORY_SEPARATOR.$name;
  628. if (file_exists($test) || is_link($test)) {
  629. $name = $this->uniqueName(dirname($path), $name, '-', false);
  630. }
  631. $result = dirname($path).DIRECTORY_SEPARATOR.$name;
  632. $archive = $result.DIRECTORY_SEPARATOR.basename($path);
  633. if (!$this->_mkdir(dirname($path), $name) || !copy($path, $archive)) {
  634. return false;
  635. }
  636. $this->_unpack($archive, $arc);
  637. @unlink($archive);
  638. }
  639. return file_exists($result) ? $result : false;
  640. }
  641. }
  642. /**
  643. * Create archive and return its path
  644. *
  645. * @param string $dir target dir
  646. * @param array $files files names list
  647. * @param string $name archive name
  648. * @param array $arc archiver options
  649. * @return string|bool
  650. * @author Dmitry (dio) Levashov,
  651. * @author Alexey Sukhotin
  652. **/
  653. protected function _archive($dir, $files, $name, $arc) {
  654. $cwd = getcwd();
  655. chdir($dir);
  656. $files = array_map('escapeshellarg', $files);
  657. $cmd = $arc['cmd'].' '.$arc['argc'].' '.escapeshellarg($name).' '.implode(' ', $files);
  658. $this->procExec($cmd, $o, $c);
  659. chdir($cwd);
  660. $path = $dir.DIRECTORY_SEPARATOR.$name;
  661. return file_exists($path) ? $path : false;
  662. }
  663. } // END class