From 228f65f88936579b91542cc7e9bf7a72edd40b6c Mon Sep 17 00:00:00 2001 From: Sara Golemon Date: Thu, 24 Apr 2003 20:32:14 +0000 Subject: [PATCH] Abtract some ftp connection/negotiation steps out and add support for stream_opendir. This allows things like opendir('ftp://ftp.foo.com/path') --- ext/standard/ftp_fopen_wrapper.c | 435 +++++++++++++++++++++++-------- 1 file changed, 327 insertions(+), 108 deletions(-) diff --git a/ext/standard/ftp_fopen_wrapper.c b/ext/standard/ftp_fopen_wrapper.c index 3ec09c1fa7..a60be294a1 100644 --- a/ext/standard/ftp_fopen_wrapper.c +++ b/ext/standard/ftp_fopen_wrapper.c @@ -113,49 +113,18 @@ static int php_stream_ftp_stream_close(php_stream_wrapper *wrapper, return 0; } - -static php_stream_wrapper_ops ftp_stream_wops = { - php_stream_url_wrap_ftp, - php_stream_ftp_stream_close, /* stream_close */ - php_stream_ftp_stream_stat, - NULL, /* stat_url */ - NULL, /* opendir */ - "FTP" -}; - -PHPAPI php_stream_wrapper php_stream_ftp_wrapper = { - &ftp_stream_wops, - NULL, - 1 /* is_url */ -}; - - -/* {{{ php_fopen_url_wrap_ftp +/* {{{ php_ftp_fopen_connect */ -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) +static php_stream *php_ftp_fopen_connect(php_stream_wrapper *wrapper, char *path, char *mode, int options, char **opened_path, php_stream_context *context, + php_stream **preuseid, php_url **presource, int *puse_ssl, int *puse_ssl_on_data + STREAMS_DC TSRMLS_DC) { - php_stream *stream=NULL, *datastream=NULL; - php_url *resource=NULL; - char tmp_line[512]; - char ip[sizeof("123.123.123.123")]; - unsigned short portno; + php_stream *stream = NULL, *reuseid = NULL; + php_url *resource = NULL; + int result, use_ssl, use_ssl_on_data = 0; char *scratch; - int result; - int i, use_ssl; - int use_ssl_on_data=0; - php_stream *reuseid=NULL; - char *tpath, *ttpath, *hoststart=NULL; - size_t file_size = 0; - zval **tmpzval; - int allow_overwrite = 0; - - tmp_line[0] = '\0'; + char tmp_line[512]; - if (strpbrk(mode, "a+")) { - php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "FTP does not support simultaneous read/write connections."); - return NULL; - } - resource = php_url_parse((char *) path); if (resource == NULL || resource->path == NULL) return NULL; @@ -169,7 +138,7 @@ php_stream * php_stream_url_wrap_ftp(php_stream_wrapper *wrapper, char *path, ch stream = php_stream_sock_open_host(resource->host, resource->port, SOCK_STREAM, NULL, 0); if (stream == NULL) { result = 0; /* silence */ - goto errexit; + goto connect_errexit; } php_stream_context_set(stream, context); @@ -179,7 +148,7 @@ php_stream * php_stream_url_wrap_ftp(php_stream_wrapper *wrapper, char *path, ch result = GET_FTP_RESULT(stream); if (result > 299 || result < 200) { php_stream_notify_error(context, PHP_STREAM_NOTIFY_FAILURE, tmp_line, result); - goto errexit; + goto connect_errexit; } if (use_ssl) { @@ -217,7 +186,7 @@ php_stream * php_stream_url_wrap_ftp(php_stream_wrapper *wrapper, char *path, ch php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unable to activate SSL mode"); php_stream_close(stream); stream = NULL; - goto errexit; + goto connect_errexit; } /* set PBSZ to 0 */ @@ -282,9 +251,147 @@ php_stream * php_stream_url_wrap_ftp(php_stream_wrapper *wrapper, char *path, ch php_stream_notify_info(context, PHP_STREAM_NOTIFY_AUTH_RESULT, tmp_line, result); } } - if (result > 299 || result < 200) - goto errexit; + if (result > 299 || result < 200) { + goto connect_errexit; + } + + if (puse_ssl) { + *puse_ssl = use_ssl; + } + if (puse_ssl_on_data) { + *puse_ssl_on_data = use_ssl_on_data; + } + if (preuseid) { + *preuseid = reuseid; + } + if (presource) { + *presource = resource; + } + + return stream; + + connect_errexit: + if (stream) { + php_stream_close(stream); + } + + return NULL; +} +/* }}} */ + +/* {{{ php_fopen_do_pasv + */ +static unsigned short php_fopen_do_pasv(php_stream *stream, char *ip, int ip_size, char **phoststart STREAMS_DC TSRMLS_DC) +{ + char tmp_line[512]; + int result, i; + unsigned short portno; + char *tpath, *ttpath, *hoststart=NULL; + + /* We try EPSV first, needed for IPv6 and works on some IPv4 servers */ + php_stream_write_string(stream, "EPSV\r\n"); + result = GET_FTP_RESULT(stream); + + /* check if we got a 229 response */ + if (result != 229) { + /* EPSV failed, let's try PASV */ + php_stream_write_string(stream, "PASV\r\n"); + result = GET_FTP_RESULT(stream); + + /* make sure we got a 227 response */ + if (result != 227) { + return 0; + } + + /* parse pasv command (129, 80, 95, 25, 13, 221) */ + tpath = tmp_line; + /* skip over the "227 Some message " part */ + for (tpath += 4; *tpath && !isdigit((int) *tpath); tpath++); + if (!*tpath) { + return 0; + } + /* skip over the host ip, to get the port */ + hoststart = tpath; + for (i = 0; i < 4; i++) { + for (; isdigit((int) *tpath); tpath++); + if (*tpath != ',') { + return 0; + } + *tpath='.'; + tpath++; + } + tpath[-1] = '\0'; + memcpy(ip, hoststart, ip_size); + ip[ip_size-1] = '\0'; + hoststart = ip; + + /* pull out the MSB of the port */ + portno = (unsigned short) strtoul(tpath, &ttpath, 10) * 256; + if (ttpath == NULL) { + /* didn't get correct response from PASV */ + return 0; + } + tpath = ttpath; + if (*tpath != ',') { + return 0; + } + tpath++; + /* pull out the LSB of the port */ + portno += (unsigned short) strtoul(tpath, &ttpath, 10); + } else { + /* parse epsv command (|||6446|) */ + for (i = 0, tpath = tmp_line + 4; *tpath; tpath++) { + if (*tpath == '|') { + i++; + if (i == 3) + break; + } + } + if (i < 3) { + return 0; + } + /* pull out the port */ + portno = (unsigned short) strtoul(tpath + 1, &ttpath, 10); + } + if (ttpath == NULL) { + /* didn't get correct response from EPSV/PASV */ + return 0; + } + + if (phoststart) { + *phoststart = hoststart; + } + + return portno; +} +/* }}} */ + +/* {{{ php_fopen_url_wrap_ftp + */ +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) +{ + php_stream *stream = NULL, *datastream = NULL; + php_url *resource = NULL; + char tmp_line[512]; + char ip[sizeof("123.123.123.123")]; + unsigned short portno; + char *scratch, *hoststart = NULL; + int result, use_ssl, use_ssl_on_data=0; + php_stream *reuseid=NULL; + size_t file_size = 0; + zval **tmpzval; + int allow_overwrite = 0; + + tmp_line[0] = '\0'; + + if (strpbrk(mode, "a+")) { + php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "FTP does not support simultaneous read/write connections."); + return NULL; + } + + stream = php_ftp_fopen_connect(wrapper, path, mode, options, opened_path, context, &reuseid, &resource, &use_ssl, &use_ssl_on_data STREAMS_CC TSRMLS_CC); + /* set the connection to be binary */ php_stream_write_string(stream, "TYPE I\r\n"); result = GET_FTP_RESULT(stream); @@ -337,73 +444,13 @@ php_stream * php_stream_url_wrap_ftp(php_stream_wrapper *wrapper, char *path, ch } /* set up the passive connection */ + portno = php_fopen_do_pasv(stream, ip, sizeof(ip), &hoststart STREAMS_CC TSRMLS_CC); - /* We try EPSV first, needed for IPv6 and works on some IPv4 servers */ - php_stream_write_string(stream, "EPSV\r\n"); - result = GET_FTP_RESULT(stream); - - /* check if we got a 229 response */ - if (result != 229) { - /* EPSV failed, let's try PASV */ - php_stream_write_string(stream, "PASV\r\n"); - result = GET_FTP_RESULT(stream); - - /* make sure we got a 227 response */ - if (result != 227) - goto errexit; - - /* parse pasv command (129, 80, 95, 25, 13, 221) */ - tpath = tmp_line; - /* skip over the "227 Some message " part */ - for (tpath += 4; *tpath && !isdigit((int) *tpath); tpath++); - if (!*tpath) - goto errexit; - /* skip over the host ip, to get the port */ - hoststart = tpath; - for (i = 0; i < 4; i++) { - for (; isdigit((int) *tpath); tpath++); - if (*tpath != ',') - goto errexit; - *tpath='.'; - tpath++; - } - tpath[-1] = '\0'; - memcpy(ip, hoststart, sizeof(ip)); - ip[sizeof(ip)-1] = '\0'; - hoststart = ip; - - /* pull out the MSB of the port */ - portno = (unsigned short) strtoul(tpath, &ttpath, 10) * 256; - if (ttpath == NULL) { - /* didn't get correct response from PASV */ - goto errexit; - } - tpath = ttpath; - if (*tpath != ',') - goto errexit; - tpath++; - /* pull out the LSB of the port */ - portno += (unsigned short) strtoul(tpath, &ttpath, 10); - } else { - /* parse epsv command (|||6446|) */ - for (i = 0, tpath = tmp_line + 4; *tpath; tpath++) { - if (*tpath == '|') { - i++; - if (i == 3) - break; - } - } - if (i < 3) - goto errexit; - /* pull out the port */ - portno = (unsigned short) strtoul(tpath + 1, &ttpath, 10); - } - - if (ttpath == NULL) { - /* didn't get correct response from EPSV/PASV */ + if (!portno) { goto errexit; } - + + /* Send RETR/STOR command */ if (mode[0] == 'r') { /* retrieve file */ php_stream_write_string(stream, "RETR "); @@ -475,6 +522,178 @@ php_stream * php_stream_url_wrap_ftp(php_stream_wrapper *wrapper, char *path, ch } /* }}} */ +/* {{{ php_ftp_dirsteam_read + */ +static size_t php_ftp_dirstream_read(php_stream *stream, char *buf, size_t count TSRMLS_DC) +{ + php_stream_dirent *ent = (php_stream_dirent *)buf; + php_stream *innerstream = (php_stream *)stream->abstract; + size_t tmp_len; + char *basename; + + if (count != sizeof(php_stream_dirent)) { + return 0; + } + + if (php_stream_eof(innerstream)) { + return 0; + } + + if (!php_stream_get_line(innerstream, ent->d_name, sizeof(ent->d_name), &tmp_len)) { + return 0; + } + if (!(basename = php_basename(ent->d_name, tmp_len, NULL, 0))) { + return 0; + } + + if (strlen(basename) == 0) { + efree(basename); + return 0; + } + strcpy(ent->d_name, basename); + efree(basename); + + return sizeof(php_stream_dirent); +} +/* }}} */ + +/* {{{ php_ftp_dirstream_close + */ +static int php_ftp_dirstream_close(php_stream *stream, int close_handle TSRMLS_DC) +{ + php_stream *innerstream = (php_stream *)stream->abstract; + + if (innerstream->wrapperdata) { + php_stream_close((php_stream *)innerstream->wrapperdata); + innerstream->wrapperdata = NULL; + } + php_stream_close((php_stream *)stream->abstract); + stream->abstract = NULL; + + return 0; +} +/* }}} */ + +/* ftp dirstreams only need to support read and close operations, + They can't be rewound because the underlying ftp stream can't be rewound. */ +static php_stream_ops php_ftp_dirstream_ops = { + NULL, /* write */ + php_ftp_dirstream_read, /* read */ + php_ftp_dirstream_close, /* close */ + NULL, /* flush */ + "ftpdir", + NULL, /* rewind */ + NULL, /* cast */ + NULL, /* stat */ + NULL /* set option */ +}; + +/* {{{ php_stream_ftp_opendir + */ +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) +{ + php_stream *stream, *reuseid, *datastream = NULL; + php_url *resource; + int result, use_ssl, use_ssl_on_data = 0; + char *hoststart = NULL, tmp_line[512]; + char ip[sizeof("123.123.123.123")]; + unsigned short portno; + + stream = php_ftp_fopen_connect(wrapper, path, mode, options, opened_path, context, &reuseid, &resource, &use_ssl, &use_ssl_on_data STREAMS_CC TSRMLS_CC); + + /* set the connection to be ascii */ + php_stream_write_string(stream, "TYPE A\r\n"); + result = GET_FTP_RESULT(stream); + if (result > 299 || result < 200) + goto opendir_errexit; + + /* set up the passive connection */ + portno = php_fopen_do_pasv(stream, ip, sizeof(ip), &hoststart STREAMS_CC TSRMLS_CC); + + if (!portno) { + goto opendir_errexit; + } + + php_stream_write_string(stream, "NLST "); + if (resource->path != NULL) { + php_stream_write_string(stream, resource->path); + } else { + php_stream_write_string(stream, "/"); + } + php_stream_write_string(stream, "\r\n"); + + /* open the data channel */ + if (hoststart == NULL) { + hoststart = resource->host; + } + datastream = php_stream_sock_open_host(hoststart, portno, SOCK_STREAM, 0, 0); + if (datastream == NULL) { + goto opendir_errexit; + } + + result = GET_FTP_RESULT(stream); + if (result != 150 && result != 125) { + /* Could not retrieve or send the file + * this data will only be sent to us after connection on the data port was initiated. + */ + php_stream_close(datastream); + datastream = NULL; + goto opendir_errexit; + } + + /* close control connection if not in ssl mode */ + if (!use_ssl) { + php_stream_write_string(stream, "QUIT\r\n"); + php_stream_close(stream); + stream = NULL; + } + + php_stream_context_set(datastream, context); + + if (use_ssl_on_data && (php_stream_xport_crypto_setup(stream, + STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL TSRMLS_CC) < 0 || + php_stream_xport_crypto_enable(stream, 1 TSRMLS_CC) < 0)) { + + php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unable to activate SSL mode"); + php_stream_close(datastream); + datastream = NULL; + goto opendir_errexit; + } + + /* remember control stream */ + datastream->wrapperdata = (zval *)stream; + + php_url_free(resource); + return php_stream_alloc(&php_ftp_dirstream_ops, datastream, 0, mode); + + opendir_errexit: + php_url_free(resource); + if (stream) { + php_stream_notify_error(context, PHP_STREAM_NOTIFY_FAILURE, tmp_line, result); + php_stream_close(stream); + } + if (tmp_line[0] != '\0') + php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "FTP server reports %s", tmp_line); + return NULL; +} +/* }}} */ + +static php_stream_wrapper_ops ftp_stream_wops = { + php_stream_url_wrap_ftp, + php_stream_ftp_stream_close, /* stream_close */ + php_stream_ftp_stream_stat, + NULL, /* stat_url */ + php_stream_ftp_opendir, /* opendir */ + "FTP" +}; + +PHPAPI php_stream_wrapper php_stream_ftp_wrapper = { + &ftp_stream_wops, + NULL, + 1 /* is_url */ +}; + + /* * Local variables: * tab-width: 4 -- 2.50.1