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