PageRenderTime 47ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/src/wp-admin/includes/class-wp-filesystem-ftpext.php

https://gitlab.com/morganestes/wordpress-develop
PHP | 594 lines | 368 code | 73 blank | 153 comment | 91 complexity | 630f524e12cb550debb73ade07687285 MD5 | raw file
  1. <?php
  2. /**
  3. * WordPress FTP Filesystem.
  4. *
  5. * @package WordPress
  6. * @subpackage Filesystem
  7. */
  8. /**
  9. * WordPress Filesystem Class for implementing FTP.
  10. *
  11. * @since 2.5.0
  12. *
  13. * @see WP_Filesystem_Base
  14. */
  15. class WP_Filesystem_FTPext extends WP_Filesystem_Base {
  16. public $link;
  17. /**
  18. * @param array $opt
  19. */
  20. public function __construct( $opt = '' ) {
  21. $this->method = 'ftpext';
  22. $this->errors = new WP_Error();
  23. // Check if possible to use ftp functions.
  24. if ( ! extension_loaded( 'ftp' ) ) {
  25. $this->errors->add( 'no_ftp_ext', __( 'The ftp PHP extension is not available' ) );
  26. return;
  27. }
  28. // This Class uses the timeout on a per-connection basis, Others use it on a per-action basis.
  29. if ( ! defined( 'FS_TIMEOUT' ) ) {
  30. define( 'FS_TIMEOUT', 240 );
  31. }
  32. if ( empty( $opt['port'] ) ) {
  33. $this->options['port'] = 21;
  34. } else {
  35. $this->options['port'] = $opt['port'];
  36. }
  37. if ( empty( $opt['hostname'] ) ) {
  38. $this->errors->add( 'empty_hostname', __( 'FTP hostname is required' ) );
  39. } else {
  40. $this->options['hostname'] = $opt['hostname'];
  41. }
  42. // Check if the options provided are OK.
  43. if ( empty( $opt['username'] ) ) {
  44. $this->errors->add( 'empty_username', __( 'FTP username is required' ) );
  45. } else {
  46. $this->options['username'] = $opt['username'];
  47. }
  48. if ( empty( $opt['password'] ) ) {
  49. $this->errors->add( 'empty_password', __( 'FTP password is required' ) );
  50. } else {
  51. $this->options['password'] = $opt['password'];
  52. }
  53. $this->options['ssl'] = false;
  54. if ( isset( $opt['connection_type'] ) && 'ftps' == $opt['connection_type'] ) {
  55. $this->options['ssl'] = true;
  56. }
  57. }
  58. /**
  59. * @return bool
  60. */
  61. public function connect() {
  62. if ( isset( $this->options['ssl'] ) && $this->options['ssl'] && function_exists( 'ftp_ssl_connect' ) ) {
  63. $this->link = @ftp_ssl_connect( $this->options['hostname'], $this->options['port'], FS_CONNECT_TIMEOUT );
  64. } else {
  65. $this->link = @ftp_connect( $this->options['hostname'], $this->options['port'], FS_CONNECT_TIMEOUT );
  66. }
  67. if ( ! $this->link ) {
  68. $this->errors->add(
  69. 'connect',
  70. /* translators: %s: hostname:port */
  71. sprintf(
  72. __( 'Failed to connect to FTP Server %s' ),
  73. $this->options['hostname'] . ':' . $this->options['port']
  74. )
  75. );
  76. return false;
  77. }
  78. if ( ! @ftp_login( $this->link, $this->options['username'], $this->options['password'] ) ) {
  79. $this->errors->add(
  80. 'auth',
  81. /* translators: %s: username */
  82. sprintf(
  83. __( 'Username/Password incorrect for %s' ),
  84. $this->options['username']
  85. )
  86. );
  87. return false;
  88. }
  89. // Set the Connection to use Passive FTP
  90. @ftp_pasv( $this->link, true );
  91. if ( @ftp_get_option( $this->link, FTP_TIMEOUT_SEC ) < FS_TIMEOUT ) {
  92. @ftp_set_option( $this->link, FTP_TIMEOUT_SEC, FS_TIMEOUT );
  93. }
  94. return true;
  95. }
  96. /**
  97. * Retrieves the file contents.
  98. *
  99. * @since 2.5.0
  100. *
  101. * @param string $file Filename.
  102. * @return string|false File contents on success, false if no temp file could be opened,
  103. * or if the file couldn't be retrieved.
  104. */
  105. public function get_contents( $file ) {
  106. $tempfile = wp_tempnam( $file );
  107. $temp = fopen( $tempfile, 'w+' );
  108. if ( ! $temp ) {
  109. unlink( $tempfile );
  110. return false;
  111. }
  112. if ( ! @ftp_fget( $this->link, $temp, $file, FTP_BINARY ) ) {
  113. fclose( $temp );
  114. unlink( $tempfile );
  115. return false;
  116. }
  117. fseek( $temp, 0 ); // Skip back to the start of the file being written to
  118. $contents = '';
  119. while ( ! feof( $temp ) ) {
  120. $contents .= fread( $temp, 8192 );
  121. }
  122. fclose( $temp );
  123. unlink( $tempfile );
  124. return $contents;
  125. }
  126. /**
  127. * @param string $file
  128. * @return array
  129. */
  130. public function get_contents_array( $file ) {
  131. return explode( "\n", $this->get_contents( $file ) );
  132. }
  133. /**
  134. * @param string $file
  135. * @param string $contents
  136. * @param bool|int $mode
  137. * @return bool
  138. */
  139. public function put_contents( $file, $contents, $mode = false ) {
  140. $tempfile = wp_tempnam( $file );
  141. $temp = fopen( $tempfile, 'wb+' );
  142. if ( ! $temp ) {
  143. unlink( $tempfile );
  144. return false;
  145. }
  146. mbstring_binary_safe_encoding();
  147. $data_length = strlen( $contents );
  148. $bytes_written = fwrite( $temp, $contents );
  149. reset_mbstring_encoding();
  150. if ( $data_length !== $bytes_written ) {
  151. fclose( $temp );
  152. unlink( $tempfile );
  153. return false;
  154. }
  155. fseek( $temp, 0 ); // Skip back to the start of the file being written to
  156. $ret = @ftp_fput( $this->link, $file, $temp, FTP_BINARY );
  157. fclose( $temp );
  158. unlink( $tempfile );
  159. $this->chmod( $file, $mode );
  160. return $ret;
  161. }
  162. /**
  163. * @return string
  164. */
  165. public function cwd() {
  166. $cwd = @ftp_pwd( $this->link );
  167. if ( $cwd ) {
  168. $cwd = trailingslashit( $cwd );
  169. }
  170. return $cwd;
  171. }
  172. /**
  173. * @param string $dir
  174. * @return bool
  175. */
  176. public function chdir( $dir ) {
  177. return @ftp_chdir( $this->link, $dir );
  178. }
  179. /**
  180. * @param string $file
  181. * @param int $mode
  182. * @param bool $recursive
  183. * @return bool
  184. */
  185. public function chmod( $file, $mode = false, $recursive = false ) {
  186. if ( ! $mode ) {
  187. if ( $this->is_file( $file ) ) {
  188. $mode = FS_CHMOD_FILE;
  189. } elseif ( $this->is_dir( $file ) ) {
  190. $mode = FS_CHMOD_DIR;
  191. } else {
  192. return false;
  193. }
  194. }
  195. // chmod any sub-objects if recursive.
  196. if ( $recursive && $this->is_dir( $file ) ) {
  197. $filelist = $this->dirlist( $file );
  198. foreach ( (array) $filelist as $filename => $filemeta ) {
  199. $this->chmod( $file . '/' . $filename, $mode, $recursive );
  200. }
  201. }
  202. // chmod the file or directory
  203. if ( ! function_exists( 'ftp_chmod' ) ) {
  204. return (bool) @ftp_site( $this->link, sprintf( 'CHMOD %o %s', $mode, $file ) );
  205. }
  206. return (bool) @ftp_chmod( $this->link, $mode, $file );
  207. }
  208. /**
  209. * @param string $file
  210. * @return string
  211. */
  212. public function owner( $file ) {
  213. $dir = $this->dirlist( $file );
  214. return $dir[ $file ]['owner'];
  215. }
  216. /**
  217. * @param string $file
  218. * @return string
  219. */
  220. public function getchmod( $file ) {
  221. $dir = $this->dirlist( $file );
  222. return $dir[ $file ]['permsn'];
  223. }
  224. /**
  225. * @param string $file
  226. * @return string
  227. */
  228. public function group( $file ) {
  229. $dir = $this->dirlist( $file );
  230. return $dir[ $file ]['group'];
  231. }
  232. /**
  233. * @param string $source
  234. * @param string $destination
  235. * @param bool $overwrite
  236. * @param string|bool $mode
  237. * @return bool
  238. */
  239. public function copy( $source, $destination, $overwrite = false, $mode = false ) {
  240. if ( ! $overwrite && $this->exists( $destination ) ) {
  241. return false;
  242. }
  243. $content = $this->get_contents( $source );
  244. if ( false === $content ) {
  245. return false;
  246. }
  247. return $this->put_contents( $destination, $content, $mode );
  248. }
  249. /**
  250. * @param string $source
  251. * @param string $destination
  252. * @param bool $overwrite
  253. * @return bool
  254. */
  255. public function move( $source, $destination, $overwrite = false ) {
  256. return ftp_rename( $this->link, $source, $destination );
  257. }
  258. /**
  259. * @param string $file
  260. * @param bool $recursive
  261. * @param string $type
  262. * @return bool
  263. */
  264. public function delete( $file, $recursive = false, $type = false ) {
  265. if ( empty( $file ) ) {
  266. return false;
  267. }
  268. if ( 'f' == $type || $this->is_file( $file ) ) {
  269. return @ftp_delete( $this->link, $file );
  270. }
  271. if ( ! $recursive ) {
  272. return @ftp_rmdir( $this->link, $file );
  273. }
  274. $filelist = $this->dirlist( trailingslashit( $file ) );
  275. if ( ! empty( $filelist ) ) {
  276. foreach ( $filelist as $delete_file ) {
  277. $this->delete( trailingslashit( $file ) . $delete_file['name'], $recursive, $delete_file['type'] );
  278. }
  279. }
  280. return @ftp_rmdir( $this->link, $file );
  281. }
  282. /**
  283. * @param string $file
  284. * @return bool
  285. */
  286. public function exists( $file ) {
  287. $list = @ftp_nlist( $this->link, $file );
  288. if ( empty( $list ) && $this->is_dir( $file ) ) {
  289. return true; // File is an empty directory.
  290. }
  291. return ! empty( $list ); //empty list = no file, so invert.
  292. }
  293. /**
  294. * @param string $file
  295. * @return bool
  296. */
  297. public function is_file( $file ) {
  298. return $this->exists( $file ) && ! $this->is_dir( $file );
  299. }
  300. /**
  301. * @param string $path
  302. * @return bool
  303. */
  304. public function is_dir( $path ) {
  305. $cwd = $this->cwd();
  306. $result = @ftp_chdir( $this->link, trailingslashit( $path ) );
  307. if ( $result && $path == $this->cwd() || $this->cwd() != $cwd ) {
  308. @ftp_chdir( $this->link, $cwd );
  309. return true;
  310. }
  311. return false;
  312. }
  313. /**
  314. * @param string $file
  315. * @return bool
  316. */
  317. public function is_readable( $file ) {
  318. return true;
  319. }
  320. /**
  321. * @param string $file
  322. * @return bool
  323. */
  324. public function is_writable( $file ) {
  325. return true;
  326. }
  327. /**
  328. * @param string $file
  329. * @return bool
  330. */
  331. public function atime( $file ) {
  332. return false;
  333. }
  334. /**
  335. * @param string $file
  336. * @return int
  337. */
  338. public function mtime( $file ) {
  339. return ftp_mdtm( $this->link, $file );
  340. }
  341. /**
  342. * @param string $file
  343. * @return int
  344. */
  345. public function size( $file ) {
  346. return ftp_size( $this->link, $file );
  347. }
  348. /**
  349. * @param string $file
  350. * @return bool
  351. */
  352. public function touch( $file, $time = 0, $atime = 0 ) {
  353. return false;
  354. }
  355. /**
  356. * @param string $path
  357. * @param mixed $chmod
  358. * @param mixed $chown
  359. * @param mixed $chgrp
  360. * @return bool
  361. */
  362. public function mkdir( $path, $chmod = false, $chown = false, $chgrp = false ) {
  363. $path = untrailingslashit( $path );
  364. if ( empty( $path ) ) {
  365. return false;
  366. }
  367. if ( ! @ftp_mkdir( $this->link, $path ) ) {
  368. return false;
  369. }
  370. $this->chmod( $path, $chmod );
  371. return true;
  372. }
  373. /**
  374. * @param string $path
  375. * @param bool $recursive
  376. * @return bool
  377. */
  378. public function rmdir( $path, $recursive = false ) {
  379. return $this->delete( $path, $recursive );
  380. }
  381. /**
  382. * @staticvar bool $is_windows
  383. * @param string $line
  384. * @return array
  385. */
  386. public function parselisting( $line ) {
  387. static $is_windows = null;
  388. if ( is_null( $is_windows ) ) {
  389. $is_windows = stripos( ftp_systype( $this->link ), 'win' ) !== false;
  390. }
  391. if ( $is_windows && preg_match( '/([0-9]{2})-([0-9]{2})-([0-9]{2}) +([0-9]{2}):([0-9]{2})(AM|PM) +([0-9]+|<DIR>) +(.+)/', $line, $lucifer ) ) {
  392. $b = array();
  393. if ( $lucifer[3] < 70 ) {
  394. $lucifer[3] += 2000;
  395. } else {
  396. $lucifer[3] += 1900; // 4digit year fix
  397. }
  398. $b['isdir'] = ( $lucifer[7] == '<DIR>' );
  399. if ( $b['isdir'] ) {
  400. $b['type'] = 'd';
  401. } else {
  402. $b['type'] = 'f';
  403. }
  404. $b['size'] = $lucifer[7];
  405. $b['month'] = $lucifer[1];
  406. $b['day'] = $lucifer[2];
  407. $b['year'] = $lucifer[3];
  408. $b['hour'] = $lucifer[4];
  409. $b['minute'] = $lucifer[5];
  410. $b['time'] = @mktime( $lucifer[4] + ( strcasecmp( $lucifer[6], 'PM' ) == 0 ? 12 : 0 ), $lucifer[5], 0, $lucifer[1], $lucifer[2], $lucifer[3] );
  411. $b['am/pm'] = $lucifer[6];
  412. $b['name'] = $lucifer[8];
  413. } elseif ( ! $is_windows && $lucifer = preg_split( '/[ ]/', $line, 9, PREG_SPLIT_NO_EMPTY ) ) {
  414. //echo $line."\n";
  415. $lcount = count( $lucifer );
  416. if ( $lcount < 8 ) {
  417. return '';
  418. }
  419. $b = array();
  420. $b['isdir'] = $lucifer[0]{0} === 'd';
  421. $b['islink'] = $lucifer[0]{0} === 'l';
  422. if ( $b['isdir'] ) {
  423. $b['type'] = 'd';
  424. } elseif ( $b['islink'] ) {
  425. $b['type'] = 'l';
  426. } else {
  427. $b['type'] = 'f';
  428. }
  429. $b['perms'] = $lucifer[0];
  430. $b['permsn'] = $this->getnumchmodfromh( $b['perms'] );
  431. $b['number'] = $lucifer[1];
  432. $b['owner'] = $lucifer[2];
  433. $b['group'] = $lucifer[3];
  434. $b['size'] = $lucifer[4];
  435. if ( $lcount == 8 ) {
  436. sscanf( $lucifer[5], '%d-%d-%d', $b['year'], $b['month'], $b['day'] );
  437. sscanf( $lucifer[6], '%d:%d', $b['hour'], $b['minute'] );
  438. $b['time'] = @mktime( $b['hour'], $b['minute'], 0, $b['month'], $b['day'], $b['year'] );
  439. $b['name'] = $lucifer[7];
  440. } else {
  441. $b['month'] = $lucifer[5];
  442. $b['day'] = $lucifer[6];
  443. if ( preg_match( '/([0-9]{2}):([0-9]{2})/', $lucifer[7], $l2 ) ) {
  444. $b['year'] = date( 'Y' );
  445. $b['hour'] = $l2[1];
  446. $b['minute'] = $l2[2];
  447. } else {
  448. $b['year'] = $lucifer[7];
  449. $b['hour'] = 0;
  450. $b['minute'] = 0;
  451. }
  452. $b['time'] = strtotime( sprintf( '%d %s %d %02d:%02d', $b['day'], $b['month'], $b['year'], $b['hour'], $b['minute'] ) );
  453. $b['name'] = $lucifer[8];
  454. }
  455. }
  456. // Replace symlinks formatted as "source -> target" with just the source name
  457. if ( isset( $b['islink'] ) && $b['islink'] ) {
  458. $b['name'] = preg_replace( '/(\s*->\s*.*)$/', '', $b['name'] );
  459. }
  460. return $b;
  461. }
  462. /**
  463. * @param string $path
  464. * @param bool $include_hidden
  465. * @param bool $recursive
  466. * @return bool|array
  467. */
  468. public function dirlist( $path = '.', $include_hidden = true, $recursive = false ) {
  469. if ( $this->is_file( $path ) ) {
  470. $limit_file = basename( $path );
  471. $path = dirname( $path ) . '/';
  472. } else {
  473. $limit_file = false;
  474. }
  475. $pwd = @ftp_pwd( $this->link );
  476. if ( ! @ftp_chdir( $this->link, $path ) ) { // Cant change to folder = folder doesn't exist
  477. return false;
  478. }
  479. $list = @ftp_rawlist( $this->link, '-a', false );
  480. @ftp_chdir( $this->link, $pwd );
  481. if ( empty( $list ) ) { // Empty array = non-existent folder (real folder will show . at least)
  482. return false;
  483. }
  484. $dirlist = array();
  485. foreach ( $list as $k => $v ) {
  486. $entry = $this->parselisting( $v );
  487. if ( empty( $entry ) ) {
  488. continue;
  489. }
  490. if ( '.' == $entry['name'] || '..' == $entry['name'] ) {
  491. continue;
  492. }
  493. if ( ! $include_hidden && '.' == $entry['name'][0] ) {
  494. continue;
  495. }
  496. if ( $limit_file && $entry['name'] != $limit_file ) {
  497. continue;
  498. }
  499. $dirlist[ $entry['name'] ] = $entry;
  500. }
  501. $ret = array();
  502. foreach ( (array) $dirlist as $struc ) {
  503. if ( 'd' == $struc['type'] ) {
  504. if ( $recursive ) {
  505. $struc['files'] = $this->dirlist( $path . '/' . $struc['name'], $include_hidden, $recursive );
  506. } else {
  507. $struc['files'] = array();
  508. }
  509. }
  510. $ret[ $struc['name'] ] = $struc;
  511. }
  512. return $ret;
  513. }
  514. /**
  515. */
  516. public function __destruct() {
  517. if ( $this->link ) {
  518. ftp_close( $this->link );
  519. }
  520. }
  521. }