1<?php 2 3/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ 4 5/** 6 * Net_FTP socket implementation of FTP functions. 7 * 8 * The functions in this file emulate the ext/FTP functions through 9 * ext/Socket. 10 * 11 * PHP versions 4 and 5 12 * 13 * @category Networking 14 * @package FTP 15 * @author Tobias Schlitt <toby@php.net> 16 * @copyright 1997-2008 The PHP Group 17 * @license BSD http://www.opensource.org/licenses/bsd-license.php 18 * @version CVS: $Id$ 19 * @link http://pear.php.net/package/Net_FTP 20 * @since File available since Release 0.0.1 21 */ 22 23/** 24* Default FTP extension constants 25*/ 26define('FTP_ASCII', 0); 27define('FTP_TEXT', 0); 28define('FTP_BINARY', 1); 29define('FTP_IMAGE', 1); 30define('FTP_TIMEOUT_SEC', 0); 31 32/** 33* What needs to be done overall? 34* #1 Install the rest of these functions 35* #2 Document better 36* #3 Alot of other things I don't remember 37*/ 38 39/* 40 * !!! NOTE !!! 41 * Most of the comment's are "not working", 42 * meaning they are not all up-to-date 43 * !!! NOTE !!! 44 */ 45 46/** 47 * &resource ftp_connect ( string host [, int port [, int timeout ] ] ); 48 * 49 * Opens an FTP connection and return resource or false on failure. 50 * 51 * FTP Success respons code: 220 52 * 53 * @param string $host Host to connect to 54 * @param int $port Optional, port to connect to 55 * @param int $timeout Optional, seconds until function timeouts 56 * 57 * @todo The FTP extension has ftp_get_option() function which returns the 58 * timeout variable. This function needs to be created and contain it as 59 * static variable. 60 * @todo The FTP extension has ftp_set_option() function which sets the 61 * timeout variable. This function needs to be created and called here. 62 * @access public 63 * @return &resource 64 */ 65if (!function_exists('ftp_connect')) { 66 function &ftp_connect($host, $port = 21, $timeout = 90) 67 { 68 $false = false; // We are going to return refrence (E_STRICT) 69 70 if (!is_string($host) || !is_integer($port) || !is_integer($timeout)) { 71 return $false; 72 } 73 74 $iError = 0; 75 $sError = ''; 76 77 $control = @fsockopen($host, $port, $iError, $sError, 78 $timeout); 79 $GLOBALS['_NET_FTP']['timeout'] = $timeout; 80 81 if (!is_resource($control)) { 82 return $false; 83 } 84 85 stream_set_blocking($control, true); 86 stream_set_timeout($control, $timeout); 87 88 do { 89 $content[] = fgets($control, 8129); 90 $array = socket_get_status($control); 91 } while ($array['unread_bytes'] > 0); 92 93 if (substr($content[count($content)-1], 0, 3) == 220) { 94 return $control; 95 } 96 97 return $false; 98 } 99} 100 101/** 102 * boolean ftp_login ( resource stream, string username, string password ); 103 * 104 * Logs in to an given FTP connection stream. 105 * Returns TRUE on success or FALSE on failure. 106 * 107 * NOTE: 108 * Username and password are *not* optional. Function will *not* 109 * assume "anonymous" if username and/or password is empty 110 * 111 * FTP Success respons code: 230 112 * 113 * @param resource &$control FTP resource to login to 114 * @param string $username FTP Username to be used 115 * @param string $password FTP Password to be used 116 * 117 * @access public 118 * @return boolean 119 */ 120if (!function_exists('ftp_login')) { 121 function ftp_login(&$control, $username, $password) 122 { 123 if (!is_resource($control) || is_null($username)) { 124 return false; 125 } 126 127 fputs($control, 'USER '.$username."\r\n"); 128 $contents = array(); 129 do { 130 $contents[] = fgets($control, 8192); 131 $array = socket_get_status($control); 132 } while ($array['unread_bytes'] > 0); 133 134 if (substr($contents[count($contents)-1], 0, 3) != 331) { 135 return false; 136 } 137 138 fputs($control, 'PASS '.$password."\r\n"); 139 $contents = array(); 140 do { 141 $contents[] = fgets($control, 8192); 142 $array = socket_get_status($control); 143 } while ($array['unread_bytes']); 144 145 if (substr($contents[count($contents)-1], 0, 3) == 230) { 146 return true; 147 } 148 149 trigger_error('ftp_login() [<a href="function.ftp-login">function.ftp-login'. 150 '</a>]: '.$contents[count($contents)-1], E_USER_WARNING); 151 152 return false; 153 } 154} 155 156/** 157 * boolean ftp_quit ( resource stream ); 158 * 159 * Closes FTP connection. 160 * Returns TRUE or FALSE on error. 161 * 162 * NOTE: The PHP function ftp_quit is *alias* to ftp_close, here it is 163 * the *other-way-around* ( ftp_close() is alias to ftp_quit() ). 164 * 165 * NOTE: 166 * resource is set to null since unset() can't unset the variable. 167 * 168 * @param resource &$control FTP resource 169 * 170 * @access public 171 * @return boolean 172 */ 173if (!function_exists('ftp_quit')) { 174 function ftp_quit(&$control) 175 { 176 if (!is_resource($control)) { 177 return false; 178 } 179 180 fputs($control, 'QUIT'."\r\n"); 181 fclose($control); 182 $control = null; 183 return true; 184 } 185} 186 187/** 188 * Alias to ftp_quit() 189 * 190 * @param resource &$control FTP resource 191 * 192 * @see ftp_quit() 193 * @access public 194 * @return boolean 195 */ 196if (!function_exists('ftp_close')) { 197 function ftp_close(&$control) 198 { 199 return ftp_quit($control); 200 } 201} 202 203/** 204 * string ftp_pwd ( resource stream ); 205 * 206 * Gets the current directory name. 207 * Returns the current directory. 208 * 209 * Needs data connection: NO 210 * Success response code: 257 211 * 212 * @param resource &$control FTP resource 213 * 214 * @access public 215 * @return string 216 */ 217if (!function_exists('ftp_pwd')) { 218 function ftp_pwd(&$control) 219 { 220 if (!is_resource($control)) { 221 return $control; 222 } 223 224 fputs($control, 'PWD'."\r\n"); 225 226 $content = array(); 227 do { 228 $content[] = fgets($control, 8192); 229 $array = socket_get_status($control); 230 } while ($array['unread_bytes'] > 0); 231 232 if (substr($cont = $content[count($content)-1], 0, 3) == 257) { 233 $pos = strpos($cont, '"')+1; 234 $pos2 = strrpos($cont, '"') - $pos; 235 $path = substr($cont, $pos, $pos2); 236 return $path; 237 } 238 239 return false; 240 } 241} 242 243/** 244 * boolean ftp_chdir ( resource stream, string directory ); 245 * 246 * Changes the current directory to the specified directory. 247 * Returns TRUE on success or FALSE on failure. 248 * 249 * FTP success response code: 250 250 * Needs data connection: NO 251 * 252 * @param resource &$control FTP stream 253 * @param string $pwd Directory name 254 * 255 * @access public 256 * @return boolean 257 */ 258if (!function_exists('ftp_chdir')) { 259 function ftp_chdir(&$control, $pwd) 260 { 261 if (!is_resource($control) || !is_string($pwd)) { 262 return false; 263 } 264 265 fputs($control, 'CWD '.$pwd."\r\n"); 266 $content = array(); 267 do { 268 $content[] = fgets($control, 8192); 269 $array = socket_get_status($control); 270 } while ($array['unread_bytes'] > 0); 271 272 if (substr($content[count($content)-1], 0, 3) == 250) { 273 return true; 274 } 275 276 trigger_error('ftp_chdir() [<a 277 href="function.ftp-chdir">function.ftp-chdir</a>]: 278 ' .$content[count($content)-1], E_USER_WARNING); 279 280 return false; 281 } 282} 283 284$_NET_FTP = array(); 285$_NET_FTP['USE_PASSIVE'] = false; 286$_NET_FTP['DATA'] = null; 287 288/** 289 * boolean ftp_pasv ( resource stream, boolean passive ); 290 * 291 * Toggles passive mode ON/OFF. 292 * Returns TRUE on success or FALSE on failure. 293 * 294 * Comment: 295 * Although my lack of C knowlege I checked how the PHP FTP extension 296 * do things here. Seems like they create the data connection and store 297 * it in object for other functions to use. 298 * This is now done here. 299 * 300 * FTP success response code: 227 301 * 302 * @param stream &$control FTP stream 303 * @param boolean $pasv True to switch to passive, false for active mode 304 * 305 * @access public 306 * @return boolean 307 */ 308if (!function_exists('ftp_pasv')) { 309 function ftp_pasv(&$control, $pasv) 310 { 311 if (!is_resource($control) || !is_bool($pasv)) { 312 return false; 313 } 314 315 // If data connection exists, destroy it 316 if (isset($GLOBALS['_NET_FTP']['DATA'])) { 317 fclose($GLOBALS['_NET_FTP']['DATA']); 318 $GLOBALS['_NET_FTP']['DATA'] = null; 319 320 do { 321 fgets($control, 16); 322 $array = socket_get_status($control); 323 } while ($array['unread_bytes'] > 0); 324 } 325 326 // Are we suppost to create active or passive connection? 327 if (!$pasv) { 328 $GLOBALS['_NET_FTP']['USE_PASSIVE'] = false; 329 // Pick random "low bit" 330 $low = rand(39, 250); 331 // Pick random "high bit" 332 $high = rand(39, 250); 333 // Lowest possible port would be; 10023 334 // Highest possible port would be; 64246 335 336 $port = ($low<<8)+$high; 337 $ip = str_replace('.', ',', $_SERVER['SERVER_ADDR']); 338 $s = $ip.','.$low.','.$high; 339 340 $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); 341 if (is_resource($socket)) { 342 if (socket_bind($socket, '0.0.0.0', $port)) { 343 if (socket_listen($socket)) { 344 $GLOBALS['_NET_FTP']['DATA'] = &$socket; 345 fputs($control, 'PORT '.$s."\r\n"); 346 $line = fgets($control, 512); 347 if (substr($line, 0, 3) == 200) { 348 return true; 349 } 350 } 351 } 352 } 353 return false; 354 } 355 356 // Since we are here, we are suppost to create passive data connection. 357 fputs($control, 'PASV' ."\r\n"); 358 359 $content = array(); 360 do { 361 $content[] = fgets($control, 128); 362 $array = socket_get_status($control); 363 } while ($array['unread_bytes']); 364 365 if (substr($cont = $content[count($content)-1], 0, 3) != 227) { 366 return false; 367 } 368 369 $pos = strpos($cont, '(')+1; 370 $pos2 = strrpos($cont, ')')-$pos; 371 $string = substr($cont, $pos, $pos2); 372 373 $array = explode(',', $string); 374 375 // IP we are connecting to 376 $ip = $array[0]. '.' .$array[1]. '.' .$array[2]. '.' .$array[3]; 377 378 // Port ( 256*lowbit + highbit 379 $port = ($array[4] << 8)+$array[5]; 380 381 // Our data connection 382 $iError = 0; 383 $sError = ''; 384 $data = fsockopen($ip, $port, $iError, $sError, 385 $GLOBALS['_NET_FTP']['timeout']); 386 387 if (is_resource($data)) { 388 $GLOBALS['_NET_FTP']['USE_PASSIVE'] = true; 389 $GLOBALS['_NET_FTP']['DATA'] = &$data; 390 stream_set_blocking($data, true); 391 stream_set_timeout($data, $GLOBALS['_NET_FTP']['timeout']); 392 393 return true; 394 } 395 396 return false; 397 } 398} 399 400/** 401 * array ftp_rawlist ( resource stream, string directory [,bool recursive] ); 402 * 403 * Returns a detailed list of files in the given directory. 404 * 405 * Needs data connection: YES 406 * 407 * @param integer &$control FTP resource 408 * @param string $pwd Path to retrieve 409 * @param boolean $recursive Optional, retrieve recursive listing 410 * 411 * @todo Enable the recursive feature. 412 * @access public 413 * @return array 414 */ 415if (!function_exists('ftp_rawlist')) { 416 function ftp_rawlist(&$control, $pwd, $recursive = false) 417 { 418 if (!is_resource($control) || !is_string($pwd)) { 419 return false; 420 } 421 422 if (!isset($GLOBALS['_NET_FTP']['DATA']) || 423 !is_resource($GLOBALS['_NET_FTP']['DATA'])) { 424 425 ftp_pasv($control, $GLOBALS['_NET_FTP']['USE_PASSIVE']); 426 427 } 428 fputs($control, 'LIST '.$pwd."\r\n"); 429 430 $msg = fgets($control, 512); 431 if (substr($msg, 0, 3) == 425) { 432 return false; 433 } 434 435 $data = &$GLOBALS['_NET_FTP']['DATA']; 436 if (!$GLOBALS['_NET_FTP']['USE_PASSIVE']) { 437 $data = &socket_accept($data); 438 } 439 440 $content = array(); 441 442 switch ($GLOBALS['_NET_FTP']['USE_PASSIVE']) { 443 case true: 444 while (true) { 445 $string = rtrim(fgets($data, 1024)); 446 447 if ($string=='') { 448 break; 449 } 450 451 $content[] = $string; 452 } 453 454 fclose($data); 455 break; 456 457 case false: 458 $string = socket_read($data, 1024, PHP_BINARY_READ); 459 460 $content = explode("\n", $string); 461 unset($content[count($content)-1]); 462 463 socket_close($GLOBALS['_NET_FTP']['DATA']); 464 socket_close($data); 465 break; 466 } 467 468 $data = $GLOBALS['_NET_FTP']['DATA'] = null; 469 470 fgets($control, 1024); 471 return $content; 472 } 473} 474 475/** 476 * string ftp_systype ( resource stream ); 477 * 478 * Gets system type identifier of remote FTP server 479 * Returns the remote system type 480 * 481 * @param resource &$control FTP resource 482 * 483 * @access public 484 * @return string 485 */ 486if (!function_exists('ftp_systype')) { 487 function ftp_systype(&$control) 488 { 489 if (!is_resource($control)) { 490 return false; 491 } 492 493 fputs($control, 'SYST'."\r\n"); 494 $line = fgets($control, 256); 495 496 if (substr($line, 0, 3) != 215) { 497 return false; 498 } 499 500 $os = substr($line, 4, strpos($line, ' ', 4)-4); 501 return $os; 502 } 503} 504 505/** 506 * boolean ftp_alloc ( resource stream, integer bytes [, string &message ] ); 507 * 508 * Allocates space for a file to be uploaded 509 * Return TRUE on success or FALSE on failure 510 * 511 * NOTE; Many FTP servers do not support this command and/or don't need it. 512 * 513 * FTP success respons key: Belive it's 200 514 * Needs data connection: NO 515 * 516 * @param resource &$control FTP stream 517 * @param integer $int Space to allocate 518 * @param string &$msg Optional, textual representation of the servers response 519 * will be returned by reference 520 * 521 * @access public 522 * @return boolean 523 */ 524if (!function_exists('ftp_alloc')) { 525 function ftp_alloc(&$control, $int, &$msg = null) 526 { 527 if (!is_resource($control) || !is_integer($int)) { 528 return false; 529 } 530 531 fputs($control, 'ALLO '.$int.' R '.$int."\r\n"); 532 533 $msg = rtrim(fgets($control, 256)); 534 535 $code = substr($msg, 0, 3); 536 if ($code == 200 || $code == 202) { 537 return true; 538 } 539 540 return false; 541 } 542} 543 544/** 545 * bool ftp_put ( resource stream, string remote_file, string local_file, 546 * int mode [, int startpos ] ); 547 * 548 * Uploads a file to the FTP server 549 * Returns TRUE on success or FALSE on failure. 550 * 551 * NOTE: 552 * The transfer mode specified must be either FTP_ASCII or FTP_BINARY. 553 * 554 * @param resource &$control FTP stream 555 * @param string $remote Remote file to write 556 * @param string $local Local file to upload 557 * @param integer $mode Upload mode, FTP_ASCI || FTP_BINARY 558 * @param integer $pos Optional, start upload at position 559 * 560 * @access public 561 * @return boolean 562 */ 563if (!function_exists('ftp_put')) { 564 function ftp_put(&$control, $remote, $local, $mode, $pos = 0) 565 { 566 if (!is_resource($control) || !is_readable($local) || 567 !is_integer($mode) || !is_integer($pos)) { 568 return false; 569 } 570 571 $types = array ( 572 0 => 'A', 573 1 => 'I' 574 ); 575 $windows = array ( 576 0 => 't', 577 1 => 'b' 578 ); 579 580 /** 581 * TYPE values: 582 * A ( ASCII ) 583 * I ( BINARY ) 584 * E ( EBCDIC ) 585 * L ( BYTE ) 586 */ 587 588 if (!isset($GLOBALS['_NET_FTP']['DATA']) || 589 !is_resource($GLOBALS['_NET_FTP']['DATA'])) { 590 ftp_pasv($control, $GLOBALS['_NET_FTP']['USE_PASSIVE']); 591 } 592 593 // Establish data connection variable 594 $data = &$GLOBALS['_NET_FTP']['DATA']; 595 596 // Decide TYPE to use 597 fputs($control, 'TYPE '.$types[$mode]."\r\n"); 598 $line = fgets($control, 256); // "Type set to TYPE" 599 if (substr($line, 0, 3) != 200) { 600 return false; 601 } 602 603 fputs($control, 'STOR '.$remote."\r\n"); 604 sleep(1); 605 $line = fgets($control, 256); // "Opening TYPE mode data connect." 606 607 if (substr($line, 0, 3) != 150) { 608 return false; 609 } 610 611 // Creating resource to $local file 612 $fp = fopen($local, 'r'. $windows[$mode]); 613 if (!is_resource($fp)) { 614 $fp = null; 615 return false; 616 } 617 618 // Loop throu that file and echo it to the data socket 619 $i = 0; 620 switch ($GLOBALS['_NET_FTP']['USE_PASSIVE']) { 621 case false: 622 $data = &socket_accept($data); 623 while (!feof($fp)) { 624 $i += socket_write($data, fread($fp, 10240), 10240); 625 } 626 socket_close($data); 627 break; 628 629 case true: 630 while (!feof($fp)) { 631 $i += fputs($data, fread($fp, 10240), 10240); 632 } 633 fclose($data); 634 break; 635 } 636 637 $data = null; 638 do { 639 $line = fgets($control, 256); 640 } while (substr($line, 0, 4) != "226 "); 641 return true; 642 } 643} 644 645/** 646 * Retrieve a remote file to a local file 647 * Returns TRUE on success or FALSE on failure 648 * 649 * @param integer &$control Stream ID 650 * @param string $local Local filename 651 * @param string $remote Remote filename 652 * @param integer $mode Transfer mode (FTP_ASCII or FTP_BINARY) 653 * @param integer $resume Resume the file transfer or not 654 * 655 * @access public 656 * @return boolean 657 */ 658if (!function_exists('ftp_get')) { 659 function ftp_get(&$control, $local, $remote, $mode, $resume = 0) 660 { 661 if (!is_resource($control) || !is_writable(dirname($local)) || 662 !is_integer($mode) || !is_integer($resume)) { 663 return false; 664 } 665 $types = array ( 666 0 => 'A', 667 1 => 'I' 668 ); 669 $windows = array ( 670 0 => 't', 671 1 => 'b' 672 ); 673 674 if (!isset($GLOBALS['_NET_FTP']['DATA']) || 675 !is_resource($GLOBALS['_NET_FTP'][ 'DATA'])) { 676 ftp_pasv($control, $GLOBALS['_NET_FTP']['USE_PASSIVE']); 677 } 678 679 fputs($control, 'TYPE '.$types[$mode]."\r\n"); 680 $line = fgets($control, 256); 681 if (substr($line, 0, 3) != 200) { 682 return false; 683 } 684 685 $fp = fopen($local, 'w'.$windows[$mode]); 686 if (!is_resource($fp)) { 687 $fp = null; 688 return false; 689 } 690 return true; 691 } 692} 693 694/** 695 * Changes to the parent directory 696 * Returns TRUE on success or FALSE on failure 697 * 698 * @param integer &$control Stream ID 699 * 700 * @access public 701 * @return boolean 702 */ 703if (!function_exists('ftp_cdup')) { 704 function ftp_cdup(&$control) 705 { 706 fputs($control, 'CDUP'."\r\n"); 707 $line = fgets($control, 256); 708 709 if (substr($line, 0, 3) != 250) { 710 return false; 711 } 712 713 return true; 714 } 715} 716 717/** 718 * Set permissions on a file via FTP 719 * Returns the new file permission on success or false on error 720 * 721 * NOTE: This command is *not* supported by the standard 722 * NOTE: This command not ready! 723 * 724 * @param integer &$control Stream ID 725 * @param integer $mode Octal value 726 * @param string $file File to change permissions on 727 * 728 * @todo Figure out a way to chmod files via FTP 729 * @access public 730 * @return integer 731 */ 732if (!function_exists('ftp_chmod')) { 733 function ftp_chmod(&$control, $mode, $file) 734 { 735 if (!is_resource($control) || !is_integer($mode) || !is_string($file)) { 736 return false; 737 } 738 739 // chmod not in the standard, proftpd doesn't recognize it 740 // use SITE CHMOD? 741 fputs($control, 'SITE CHMOD '.$mode. ' ' .$file."\r\n"); 742 $line = fgets($control, 256); 743 744 if (substr($line, 0, 3) == 200) { 745 return $mode; 746 } 747 748 trigger_error('ftp_chmod() [<a 749 href="function.ftp-chmod">function.ftp-chmod</a>]: ' . 750 rtrim($line), E_USER_WARNING); 751 return false; 752 } 753} 754 755/** 756 * Deletes a file on the FTP server 757 * Returns TRUE on success or FALSE on failure 758 * 759 * @param integer &$control Stream ID 760 * @param string $path File to delete 761 * 762 * @access public 763 * @return boolean 764 */ 765if (!function_exists('ftp_delete')) { 766 function ftp_delete(&$control, $path) 767 { 768 if (!is_resource($control) || !is_string($path)) { 769 return false; 770 } 771 772 fputs($control, 'DELE '.$path."\r\n"); 773 $line = fgets($control, 256); 774 775 if (substr($line, 0, 3) == 250) { 776 return true; 777 } 778 779 return false; 780 } 781} 782 783/** 784 * Requests execution of a program on the FTP server 785 * NOTE; SITE EXEC is *not* supported by the standart 786 * Returns TRUE on success or FALSE on error 787 * 788 * @param integer &$control Stream ID 789 * @param string $cmd Command to send 790 * 791 * @access public 792 * @todo Look a littlebit better into this 793 * @return boolean 794 */ 795if (!function_exists('ftp_exec')) { 796 function ftp_exec(&$control, $cmd) 797 { 798 if (!is_resource($control) || !is_string($cmd)) { 799 return false; 800 } 801 802 // Command not defined in the standart 803 // proftpd doesn't recognize SITE EXEC (only help,chgrp,chmod and ratio) 804 fputs($control, 'SITE EXEC '.$cmd."\r\n"); 805 $line = fgets($control, 256); 806 807 // php.net/ftp_exec uses respons code 200 to verify if command was sent 808 // successfully or not, so we'll just do the same 809 if (substr($line, 0, 3) == 200) { 810 return true; 811 } 812 813 return false; 814 } 815} 816?> 817