Back to index

php5  5.3.10
ftp_fopen_wrapper.c
Go to the documentation of this file.
00001 /*
00002    +----------------------------------------------------------------------+
00003    | PHP Version 5                                                        |
00004    +----------------------------------------------------------------------+
00005    | Copyright (c) 1997-2012 The PHP Group                                |
00006    +----------------------------------------------------------------------+
00007    | This source file is subject to version 3.01 of the PHP license,      |
00008    | that is bundled with this package in the file LICENSE, and is        |
00009    | available through the world-wide-web at the following url:           |
00010    | http://www.php.net/license/3_01.txt                                  |
00011    | If you did not receive a copy of the PHP license and are unable to   |
00012    | obtain it through the world-wide-web, please send a note to          |
00013    | license@php.net so we can mail you a copy immediately.               |
00014    +----------------------------------------------------------------------+
00015    | Authors: Rasmus Lerdorf <rasmus@php.net>                             |
00016    |          Jim Winstead <jimw@php.net>                                 |
00017    |          Hartmut Holzgraefe <hholzgra@php.net>                       |
00018    |          Sara Golemon <pollita@php.net>                              |
00019    +----------------------------------------------------------------------+
00020  */
00021 /* $Id: ftp_fopen_wrapper.c 321634 2012-01-01 13:15:04Z felipe $ */
00022 
00023 #include "php.h"
00024 #include "php_globals.h"
00025 #include "php_network.h"
00026 #include "php_ini.h"
00027 
00028 #include <stdio.h>
00029 #include <stdlib.h>
00030 #include <errno.h>
00031 #include <sys/types.h>
00032 #include <sys/stat.h>
00033 #include <fcntl.h>
00034 
00035 #ifdef PHP_WIN32
00036 #include <winsock2.h>
00037 #define O_RDONLY _O_RDONLY
00038 #include "win32/param.h"
00039 #else
00040 #include <sys/param.h>
00041 #endif
00042 
00043 #include "php_standard.h"
00044 
00045 #include <sys/types.h>
00046 #if HAVE_SYS_SOCKET_H
00047 #include <sys/socket.h>
00048 #endif
00049 
00050 #ifdef PHP_WIN32
00051 #include <winsock2.h>
00052 #elif defined(NETWARE) && defined(USE_WINSOCK)
00053 #include <novsock2.h>
00054 #else
00055 #include <netinet/in.h>
00056 #include <netdb.h>
00057 #if HAVE_ARPA_INET_H
00058 #include <arpa/inet.h>
00059 #endif
00060 #endif
00061 
00062 #if defined(PHP_WIN32) || defined(__riscos__) || defined(NETWARE)
00063 #undef AF_UNIX
00064 #endif
00065 
00066 #if defined(AF_UNIX)
00067 #include <sys/un.h>
00068 #endif
00069 
00070 #include "php_fopen_wrappers.h"
00071 
00072 #define FTPS_ENCRYPT_DATA 1
00073 #define GET_FTP_RESULT(stream)     get_ftp_result((stream), tmp_line, sizeof(tmp_line) TSRMLS_CC)
00074 
00075 typedef struct _php_ftp_dirstream_data {
00076        php_stream *datastream;
00077        php_stream *controlstream;
00078        php_stream *dirstream;
00079 } php_ftp_dirstream_data;
00080 
00081 /* {{{ get_ftp_result
00082  */
00083 static inline int get_ftp_result(php_stream *stream, char *buffer, size_t buffer_size TSRMLS_DC)
00084 {
00085        while (php_stream_gets(stream, buffer, buffer_size-1) &&
00086                  !(isdigit((int) buffer[0]) && isdigit((int) buffer[1]) &&
00087                       isdigit((int) buffer[2]) && buffer[3] == ' '));
00088        return strtol(buffer, NULL, 10);
00089 }
00090 /* }}} */
00091 
00092 /* {{{ php_stream_ftp_stream_stat
00093  */
00094 static int php_stream_ftp_stream_stat(php_stream_wrapper *wrapper, php_stream *stream, php_stream_statbuf *ssb TSRMLS_DC)
00095 {
00096        /* For now, we return with a failure code to prevent the underlying
00097         * file's details from being used instead. */
00098        return -1;
00099 }
00100 /* }}} */
00101 
00102 /* {{{ php_stream_ftp_stream_close
00103  */
00104 static int php_stream_ftp_stream_close(php_stream_wrapper *wrapper, php_stream *stream TSRMLS_DC)
00105 {
00106        php_stream *controlstream = stream->wrapperthis;
00107        int ret = 0;
00108        
00109        if (controlstream) {
00110               if (strpbrk(stream->mode, "wa+")) {
00111                      char tmp_line[512];
00112                      int result;
00113 
00114                      /* For write modes close data stream first to signal EOF to server */
00115                      result = GET_FTP_RESULT(controlstream);
00116                      if (result != 226 && result != 250) {
00117                             php_error_docref(NULL TSRMLS_CC, E_WARNING, "FTP server error %d:%s", result, tmp_line);
00118                             ret = EOF;
00119                      }
00120               }
00121 
00122               php_stream_write_string(controlstream, "QUIT\r\n");
00123               php_stream_close(controlstream);
00124               stream->wrapperthis = NULL;
00125        }
00126 
00127        return ret;
00128 }
00129 /* }}} */
00130 
00131 /* {{{ php_ftp_fopen_connect
00132  */
00133 static php_stream *php_ftp_fopen_connect(php_stream_wrapper *wrapper, char *path, char *mode, int options, char **opened_path, php_stream_context *context,
00134                                                                 php_stream **preuseid, php_url **presource, int *puse_ssl, int *puse_ssl_on_data TSRMLS_DC)
00135 {
00136        php_stream *stream = NULL, *reuseid = NULL;
00137        php_url *resource = NULL;
00138        int result, use_ssl, use_ssl_on_data = 0, tmp_len;
00139        char tmp_line[512];
00140        char *transport;
00141        int transport_len;
00142 
00143        resource = php_url_parse(path);
00144        if (resource == NULL || resource->path == NULL) {
00145               if (resource && presource) {
00146                      *presource = resource;
00147               }
00148               return NULL;
00149        }
00150 
00151        use_ssl = resource->scheme && (strlen(resource->scheme) > 3) && resource->scheme[3] == 's';
00152 
00153        /* use port 21 if one wasn't specified */
00154        if (resource->port == 0)
00155               resource->port = 21;
00156        
00157        transport_len = spprintf(&transport, 0, "tcp://%s:%d", resource->host, resource->port);
00158        stream = php_stream_xport_create(transport, transport_len, REPORT_ERRORS, STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT, NULL, NULL, context, NULL, NULL);
00159        efree(transport);
00160        if (stream == NULL) {
00161               result = 0; /* silence */
00162               goto connect_errexit;
00163        }
00164 
00165        php_stream_context_set(stream, context);
00166        php_stream_notify_info(context, PHP_STREAM_NOTIFY_CONNECT, NULL, 0);
00167 
00168        /* Start talking to ftp server */
00169        result = GET_FTP_RESULT(stream);
00170        if (result > 299 || result < 200) {
00171               php_stream_notify_error(context, PHP_STREAM_NOTIFY_FAILURE, tmp_line, result);
00172               goto connect_errexit;
00173        }
00174 
00175        if (use_ssl)  {
00176        
00177               /* send the AUTH TLS request name */
00178               php_stream_write_string(stream, "AUTH TLS\r\n");
00179 
00180               /* get the response */
00181               result = GET_FTP_RESULT(stream);
00182               if (result != 234) {
00183                      /* AUTH TLS not supported try AUTH SSL */
00184                      php_stream_write_string(stream, "AUTH SSL\r\n");
00185                      
00186                      /* get the response */
00187                      result = GET_FTP_RESULT(stream);
00188                      if (result != 334) {
00189                             use_ssl = 0;
00190                      } else {
00191                             /* we must reuse the old SSL session id */
00192                             /* if we talk to an old ftpd-ssl */
00193                             reuseid = stream;
00194                      }
00195               } else {
00196                      /* encrypt data etc */
00197 
00198 
00199               }
00200 
00201        }
00202        
00203        if (use_ssl) {
00204               if (php_stream_xport_crypto_setup(stream,
00205                             STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL TSRMLS_CC) < 0
00206                             || php_stream_xport_crypto_enable(stream, 1 TSRMLS_CC) < 0) {
00207                      php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unable to activate SSL mode");
00208                      php_stream_close(stream);
00209                      stream = NULL;
00210                      goto connect_errexit;
00211               }
00212        
00213               /* set PBSZ to 0 */
00214               php_stream_write_string(stream, "PBSZ 0\r\n");
00215 
00216               /* ignore the response */
00217               result = GET_FTP_RESULT(stream);
00218               
00219               /* set data connection protection level */
00220 #if FTPS_ENCRYPT_DATA
00221               php_stream_write_string(stream, "PROT P\r\n");
00222 
00223               /* get the response */
00224               result = GET_FTP_RESULT(stream);
00225               use_ssl_on_data = (result >= 200 && result<=299) || reuseid;
00226 #else
00227               php_stream_write_string(stream, "PROT C\r\n");
00228 
00229               /* get the response */
00230               result = GET_FTP_RESULT(stream);
00231 #endif
00232        }
00233 
00234 #define PHP_FTP_CNTRL_CHK(val, val_len, err_msg) {      \
00235        unsigned char *s = val, *e = s + val_len; \
00236        while (s < e) {      \
00237               if (iscntrl(*s)) {   \
00238                      php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, err_msg, val);      \
00239                      goto connect_errexit;       \
00240               }      \
00241               s++;   \
00242        }      \
00243 } 
00244 
00245        /* send the user name */
00246        if (resource->user != NULL) {
00247               tmp_len = php_raw_url_decode(resource->user, strlen(resource->user));
00248 
00249               PHP_FTP_CNTRL_CHK(resource->user, tmp_len, "Invalid login %s")
00250 
00251               php_stream_printf(stream TSRMLS_CC, "USER %s\r\n", resource->user);
00252        } else {
00253               php_stream_write_string(stream, "USER anonymous\r\n");
00254        }
00255        
00256        /* get the response */
00257        result = GET_FTP_RESULT(stream);
00258        
00259        /* if a password is required, send it */
00260        if (result >= 300 && result <= 399) {
00261               php_stream_notify_info(context, PHP_STREAM_NOTIFY_AUTH_REQUIRED, tmp_line, 0);
00262 
00263               if (resource->pass != NULL) {
00264                      tmp_len = php_raw_url_decode(resource->pass, strlen(resource->pass));
00265 
00266                      PHP_FTP_CNTRL_CHK(resource->pass, tmp_len, "Invalid password %s")
00267 
00268                      php_stream_printf(stream TSRMLS_CC, "PASS %s\r\n", resource->pass);
00269               } else {
00270                      /* if the user has configured who they are,
00271                         send that as the password */
00272                      char *from_address = php_ini_string("from", sizeof("from"), 0);
00273                      if (from_address[0] != '\0') {
00274                             php_stream_printf(stream TSRMLS_CC, "PASS %s\r\n", from_address);
00275                      } else {
00276                             php_stream_write_string(stream, "PASS anonymous\r\n");
00277                      }
00278               }
00279 
00280               /* read the response */
00281               result = GET_FTP_RESULT(stream);
00282 
00283               if (result > 299 || result < 200) {
00284                      php_stream_notify_error(context, PHP_STREAM_NOTIFY_AUTH_RESULT, tmp_line, result);
00285               } else {
00286                      php_stream_notify_info(context, PHP_STREAM_NOTIFY_AUTH_RESULT, tmp_line, result);
00287               }
00288        }
00289        if (result > 299 || result < 200) {
00290               goto connect_errexit;
00291        }
00292 
00293        if (puse_ssl) {
00294               *puse_ssl = use_ssl;
00295        }
00296        if (puse_ssl_on_data) {
00297               *puse_ssl_on_data = use_ssl_on_data;
00298        }
00299        if (preuseid) {
00300               *preuseid = reuseid;
00301        }
00302        if (presource) {
00303               *presource = resource;
00304        }
00305 
00306        return stream;
00307 
00308 connect_errexit:
00309        if (resource) {
00310               php_url_free(resource);     
00311        }
00312 
00313        if (stream) {
00314               php_stream_close(stream);
00315        }
00316 
00317        return NULL;
00318 }
00319 /* }}} */
00320 
00321 /* {{{ php_fopen_do_pasv
00322  */
00323 static unsigned short php_fopen_do_pasv(php_stream *stream, char *ip, size_t ip_size, char **phoststart TSRMLS_DC)
00324 {
00325        char tmp_line[512];
00326        int result, i;
00327        unsigned short portno;
00328        char *tpath, *ttpath, *hoststart=NULL;
00329 
00330 #ifdef HAVE_IPV6
00331        /* We try EPSV first, needed for IPv6 and works on some IPv4 servers */
00332        php_stream_write_string(stream, "EPSV\r\n");
00333        result = GET_FTP_RESULT(stream);
00334 
00335        /* check if we got a 229 response */
00336        if (result != 229) {
00337 #endif
00338               /* EPSV failed, let's try PASV */
00339               php_stream_write_string(stream, "PASV\r\n");
00340               result = GET_FTP_RESULT(stream);
00341               
00342               /* make sure we got a 227 response */
00343               if (result != 227) {
00344                      return 0;
00345               }
00346 
00347               /* parse pasv command (129, 80, 95, 25, 13, 221) */
00348               tpath = tmp_line;
00349               /* skip over the "227 Some message " part */
00350               for (tpath += 4; *tpath && !isdigit((int) *tpath); tpath++);
00351               if (!*tpath) {
00352                      return 0;
00353               }
00354               /* skip over the host ip, to get the port */
00355               hoststart = tpath;
00356               for (i = 0; i < 4; i++) {
00357                      for (; isdigit((int) *tpath); tpath++);
00358                      if (*tpath != ',') {
00359                             return 0;
00360                      }
00361                      *tpath='.';   
00362                      tpath++;
00363               }
00364               tpath[-1] = '\0';
00365               memcpy(ip, hoststart, ip_size);
00366               ip[ip_size-1] = '\0';
00367               hoststart = ip;
00368               
00369               /* pull out the MSB of the port */
00370               portno = (unsigned short) strtoul(tpath, &ttpath, 10) * 256;
00371               if (ttpath == NULL) {
00372                      /* didn't get correct response from PASV */
00373                      return 0;
00374               }
00375               tpath = ttpath;
00376               if (*tpath != ',') {
00377                      return 0;
00378               }
00379               tpath++;
00380               /* pull out the LSB of the port */
00381               portno += (unsigned short) strtoul(tpath, &ttpath, 10);
00382 #ifdef HAVE_IPV6
00383        } else {
00384               /* parse epsv command (|||6446|) */
00385               for (i = 0, tpath = tmp_line + 4; *tpath; tpath++) {
00386                      if (*tpath == '|') {
00387                             i++;
00388                             if (i == 3)
00389                                    break;
00390                      }
00391               }
00392               if (i < 3) {
00393                      return 0;
00394               }
00395               /* pull out the port */
00396               portno = (unsigned short) strtoul(tpath + 1, &ttpath, 10);
00397        }
00398 #endif 
00399        if (ttpath == NULL) {
00400               /* didn't get correct response from EPSV/PASV */
00401               return 0;
00402        }
00403 
00404        if (phoststart) {
00405               *phoststart = hoststart;
00406        }      
00407 
00408        return portno;
00409 }
00410 /* }}} */
00411 
00412 /* {{{ php_fopen_url_wrap_ftp
00413  */
00414 php_stream * php_stream_url_wrap_ftp(php_stream_wrapper *wrapper, char *path, char *mode, int options, char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC)
00415 {
00416        php_stream *stream = NULL, *datastream = NULL;
00417        php_url *resource = NULL;
00418        char tmp_line[512];
00419        char ip[sizeof("123.123.123.123")];
00420        unsigned short portno;
00421        char *hoststart = NULL;
00422        int result = 0, use_ssl, use_ssl_on_data=0;
00423        php_stream *reuseid=NULL;
00424        size_t file_size = 0;
00425        zval **tmpzval;
00426        int allow_overwrite = 0;
00427        int read_write = 0;
00428        char *transport;
00429        int transport_len;
00430 
00431        tmp_line[0] = '\0';
00432 
00433        if (strpbrk(mode, "r+")) {
00434               read_write = 1; /* Open for reading */
00435        }
00436        if (strpbrk(mode, "wa+")) {
00437               if (read_write) {
00438                      php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "FTP does not support simultaneous read/write connections");
00439                      return NULL;
00440               }
00441               if (strchr(mode, 'a')) {
00442                      read_write = 3; /* Open for Appending */
00443               } else {
00444                      read_write = 2; /* Open for writting */
00445               }
00446        }
00447        if (!read_write) {
00448               /* No mode specified? */
00449               php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unknown file open mode");
00450               return NULL;
00451        }
00452 
00453        if (context &&
00454               php_stream_context_get_option(context, "ftp", "proxy", &tmpzval) == SUCCESS) {
00455               if (read_write == 1) {
00456                      /* Use http wrapper to proxy ftp request */
00457                      return php_stream_url_wrap_http(wrapper, path, mode, options, opened_path, context STREAMS_CC TSRMLS_CC);
00458               } else {
00459                      /* ftp proxy is read-only */
00460                      php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "FTP proxy may only be used in read mode");
00461                      return NULL;
00462               }
00463        }
00464 
00465        stream = php_ftp_fopen_connect(wrapper, path, mode, options, opened_path, context, &reuseid, &resource, &use_ssl, &use_ssl_on_data TSRMLS_CC);
00466        if (!stream) {
00467               goto errexit;
00468        }
00469 
00470        /* set the connection to be binary */
00471        php_stream_write_string(stream, "TYPE I\r\n");
00472        result = GET_FTP_RESULT(stream);
00473        if (result > 299 || result < 200)
00474               goto errexit;
00475        
00476        /* find out the size of the file (verifying it exists) */
00477        php_stream_printf(stream TSRMLS_CC, "SIZE %s\r\n", resource->path);
00478        
00479        /* read the response */
00480        result = GET_FTP_RESULT(stream);
00481        if (read_write == 1) {
00482               /* Read Mode */
00483               char *sizestr;
00484               
00485               /* when reading file, it must exist */
00486               if (result > 299 || result < 200) {
00487                      errno = ENOENT;
00488                      goto errexit;
00489               }
00490               
00491               sizestr = strchr(tmp_line, ' ');
00492               if (sizestr) {
00493                      sizestr++;
00494                      file_size = atoi(sizestr);
00495                      php_stream_notify_file_size(context, file_size, tmp_line, result);
00496               }      
00497        } else if (read_write == 2) {
00498               /* when writing file (but not appending), it must NOT exist, unless a context option exists which allows it */
00499               if (context && php_stream_context_get_option(context, "ftp", "overwrite", &tmpzval) == SUCCESS) {
00500                      allow_overwrite = Z_LVAL_PP(tmpzval);
00501               }
00502               if (result <= 299 && result >= 200) {
00503                      if (allow_overwrite) {
00504                             /* Context permits overwritting file, 
00505                                so we just delete whatever's there in preparation */
00506                             php_stream_printf(stream TSRMLS_CC, "DELE %s\r\n", resource->path);
00507                             result = GET_FTP_RESULT(stream);
00508                             if (result >= 300 || result <= 199) {
00509                                    goto errexit;
00510                             }
00511                      } else {
00512                             php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Remote file already exists and overwrite context option not specified");
00513                             errno = EEXIST;
00514                             goto errexit;
00515                      }
00516               }
00517        }
00518 
00519        /* set up the passive connection */
00520        portno = php_fopen_do_pasv(stream, ip, sizeof(ip), &hoststart TSRMLS_CC);
00521 
00522        if (!portno) {
00523               goto errexit;
00524        }
00525 
00526        /* Send RETR/STOR command */
00527        if (read_write == 1) {
00528               /* set resume position if applicable */
00529               if (context &&
00530                      php_stream_context_get_option(context, "ftp", "resume_pos", &tmpzval) == SUCCESS &&
00531                      Z_TYPE_PP(tmpzval) == IS_LONG &&
00532                      Z_LVAL_PP(tmpzval) > 0) {
00533                      php_stream_printf(stream TSRMLS_CC, "REST %ld\r\n", Z_LVAL_PP(tmpzval));
00534                      result = GET_FTP_RESULT(stream);
00535                      if (result < 300 || result > 399) {                     
00536                             php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unable to resume from offset %ld", Z_LVAL_PP(tmpzval));
00537                             goto errexit;
00538                      }
00539               }
00540 
00541               /* retrieve file */
00542               memcpy(tmp_line, "RETR", sizeof("RETR"));
00543        } else if (read_write == 2) {
00544               /* Write new file */
00545               memcpy(tmp_line, "STOR", sizeof("STOR"));
00546        } else {
00547               /* Append */
00548               memcpy(tmp_line, "APPE", sizeof("APPE"));
00549        } 
00550        php_stream_printf(stream TSRMLS_CC, "%s %s\r\n", tmp_line, (resource->path != NULL ? resource->path : "/"));
00551        
00552        /* open the data channel */
00553        if (hoststart == NULL) {
00554               hoststart = resource->host;
00555        }
00556        transport_len = spprintf(&transport, 0, "tcp://%s:%d", hoststart, portno);
00557        datastream = php_stream_xport_create(transport, transport_len, REPORT_ERRORS, STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT, NULL, NULL, context, NULL, NULL);
00558        efree(transport);
00559        if (datastream == NULL) {
00560               goto errexit;
00561        }
00562 
00563        result = GET_FTP_RESULT(stream);
00564        if (result != 150 && result != 125) {
00565               /* Could not retrieve or send the file 
00566                * this data will only be sent to us after connection on the data port was initiated.
00567                */
00568               php_stream_close(datastream);
00569               datastream = NULL;
00570               goto errexit; 
00571        }
00572        
00573        php_stream_context_set(datastream, context);
00574        php_stream_notify_progress_init(context, 0, file_size);
00575 
00576        if (use_ssl_on_data && (php_stream_xport_crypto_setup(datastream,
00577                      STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL TSRMLS_CC) < 0 ||
00578                      php_stream_xport_crypto_enable(datastream, 1 TSRMLS_CC) < 0)) {
00579 
00580               php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unable to activate SSL mode");
00581               php_stream_close(datastream);
00582               datastream = NULL;
00583               goto errexit;
00584        }
00585 
00586        /* remember control stream */      
00587        datastream->wrapperthis = stream;
00588 
00589        php_url_free(resource);
00590        return datastream;
00591 
00592 errexit:
00593        if (resource) {
00594               php_url_free(resource);
00595        }
00596        if (stream) {
00597               php_stream_notify_error(context, PHP_STREAM_NOTIFY_FAILURE, tmp_line, result);
00598               php_stream_close(stream);
00599        }
00600        if (tmp_line[0] != '\0')
00601               php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "FTP server reports %s", tmp_line);
00602        return NULL;
00603 }
00604 /* }}} */
00605 
00606 /* {{{ php_ftp_dirsteam_read
00607  */
00608 static size_t php_ftp_dirstream_read(php_stream *stream, char *buf, size_t count TSRMLS_DC)
00609 {
00610        php_stream_dirent *ent = (php_stream_dirent *)buf;
00611        php_stream *innerstream;
00612        size_t tmp_len;
00613        char *basename;
00614        size_t basename_len;
00615 
00616        innerstream =  ((php_ftp_dirstream_data *)stream->abstract)->datastream;
00617 
00618        if (count != sizeof(php_stream_dirent)) {
00619               return 0;
00620        }
00621 
00622        if (php_stream_eof(innerstream)) {
00623               return 0;
00624        }
00625 
00626        if (!php_stream_get_line(innerstream, ent->d_name, sizeof(ent->d_name), &tmp_len)) {
00627               return 0;
00628        }
00629 
00630        php_basename(ent->d_name, tmp_len, NULL, 0, &basename, &basename_len TSRMLS_CC);
00631        if (!basename) {
00632               return 0;
00633        }
00634 
00635        if (!basename_len) {
00636               efree(basename);
00637               return 0;
00638        }
00639 
00640        tmp_len = MIN(sizeof(ent->d_name), basename_len - 1);
00641        memcpy(ent->d_name, basename, tmp_len);
00642        ent->d_name[tmp_len - 1] = '\0';
00643        efree(basename);
00644 
00645        /* Trim off trailing whitespace characters */
00646        tmp_len--;
00647        while (tmp_len >= 0 &&
00648                      (ent->d_name[tmp_len] == '\n' || ent->d_name[tmp_len] == '\r' ||
00649                       ent->d_name[tmp_len] == '\t' || ent->d_name[tmp_len] == ' ')) {
00650               ent->d_name[tmp_len--] = '\0';
00651        }
00652 
00653        return sizeof(php_stream_dirent);
00654 }
00655 /* }}} */
00656 
00657 /* {{{ php_ftp_dirstream_close
00658  */
00659 static int php_ftp_dirstream_close(php_stream *stream, int close_handle TSRMLS_DC)
00660 {
00661        php_ftp_dirstream_data *data = stream->abstract;
00662 
00663        /* close control connection */
00664        if (data->controlstream) {
00665               php_stream_close(data->controlstream);
00666               data->controlstream = NULL;
00667        }
00668        /* close data connection */
00669        php_stream_close(data->datastream);
00670        data->datastream = NULL;
00671        
00672        efree(data);
00673        stream->abstract = NULL;
00674 
00675        return 0;
00676 }
00677 /* }}} */
00678 
00679 /* ftp dirstreams only need to support read and close operations,
00680    They can't be rewound because the underlying ftp stream can't be rewound. */
00681 static php_stream_ops php_ftp_dirstream_ops = {
00682        NULL, /* write */
00683        php_ftp_dirstream_read, /* read */
00684        php_ftp_dirstream_close, /* close */
00685        NULL, /* flush */
00686        "ftpdir",
00687        NULL, /* rewind */
00688        NULL, /* cast */
00689        NULL, /* stat */
00690        NULL  /* set option */
00691 };
00692 
00693 /* {{{ php_stream_ftp_opendir
00694  */
00695 php_stream * php_stream_ftp_opendir(php_stream_wrapper *wrapper, char *path, char *mode, int options, char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC)
00696 {
00697        php_stream *stream, *reuseid, *datastream = NULL;
00698        php_ftp_dirstream_data *dirsdata;
00699        php_url *resource = NULL;
00700        int result = 0, use_ssl, use_ssl_on_data = 0;
00701        char *hoststart = NULL, tmp_line[512];
00702        char ip[sizeof("123.123.123.123")];
00703        unsigned short portno;
00704 
00705        tmp_line[0] = '\0';
00706 
00707        stream = php_ftp_fopen_connect(wrapper, path, mode, options, opened_path, context, &reuseid, &resource, &use_ssl, &use_ssl_on_data TSRMLS_CC);
00708        if (!stream) {
00709               goto opendir_errexit;       
00710        }
00711 
00712        /* set the connection to be ascii */
00713        php_stream_write_string(stream, "TYPE A\r\n");
00714        result = GET_FTP_RESULT(stream);
00715        if (result > 299 || result < 200)
00716               goto opendir_errexit;
00717 
00718        /* set up the passive connection */
00719        portno = php_fopen_do_pasv(stream, ip, sizeof(ip), &hoststart TSRMLS_CC);
00720 
00721        if (!portno) {
00722               goto opendir_errexit;
00723        }
00724 
00725        php_stream_printf(stream TSRMLS_CC, "NLST %s\r\n", (resource->path != NULL ? resource->path : "/"));
00726        
00727        /* open the data channel */
00728        if (hoststart == NULL) {
00729               hoststart = resource->host;
00730        }
00731        datastream = php_stream_sock_open_host(hoststart, portno, SOCK_STREAM, 0, 0);
00732        if (datastream == NULL) {
00733               goto opendir_errexit;
00734        }
00735 
00736        result = GET_FTP_RESULT(stream);
00737        if (result != 150 && result != 125) {
00738               /* Could not retrieve or send the file 
00739                * this data will only be sent to us after connection on the data port was initiated.
00740                */
00741               php_stream_close(datastream);
00742               datastream = NULL;
00743               goto opendir_errexit;       
00744        }
00745        
00746        php_stream_context_set(datastream, context);
00747 
00748        if (use_ssl_on_data && (php_stream_xport_crypto_setup(stream,
00749                      STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL TSRMLS_CC) < 0 ||
00750                      php_stream_xport_crypto_enable(stream, 1 TSRMLS_CC) < 0)) {
00751 
00752               php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unable to activate SSL mode");
00753               php_stream_close(datastream);
00754               datastream = NULL;
00755               goto opendir_errexit;
00756        }
00757 
00758        php_url_free(resource);
00759 
00760        dirsdata = emalloc(sizeof *dirsdata);
00761        dirsdata->datastream = datastream;
00762        dirsdata->controlstream = stream;
00763        dirsdata->dirstream = php_stream_alloc(&php_ftp_dirstream_ops, dirsdata, 0, mode);
00764 
00765        return dirsdata->dirstream;
00766 
00767 opendir_errexit:
00768        if (resource) {
00769               php_url_free(resource);
00770        }
00771        if (stream) {
00772               php_stream_notify_error(context, PHP_STREAM_NOTIFY_FAILURE, tmp_line, result);
00773               php_stream_close(stream);
00774        }
00775        if (tmp_line[0] != '\0') {
00776               php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "FTP server reports %s", tmp_line);
00777        }
00778        return NULL;
00779 }
00780 /* }}} */
00781 
00782 /* {{{ php_stream_ftp_url_stat
00783  */
00784 static int php_stream_ftp_url_stat(php_stream_wrapper *wrapper, char *url, int flags, php_stream_statbuf *ssb, php_stream_context *context TSRMLS_DC)
00785 {
00786        php_stream *stream = NULL;
00787        php_url *resource = NULL;
00788        int result;
00789        char tmp_line[512];
00790 
00791        /* If ssb is NULL then someone is misbehaving */
00792        if (!ssb) return -1;
00793 
00794        stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, context, NULL, &resource, NULL, NULL TSRMLS_CC);
00795        if (!stream) {
00796               goto stat_errexit;
00797        }
00798 
00799        ssb->sb.st_mode = 0644;                                                             /* FTP won't give us a valid mode, so aproximate one based on being readable */
00800        php_stream_printf(stream TSRMLS_CC, "CWD %s\r\n", (resource->path != NULL ? resource->path : "/")); /* If we can CWD to it, it's a directory (maybe a link, but we can't tell) */
00801        result = GET_FTP_RESULT(stream);
00802        if (result < 200 || result > 299) {
00803               ssb->sb.st_mode |= S_IFREG;
00804        } else {
00805               ssb->sb.st_mode |= S_IFDIR;
00806        }
00807 
00808        php_stream_write_string(stream, "TYPE I\r\n"); /* we need this since some servers refuse to accept SIZE command in ASCII mode */
00809        
00810        result = GET_FTP_RESULT(stream);
00811 
00812        if(result < 200 || result > 299) {
00813               goto stat_errexit;
00814        }
00815        
00816        php_stream_printf(stream TSRMLS_CC, "SIZE %s\r\n", (resource->path != NULL ? resource->path : "/"));
00817        result = GET_FTP_RESULT(stream);
00818        if (result < 200 || result > 299) {
00819               /* Failure either means it doesn't exist 
00820                  or it's a directory and this server
00821                  fails on listing directory sizes */
00822               if (ssb->sb.st_mode & S_IFDIR) {
00823                      ssb->sb.st_size = 0;
00824               } else {
00825                      goto stat_errexit;
00826               }
00827        } else {
00828               ssb->sb.st_size = atoi(tmp_line + 4);
00829        }
00830 
00831        php_stream_printf(stream TSRMLS_CC, "MDTM %s\r\n", (resource->path != NULL ? resource->path : "/"));
00832        result = GET_FTP_RESULT(stream);
00833        if (result == 213) {
00834               char *p = tmp_line + 4;
00835               int n;
00836               struct tm tm, tmbuf, *gmt;
00837               time_t stamp;
00838 
00839               while (p - tmp_line < sizeof(tmp_line) && !isdigit(*p)) {
00840                      p++;
00841               }
00842 
00843               if (p - tmp_line > sizeof(tmp_line)) {
00844                      goto mdtm_error;
00845               }
00846 
00847               n = sscanf(p, "%4u%2u%2u%2u%2u%2u", &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
00848               if (n != 6) {
00849                      goto mdtm_error;
00850               }
00851 
00852               tm.tm_year -= 1900;
00853               tm.tm_mon--;
00854               tm.tm_isdst = -1;
00855 
00856               /* figure out the GMT offset */
00857               stamp = time(NULL);
00858               gmt = php_gmtime_r(&stamp, &tmbuf);
00859               if (!gmt) {
00860                      goto mdtm_error;
00861               }
00862               gmt->tm_isdst = -1;
00863 
00864               /* apply the GMT offset */
00865               tm.tm_sec += stamp - mktime(gmt);
00866               tm.tm_isdst = gmt->tm_isdst;
00867 
00868               ssb->sb.st_mtime = mktime(&tm);
00869        } else {
00870               /* error or unsupported command */
00871 mdtm_error:
00872               ssb->sb.st_mtime = -1;
00873        }
00874 
00875        ssb->sb.st_ino = 0;                                     /* Unknown values */
00876        ssb->sb.st_dev = 0;
00877        ssb->sb.st_uid = 0;
00878        ssb->sb.st_gid = 0;
00879        ssb->sb.st_atime = -1;
00880        ssb->sb.st_ctime = -1;
00881 
00882        ssb->sb.st_nlink = 1;
00883        ssb->sb.st_rdev = -1;
00884 #ifdef HAVE_ST_BLKSIZE
00885        ssb->sb.st_blksize = 4096;                       /* Guess since FTP won't expose this information */
00886 #ifdef HAVE_ST_BLOCKS
00887        ssb->sb.st_blocks = (int)((4095 + ssb->sb.st_size) / ssb->sb.st_blksize); /* emulate ceil */
00888 #endif
00889 #endif
00890        php_stream_close(stream);
00891        php_url_free(resource);
00892        return 0;
00893 
00894 stat_errexit:
00895        if (resource) {
00896               php_url_free(resource);
00897        }
00898        if (stream) {
00899               php_stream_close(stream);
00900        }
00901        return -1;
00902 }
00903 /* }}} */
00904 
00905 /* {{{ php_stream_ftp_unlink
00906  */
00907 static int php_stream_ftp_unlink(php_stream_wrapper *wrapper, char *url, int options, php_stream_context *context TSRMLS_DC)
00908 {
00909        php_stream *stream = NULL;
00910        php_url *resource = NULL;
00911        int result;
00912        char tmp_line[512];
00913 
00914        stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, NULL, NULL, &resource, NULL, NULL TSRMLS_CC);
00915        if (!stream) {
00916               if (options & REPORT_ERRORS) {
00917                      php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to connect to %s", url);
00918               }
00919               goto unlink_errexit;
00920        }
00921 
00922        if (resource->path == NULL) {
00923               if (options & REPORT_ERRORS) {
00924                      php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid path provided in %s", url);
00925               }
00926               goto unlink_errexit;
00927        }
00928 
00929        /* Attempt to delete the file */
00930        php_stream_printf(stream TSRMLS_CC, "DELE %s\r\n", (resource->path != NULL ? resource->path : "/"));
00931 
00932        result = GET_FTP_RESULT(stream);
00933        if (result < 200 || result > 299) {
00934               if (options & REPORT_ERRORS) {
00935                      php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error Deleting file: %s", tmp_line);
00936               }
00937               goto unlink_errexit;
00938        }
00939 
00940        php_url_free(resource);
00941        php_stream_close(stream);
00942        return 1;
00943 
00944 unlink_errexit:
00945        if (resource) {
00946               php_url_free(resource);
00947        }
00948        if (stream) {
00949               php_stream_close(stream);
00950        }
00951        return 0;
00952 }
00953 /* }}} */
00954 
00955 /* {{{ php_stream_ftp_rename
00956  */
00957 static int php_stream_ftp_rename(php_stream_wrapper *wrapper, char *url_from, char *url_to, int options, php_stream_context *context TSRMLS_DC)
00958 {
00959        php_stream *stream = NULL;
00960        php_url *resource_from = NULL, *resource_to = NULL;
00961        int result;
00962        char tmp_line[512];
00963 
00964        resource_from = php_url_parse(url_from);
00965        resource_to = php_url_parse(url_to);
00966        /* Must be same scheme (ftp/ftp or ftps/ftps), same host, and same port 
00967               (or a 21/0 0/21 combination which is also "same") 
00968           Also require paths to/from */
00969        if (!resource_from ||
00970               !resource_to ||
00971               !resource_from->scheme ||
00972               !resource_to->scheme ||
00973               strcmp(resource_from->scheme, resource_to->scheme) ||
00974               !resource_from->host ||
00975               !resource_to->host ||
00976               strcmp(resource_from->host, resource_to->host) ||
00977               (resource_from->port != resource_to->port && 
00978                resource_from->port * resource_to->port != 0 && 
00979                resource_from->port + resource_to->port != 21) ||
00980               !resource_from->path ||
00981               !resource_to->path) {
00982               goto rename_errexit;
00983        }
00984 
00985        stream = php_ftp_fopen_connect(wrapper, url_from, "r", 0, NULL, NULL, NULL, NULL, NULL, NULL TSRMLS_CC);
00986        if (!stream) {
00987               if (options & REPORT_ERRORS) {
00988                      php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to connect to %s", resource_from->host);
00989               }
00990               goto rename_errexit;
00991        }
00992 
00993        /* Rename FROM */
00994        php_stream_printf(stream TSRMLS_CC, "RNFR %s\r\n", (resource_from->path != NULL ? resource_from->path : "/"));
00995 
00996        result = GET_FTP_RESULT(stream);
00997        if (result < 300 || result > 399) {
00998               if (options & REPORT_ERRORS) {
00999                      php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error Renaming file: %s", tmp_line);
01000               }
01001               goto rename_errexit;
01002        }
01003 
01004        /* Rename TO */
01005        php_stream_printf(stream TSRMLS_CC, "RNTO %s\r\n", (resource_to->path != NULL ? resource_to->path : "/"));
01006 
01007        result = GET_FTP_RESULT(stream);
01008        if (result < 200 || result > 299) {
01009               if (options & REPORT_ERRORS) {
01010                      php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error Renaming file: %s", tmp_line);
01011               }
01012               goto rename_errexit;
01013        }
01014 
01015        php_url_free(resource_from);
01016        php_url_free(resource_to);
01017        php_stream_close(stream);
01018        return 1;
01019 
01020 rename_errexit:
01021        if (resource_from) {
01022               php_url_free(resource_from);
01023        }
01024        if (resource_to) {
01025               php_url_free(resource_to);
01026        }
01027        if (stream) {
01028               php_stream_close(stream);
01029        }
01030        return 0;
01031 }
01032 /* }}} */
01033 
01034 /* {{{ php_stream_ftp_mkdir
01035  */
01036 static int php_stream_ftp_mkdir(php_stream_wrapper *wrapper, char *url, int mode, int options, php_stream_context *context TSRMLS_DC)
01037 {
01038        php_stream *stream = NULL;
01039        php_url *resource = NULL;
01040        int result, recursive = options & PHP_STREAM_MKDIR_RECURSIVE;
01041        char tmp_line[512];
01042 
01043        stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, NULL, NULL, &resource, NULL, NULL TSRMLS_CC);
01044        if (!stream) {
01045               if (options & REPORT_ERRORS) {
01046                      php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to connect to %s", url);
01047               }
01048               goto mkdir_errexit;
01049        }
01050 
01051        if (resource->path == NULL) {
01052               if (options & REPORT_ERRORS) {
01053                      php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid path provided in %s", url);
01054               }
01055               goto mkdir_errexit;
01056        }
01057 
01058        if (!recursive) {
01059               php_stream_printf(stream TSRMLS_CC, "MKD %s\r\n", resource->path);
01060               result = GET_FTP_RESULT(stream);
01061     } else {
01062         /* we look for directory separator from the end of string, thus hopefuly reducing our work load */
01063         char *p, *e, *buf;
01064 
01065         buf = estrdup(resource->path);
01066         e = buf + strlen(buf);
01067 
01068         /* find a top level directory we need to create */
01069         while ((p = strrchr(buf, '/'))) {
01070             *p = '\0';
01071                      php_stream_printf(stream TSRMLS_CC, "CWD %s\r\n", buf);
01072                      result = GET_FTP_RESULT(stream);
01073                      if (result >= 200 && result <= 299) {
01074                             *p = '/';
01075                             break;
01076                      }
01077         }
01078         if (p == buf) {
01079                      php_stream_printf(stream TSRMLS_CC, "MKD %s\r\n", resource->path);
01080                      result = GET_FTP_RESULT(stream);
01081         } else {
01082                      php_stream_printf(stream TSRMLS_CC, "MKD %s\r\n", buf);
01083                      result = GET_FTP_RESULT(stream);
01084                      if (result >= 200 && result <= 299) {
01085                             if (!p) {
01086                                    p = buf;
01087                             }
01088                             /* create any needed directories if the creation of the 1st directory worked */
01089                             while (++p != e) {
01090                                    if (*p == '\0' && *(p + 1) != '\0') {
01091                                           *p = '/';
01092                                           php_stream_printf(stream TSRMLS_CC, "MKD %s\r\n", buf);
01093                                           result = GET_FTP_RESULT(stream);
01094                                           if (result < 200 || result > 299) {
01095                                                  if (options & REPORT_ERRORS) {
01096                                                         php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", tmp_line);
01097                                                  }
01098                                                  break;
01099                                           }
01100                                    }
01101                             }
01102                      }
01103               }
01104         efree(buf);
01105     }
01106 
01107        php_url_free(resource);
01108        php_stream_close(stream);
01109 
01110        if (result < 200 || result > 299) {
01111               /* Failure */
01112               return 0;
01113        }
01114 
01115        return 1;
01116 
01117 mkdir_errexit:
01118        if (resource) {
01119               php_url_free(resource);
01120        }
01121        if (stream) {
01122               php_stream_close(stream);
01123        }
01124        return 0;
01125 }
01126 /* }}} */
01127 
01128 /* {{{ php_stream_ftp_rmdir
01129  */
01130 static int php_stream_ftp_rmdir(php_stream_wrapper *wrapper, char *url, int options, php_stream_context *context TSRMLS_DC)
01131 {
01132        php_stream *stream = NULL;
01133        php_url *resource = NULL;
01134        int result;
01135        char tmp_line[512];
01136 
01137        stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, NULL, NULL, &resource, NULL, NULL TSRMLS_CC);
01138        if (!stream) {
01139               if (options & REPORT_ERRORS) {
01140                      php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to connect to %s", url);
01141               }
01142               goto rmdir_errexit;
01143        }
01144 
01145        if (resource->path == NULL) {
01146               if (options & REPORT_ERRORS) {
01147                      php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid path provided in %s", url);
01148               }
01149               goto rmdir_errexit;
01150        }
01151 
01152        php_stream_printf(stream TSRMLS_CC, "RMD %s\r\n", resource->path);
01153        result = GET_FTP_RESULT(stream);
01154 
01155        if (result < 200 || result > 299) {
01156               if (options & REPORT_ERRORS) {
01157                      php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", tmp_line);
01158               }
01159               goto rmdir_errexit;
01160        }
01161 
01162        php_url_free(resource);
01163        php_stream_close(stream);
01164 
01165        return 1;
01166 
01167 rmdir_errexit:
01168        if (resource) {
01169               php_url_free(resource);
01170        }
01171        if (stream) {
01172               php_stream_close(stream);
01173        }
01174        return 0;
01175 }
01176 /* }}} */
01177 
01178 static php_stream_wrapper_ops ftp_stream_wops = {
01179        php_stream_url_wrap_ftp,
01180        php_stream_ftp_stream_close, /* stream_close */
01181        php_stream_ftp_stream_stat,
01182        php_stream_ftp_url_stat, /* stat_url */
01183        php_stream_ftp_opendir, /* opendir */
01184        "ftp",
01185        php_stream_ftp_unlink, /* unlink */
01186        php_stream_ftp_rename, /* rename */
01187        php_stream_ftp_mkdir,  /* mkdir */
01188        php_stream_ftp_rmdir   /* rmdir */
01189 };
01190 
01191 PHPAPI php_stream_wrapper php_stream_ftp_wrapper =      {
01192        &ftp_stream_wops,
01193        NULL,
01194        1 /* is_url */
01195 };
01196 
01197 
01198 /*
01199  * Local variables:
01200  * tab-width: 4
01201  * c-basic-offset: 4
01202  * End:
01203  * vim600: sw=4 ts=4 fdm=marker
01204  * vim<600: sw=4 ts=4
01205  */