/* +----------------------------------------------------------------------+ | Copyright (c) The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | https://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Authors: Rasmus Lerdorf | | Jim Winstead | | Hartmut Holzgraefe | | Sara Golemon | +----------------------------------------------------------------------+ */ #include "php.h" #include "php_globals.h" #include "php_network.h" #include "php_ini.h" #include #include #include #include #include #include #ifdef PHP_WIN32 #include #define O_RDONLY _O_RDONLY #include "win32/param.h" #else #include #endif #include "php_standard.h" #include #if HAVE_SYS_SOCKET_H #include #endif #ifdef PHP_WIN32 #include #else #include #include #if HAVE_ARPA_INET_H #include #endif #endif #if defined(PHP_WIN32) || defined(__riscos__) #undef AF_UNIX #endif #if defined(AF_UNIX) #include #endif #include "php_fopen_wrappers.h" #define FTPS_ENCRYPT_DATA 1 #define GET_FTP_RESULT(stream) get_ftp_result((stream), tmp_line, sizeof(tmp_line)) typedef struct _php_ftp_dirstream_data { php_stream *datastream; php_stream *controlstream; php_stream *dirstream; } php_ftp_dirstream_data; /* {{{ get_ftp_result */ static inline int get_ftp_result(php_stream *stream, char *buffer, size_t buffer_size) { buffer[0] = '\0'; /* in case read fails to read anything */ while (php_stream_gets(stream, buffer, buffer_size-1) && !(isdigit((int) buffer[0]) && isdigit((int) buffer[1]) && isdigit((int) buffer[2]) && buffer[3] == ' ')); return strtol(buffer, NULL, 10); } /* }}} */ /* {{{ php_stream_ftp_stream_stat */ static int php_stream_ftp_stream_stat(php_stream_wrapper *wrapper, php_stream *stream, php_stream_statbuf *ssb) { /* For now, we return with a failure code to prevent the underlying * file's details from being used instead. */ return -1; } /* }}} */ /* {{{ php_stream_ftp_stream_close */ static int php_stream_ftp_stream_close(php_stream_wrapper *wrapper, php_stream *stream) { php_stream *controlstream = stream->wrapperthis; int ret = 0; if (controlstream) { if (strpbrk(stream->mode, "wa+")) { char tmp_line[512]; int result; /* For write modes close data stream first to signal EOF to server */ result = GET_FTP_RESULT(controlstream); if (result != 226 && result != 250) { php_error_docref(NULL, E_WARNING, "FTP server error %d:%s", result, tmp_line); ret = EOF; } } php_stream_write_string(controlstream, "QUIT\r\n"); php_stream_close(controlstream); stream->wrapperthis = NULL; } return ret; } /* }}} */ /* {{{ php_ftp_fopen_connect */ static php_stream *php_ftp_fopen_connect(php_stream_wrapper *wrapper, const char *path, const char *mode, int options, zend_string **opened_path, php_stream_context *context, php_stream **preuseid, php_url **presource, int *puse_ssl, int *puse_ssl_on_data) { php_stream *stream = NULL, *reuseid = NULL; php_url *resource = NULL; int result, use_ssl, use_ssl_on_data = 0; char tmp_line[512]; char *transport; int transport_len; resource = php_url_parse(path); if (resource == NULL || resource->path == NULL) { if (resource && presource) { *presource = resource; } return NULL; } use_ssl = resource->scheme && (ZSTR_LEN(resource->scheme) > 3) && ZSTR_VAL(resource->scheme)[3] == 's'; /* use port 21 if one wasn't specified */ if (resource->port == 0) resource->port = 21; transport_len = (int)spprintf(&transport, 0, "tcp://%s:%d", ZSTR_VAL(resource->host), resource->port); stream = php_stream_xport_create(transport, transport_len, REPORT_ERRORS, STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT, NULL, NULL, context, NULL, NULL); efree(transport); if (stream == NULL) { result = 0; /* silence */ goto connect_errexit; } php_stream_context_set(stream, context); php_stream_notify_info(context, PHP_STREAM_NOTIFY_CONNECT, NULL, 0); /* Start talking to ftp server */ result = GET_FTP_RESULT(stream); if (result > 299 || result < 200) { php_stream_notify_error(context, PHP_STREAM_NOTIFY_FAILURE, tmp_line, result); goto connect_errexit; } if (use_ssl) { /* send the AUTH TLS request name */ php_stream_write_string(stream, "AUTH TLS\r\n"); /* get the response */ result = GET_FTP_RESULT(stream); if (result != 234) { /* AUTH TLS not supported try AUTH SSL */ php_stream_write_string(stream, "AUTH SSL\r\n"); /* get the response */ result = GET_FTP_RESULT(stream); if (result != 334) { php_stream_wrapper_log_error(wrapper, options, "Server doesn't support FTPS."); goto connect_errexit; } else { /* we must reuse the old SSL session id */ /* if we talk to an old ftpd-ssl */ reuseid = stream; } } else { /* encrypt data etc */ } } if (use_ssl) { if (php_stream_xport_crypto_setup(stream, STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL) < 0 || php_stream_xport_crypto_enable(stream, 1) < 0) { php_stream_wrapper_log_error(wrapper, options, "Unable to activate SSL mode"); php_stream_close(stream); stream = NULL; goto connect_errexit; } /* set PBSZ to 0 */ php_stream_write_string(stream, "PBSZ 0\r\n"); /* ignore the response */ result = GET_FTP_RESULT(stream); /* set data connection protection level */ #if FTPS_ENCRYPT_DATA php_stream_write_string(stream, "PROT P\r\n"); /* get the response */ result = GET_FTP_RESULT(stream); use_ssl_on_data = (result >= 200 && result<=299) || reuseid; #else php_stream_write_string(stream, "PROT C\r\n"); /* get the response */ result = GET_FTP_RESULT(stream); #endif } #define PHP_FTP_CNTRL_CHK(val, val_len, err_msg) { \ unsigned char *s = (unsigned char *) val, *e = (unsigned char *) s + val_len; \ while (s < e) { \ if (iscntrl(*s)) { \ php_stream_wrapper_log_error(wrapper, options, err_msg, val); \ goto connect_errexit; \ } \ s++; \ } \ } /* send the user name */ if (resource->user != NULL) { ZSTR_LEN(resource->user) = php_raw_url_decode(ZSTR_VAL(resource->user), ZSTR_LEN(resource->user)); PHP_FTP_CNTRL_CHK(ZSTR_VAL(resource->user), ZSTR_LEN(resource->user), "Invalid login %s") php_stream_printf(stream, "USER %s\r\n", ZSTR_VAL(resource->user)); } else { php_stream_write_string(stream, "USER anonymous\r\n"); } /* get the response */ result = GET_FTP_RESULT(stream); /* if a password is required, send it */ if (result >= 300 && result <= 399) { php_stream_notify_info(context, PHP_STREAM_NOTIFY_AUTH_REQUIRED, tmp_line, 0); if (resource->pass != NULL) { ZSTR_LEN(resource->pass) = php_raw_url_decode(ZSTR_VAL(resource->pass), ZSTR_LEN(resource->pass)); PHP_FTP_CNTRL_CHK(ZSTR_VAL(resource->pass), ZSTR_LEN(resource->pass), "Invalid password %s") php_stream_printf(stream, "PASS %s\r\n", ZSTR_VAL(resource->pass)); } else { /* if the user has configured who they are, send that as the password */ if (FG(from_address)) { php_stream_printf(stream, "PASS %s\r\n", FG(from_address)); } else { php_stream_write_string(stream, "PASS anonymous\r\n"); } } /* read the response */ result = GET_FTP_RESULT(stream); if (result > 299 || result < 200) { php_stream_notify_error(context, PHP_STREAM_NOTIFY_AUTH_RESULT, tmp_line, result); } else { php_stream_notify_info(context, PHP_STREAM_NOTIFY_AUTH_RESULT, tmp_line, result); } } 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 (resource) { php_url_free(resource); } if (stream) { php_stream_close(stream); } return NULL; } /* }}} */ /* {{{ php_fopen_do_pasv */ static unsigned short php_fopen_do_pasv(php_stream *stream, char *ip, size_t ip_size, char **phoststart) { char tmp_line[512]; int result, i; unsigned short portno; char *tpath, *ttpath, *hoststart=NULL; #ifdef HAVE_IPV6 /* 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) { #endif /* 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); #ifdef HAVE_IPV6 } 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); } #endif 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, const char *path, const char *mode, int options, zend_string **opened_path, php_stream_context *context STREAMS_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 *hoststart = NULL; int result = 0, use_ssl, use_ssl_on_data=0; php_stream *reuseid=NULL; size_t file_size = 0; zval *tmpzval; bool allow_overwrite = 0; int8_t read_write = 0; char *transport; int transport_len; zend_string *error_message = NULL; tmp_line[0] = '\0'; if (strpbrk(mode, "r+")) { read_write = 1; /* Open for reading */ } if (strpbrk(mode, "wa+")) { if (read_write) { php_stream_wrapper_log_error(wrapper, options, "FTP does not support simultaneous read/write connections"); return NULL; } if (strchr(mode, 'a')) { read_write = 3; /* Open for Appending */ } else { read_write = 2; /* Open for writing */ } } if (!read_write) { /* No mode specified? */ php_stream_wrapper_log_error(wrapper, options, "Unknown file open mode"); return NULL; } if (context && (tmpzval = php_stream_context_get_option(context, "ftp", "proxy")) != NULL) { if (read_write == 1) { /* Use http wrapper to proxy ftp request */ return php_stream_url_wrap_http(wrapper, path, mode, options, opened_path, context STREAMS_CC); } else { /* ftp proxy is read-only */ php_stream_wrapper_log_error(wrapper, options, "FTP proxy may only be used in read mode"); return NULL; } } stream = php_ftp_fopen_connect(wrapper, path, mode, options, opened_path, context, &reuseid, &resource, &use_ssl, &use_ssl_on_data); if (!stream) { goto errexit; } /* set the connection to be binary */ php_stream_write_string(stream, "TYPE I\r\n"); result = GET_FTP_RESULT(stream); if (result > 299 || result < 200) goto errexit; /* find out the size of the file (verifying it exists) */ php_stream_printf(stream, "SIZE %s\r\n", ZSTR_VAL(resource->path)); /* read the response */ result = GET_FTP_RESULT(stream); if (read_write == 1) { /* Read Mode */ char *sizestr; /* when reading file, it must exist */ if (result > 299 || result < 200) { errno = ENOENT; goto errexit; } sizestr = strchr(tmp_line, ' '); if (sizestr) { sizestr++; file_size = atoi(sizestr); php_stream_notify_file_size(context, file_size, tmp_line, result); } } else if (read_write == 2) { /* when writing file (but not appending), it must NOT exist, unless a context option exists which allows it */ if (context && (tmpzval = php_stream_context_get_option(context, "ftp", "overwrite")) != NULL) { allow_overwrite = zend_is_true(tmpzval); } if (result <= 299 && result >= 200) { if (allow_overwrite) { /* Context permits overwriting file, so we just delete whatever's there in preparation */ php_stream_printf(stream, "DELE %s\r\n", ZSTR_VAL(resource->path)); result = GET_FTP_RESULT(stream); if (result >= 300 || result <= 199) { goto errexit; } } else { php_stream_wrapper_log_error(wrapper, options, "Remote file already exists and overwrite context option not specified"); errno = EEXIST; goto errexit; } } } /* set up the passive connection */ portno = php_fopen_do_pasv(stream, ip, sizeof(ip), &hoststart); if (!portno) { goto errexit; } /* Send RETR/STOR command */ if (read_write == 1) { /* set resume position if applicable */ if (context && (tmpzval = php_stream_context_get_option(context, "ftp", "resume_pos")) != NULL && Z_TYPE_P(tmpzval) == IS_LONG && Z_LVAL_P(tmpzval) > 0) { php_stream_printf(stream, "REST " ZEND_LONG_FMT "\r\n", Z_LVAL_P(tmpzval)); result = GET_FTP_RESULT(stream); if (result < 300 || result > 399) { php_stream_wrapper_log_error(wrapper, options, "Unable to resume from offset " ZEND_LONG_FMT, Z_LVAL_P(tmpzval)); goto errexit; } } /* retrieve file */ memcpy(tmp_line, "RETR", sizeof("RETR")); } else if (read_write == 2) { /* Write new file */ memcpy(tmp_line, "STOR", sizeof("STOR")); } else { /* Append */ memcpy(tmp_line, "APPE", sizeof("APPE")); } php_stream_printf(stream, "%s %s\r\n", tmp_line, (resource->path != NULL ? ZSTR_VAL(resource->path) : "/")); /* open the data channel */ if (hoststart == NULL) { hoststart = ZSTR_VAL(resource->host); } transport_len = (int)spprintf(&transport, 0, "tcp://%s:%d", hoststart, portno); datastream = php_stream_xport_create(transport, transport_len, REPORT_ERRORS, STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT, NULL, NULL, context, &error_message, NULL); efree(transport); if (datastream == NULL) { tmp_line[0]='\0'; goto 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 errexit; } php_stream_context_set(datastream, context); php_stream_notify_progress_init(context, 0, file_size); if (use_ssl_on_data && (php_stream_xport_crypto_setup(datastream, STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL) < 0 || php_stream_xport_crypto_enable(datastream, 1) < 0)) { php_stream_wrapper_log_error(wrapper, options, "Unable to activate SSL mode"); php_stream_close(datastream); datastream = NULL; tmp_line[0]='\0'; goto errexit; } /* remember control stream */ datastream->wrapperthis = stream; php_url_free(resource); return datastream; errexit: if (resource) { 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, "FTP server reports %s", tmp_line); if (error_message) { php_stream_wrapper_log_error(wrapper, options, "Failed to set up data channel: %s", ZSTR_VAL(error_message)); zend_string_release(error_message); } return NULL; } /* }}} */ /* {{{ php_ftp_dirsteam_read */ static ssize_t php_ftp_dirstream_read(php_stream *stream, char *buf, size_t count) { php_stream_dirent *ent = (php_stream_dirent *)buf; php_stream *innerstream; size_t tmp_len; zend_string *basename; innerstream = ((php_ftp_dirstream_data *)stream->abstract)->datastream; if (count != sizeof(php_stream_dirent)) { return -1; } if (php_stream_eof(innerstream)) { return 0; } if (!php_stream_get_line(innerstream, ent->d_name, sizeof(ent->d_name), &tmp_len)) { return -1; } basename = php_basename(ent->d_name, tmp_len, NULL, 0); tmp_len = MIN(sizeof(ent->d_name), ZSTR_LEN(basename) - 1); memcpy(ent->d_name, ZSTR_VAL(basename), tmp_len); ent->d_name[tmp_len - 1] = '\0'; zend_string_release_ex(basename, 0); /* Trim off trailing whitespace characters */ while (tmp_len > 0 && (ent->d_name[tmp_len - 1] == '\n' || ent->d_name[tmp_len - 1] == '\r' || ent->d_name[tmp_len - 1] == '\t' || ent->d_name[tmp_len - 1] == ' ')) { ent->d_name[--tmp_len] = '\0'; } return sizeof(php_stream_dirent); } /* }}} */ /* {{{ php_ftp_dirstream_close */ static int php_ftp_dirstream_close(php_stream *stream, int close_handle) { php_ftp_dirstream_data *data = stream->abstract; /* close control connection */ if (data->controlstream) { php_stream_close(data->controlstream); data->controlstream = NULL; } /* close data connection */ php_stream_close(data->datastream); data->datastream = NULL; efree(data); 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 const 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, const char *path, const char *mode, int options, zend_string **opened_path, php_stream_context *context STREAMS_DC) { php_stream *stream, *reuseid, *datastream = NULL; php_ftp_dirstream_data *dirsdata; php_url *resource = NULL; int result = 0, use_ssl, use_ssl_on_data = 0; char *hoststart = NULL, tmp_line[512]; char ip[sizeof("123.123.123.123")]; unsigned short portno; tmp_line[0] = '\0'; stream = php_ftp_fopen_connect(wrapper, path, mode, options, opened_path, context, &reuseid, &resource, &use_ssl, &use_ssl_on_data); if (!stream) { goto opendir_errexit; } /* 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; // tmp_line isn't relevant after the php_fopen_do_pasv(). tmp_line[0] = '\0'; /* set up the passive connection */ portno = php_fopen_do_pasv(stream, ip, sizeof(ip), &hoststart); if (!portno) { goto opendir_errexit; } /* open the data channel */ if (hoststart == NULL) { hoststart = ZSTR_VAL(resource->host); } datastream = php_stream_sock_open_host(hoststart, portno, SOCK_STREAM, 0, 0); if (datastream == NULL) { goto opendir_errexit; } php_stream_printf(stream, "NLST %s\r\n", (resource->path != NULL ? ZSTR_VAL(resource->path) : "/")); 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; } php_stream_context_set(datastream, context); if (use_ssl_on_data && (php_stream_xport_crypto_setup(datastream, STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL) < 0 || php_stream_xport_crypto_enable(datastream, 1) < 0)) { php_stream_wrapper_log_error(wrapper, options, "Unable to activate SSL mode"); php_stream_close(datastream); datastream = NULL; goto opendir_errexit; } php_url_free(resource); dirsdata = emalloc(sizeof *dirsdata); dirsdata->datastream = datastream; dirsdata->controlstream = stream; dirsdata->dirstream = php_stream_alloc(&php_ftp_dirstream_ops, dirsdata, 0, mode); return dirsdata->dirstream; opendir_errexit: if (resource) { 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, "FTP server reports %s", tmp_line); } return NULL; } /* }}} */ /* {{{ php_stream_ftp_url_stat */ static int php_stream_ftp_url_stat(php_stream_wrapper *wrapper, const char *url, int flags, php_stream_statbuf *ssb, php_stream_context *context) { php_stream *stream = NULL; php_url *resource = NULL; int result; char tmp_line[512]; /* If ssb is NULL then someone is misbehaving */ if (!ssb) return -1; stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, context, NULL, &resource, NULL, NULL); if (!stream) { goto stat_errexit; } ssb->sb.st_mode = 0644; /* FTP won't give us a valid mode, so approximate one based on being readable */ php_stream_printf(stream, "CWD %s\r\n", (resource->path != NULL ? ZSTR_VAL(resource->path) : "/")); /* If we can CWD to it, it's a directory (maybe a link, but we can't tell) */ result = GET_FTP_RESULT(stream); if (result < 200 || result > 299) { ssb->sb.st_mode |= S_IFREG; } else { ssb->sb.st_mode |= S_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH; } php_stream_write_string(stream, "TYPE I\r\n"); /* we need this since some servers refuse to accept SIZE command in ASCII mode */ result = GET_FTP_RESULT(stream); if(result < 200 || result > 299) { goto stat_errexit; } php_stream_printf(stream, "SIZE %s\r\n", (resource->path != NULL ? ZSTR_VAL(resource->path) : "/")); result = GET_FTP_RESULT(stream); if (result < 200 || result > 299) { /* Failure either means it doesn't exist or it's a directory and this server fails on listing directory sizes */ if (ssb->sb.st_mode & S_IFDIR) { ssb->sb.st_size = 0; } else { goto stat_errexit; } } else { ssb->sb.st_size = atoi(tmp_line + 4); } php_stream_printf(stream, "MDTM %s\r\n", (resource->path != NULL ? ZSTR_VAL(resource->path) : "/")); result = GET_FTP_RESULT(stream); if (result == 213) { char *p = tmp_line + 4; int n; struct tm tm, tmbuf, *gmt; time_t stamp; while ((size_t)(p - tmp_line) < sizeof(tmp_line) && !isdigit(*p)) { p++; } if ((size_t)(p - tmp_line) > sizeof(tmp_line)) { goto mdtm_error; } n = sscanf(p, "%4d%2d%2d%2d%2d%2d", &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec); if (n != 6) { goto mdtm_error; } tm.tm_year -= 1900; tm.tm_mon--; tm.tm_isdst = -1; /* figure out the GMT offset */ stamp = time(NULL); gmt = php_gmtime_r(&stamp, &tmbuf); if (!gmt) { goto mdtm_error; } gmt->tm_isdst = -1; /* apply the GMT offset */ tm.tm_sec += (long)(stamp - mktime(gmt)); tm.tm_isdst = gmt->tm_isdst; ssb->sb.st_mtime = mktime(&tm); } else { /* error or unsupported command */ mdtm_error: ssb->sb.st_mtime = -1; } ssb->sb.st_ino = 0; /* Unknown values */ ssb->sb.st_dev = 0; ssb->sb.st_uid = 0; ssb->sb.st_gid = 0; ssb->sb.st_atime = -1; ssb->sb.st_ctime = -1; ssb->sb.st_nlink = 1; ssb->sb.st_rdev = -1; #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE ssb->sb.st_blksize = 4096; /* Guess since FTP won't expose this information */ #ifdef HAVE_STRUCT_STAT_ST_BLOCKS ssb->sb.st_blocks = (int)((4095 + ssb->sb.st_size) / ssb->sb.st_blksize); /* emulate ceil */ #endif #endif php_stream_close(stream); php_url_free(resource); return 0; stat_errexit: if (resource) { php_url_free(resource); } if (stream) { php_stream_close(stream); } return -1; } /* }}} */ /* {{{ php_stream_ftp_unlink */ static int php_stream_ftp_unlink(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context) { php_stream *stream = NULL; php_url *resource = NULL; int result; char tmp_line[512]; stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, context, NULL, &resource, NULL, NULL); if (!stream) { if (options & REPORT_ERRORS) { php_error_docref(NULL, E_WARNING, "Unable to connect to %s", url); } goto unlink_errexit; } if (resource->path == NULL) { if (options & REPORT_ERRORS) { php_error_docref(NULL, E_WARNING, "Invalid path provided in %s", url); } goto unlink_errexit; } /* Attempt to delete the file */ php_stream_printf(stream, "DELE %s\r\n", (resource->path != NULL ? ZSTR_VAL(resource->path) : "/")); result = GET_FTP_RESULT(stream); if (result < 200 || result > 299) { if (options & REPORT_ERRORS) { php_error_docref(NULL, E_WARNING, "Error Deleting file: %s", tmp_line); } goto unlink_errexit; } php_url_free(resource); php_stream_close(stream); return 1; unlink_errexit: if (resource) { php_url_free(resource); } if (stream) { php_stream_close(stream); } return 0; } /* }}} */ /* {{{ php_stream_ftp_rename */ static int php_stream_ftp_rename(php_stream_wrapper *wrapper, const char *url_from, const char *url_to, int options, php_stream_context *context) { php_stream *stream = NULL; php_url *resource_from = NULL, *resource_to = NULL; int result; char tmp_line[512]; resource_from = php_url_parse(url_from); resource_to = php_url_parse(url_to); /* Must be same scheme (ftp/ftp or ftps/ftps), same host, and same port (or a 21/0 0/21 combination which is also "same") Also require paths to/from */ if (!resource_from || !resource_to || !resource_from->scheme || !resource_to->scheme || !zend_string_equals(resource_from->scheme, resource_to->scheme) || !resource_from->host || !resource_to->host || !zend_string_equals(resource_from->host, resource_to->host) || (resource_from->port != resource_to->port && resource_from->port * resource_to->port != 0 && resource_from->port + resource_to->port != 21) || !resource_from->path || !resource_to->path) { goto rename_errexit; } stream = php_ftp_fopen_connect(wrapper, url_from, "r", 0, NULL, context, NULL, NULL, NULL, NULL); if (!stream) { if (options & REPORT_ERRORS) { php_error_docref(NULL, E_WARNING, "Unable to connect to %s", ZSTR_VAL(resource_from->host)); } goto rename_errexit; } /* Rename FROM */ php_stream_printf(stream, "RNFR %s\r\n", (resource_from->path != NULL ? ZSTR_VAL(resource_from->path) : "/")); result = GET_FTP_RESULT(stream); if (result < 300 || result > 399) { if (options & REPORT_ERRORS) { php_error_docref(NULL, E_WARNING, "Error Renaming file: %s", tmp_line); } goto rename_errexit; } /* Rename TO */ php_stream_printf(stream, "RNTO %s\r\n", (resource_to->path != NULL ? ZSTR_VAL(resource_to->path) : "/")); result = GET_FTP_RESULT(stream); if (result < 200 || result > 299) { if (options & REPORT_ERRORS) { php_error_docref(NULL, E_WARNING, "Error Renaming file: %s", tmp_line); } goto rename_errexit; } php_url_free(resource_from); php_url_free(resource_to); php_stream_close(stream); return 1; rename_errexit: if (resource_from) { php_url_free(resource_from); } if (resource_to) { php_url_free(resource_to); } if (stream) { php_stream_close(stream); } return 0; } /* }}} */ /* {{{ php_stream_ftp_mkdir */ static int php_stream_ftp_mkdir(php_stream_wrapper *wrapper, const char *url, int mode, int options, php_stream_context *context) { php_stream *stream = NULL; php_url *resource = NULL; int result, recursive = options & PHP_STREAM_MKDIR_RECURSIVE; char tmp_line[512]; stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, context, NULL, &resource, NULL, NULL); if (!stream) { if (options & REPORT_ERRORS) { php_error_docref(NULL, E_WARNING, "Unable to connect to %s", url); } goto mkdir_errexit; } if (resource->path == NULL) { if (options & REPORT_ERRORS) { php_error_docref(NULL, E_WARNING, "Invalid path provided in %s", url); } goto mkdir_errexit; } if (!recursive) { php_stream_printf(stream, "MKD %s\r\n", ZSTR_VAL(resource->path)); result = GET_FTP_RESULT(stream); } else { /* we look for directory separator from the end of string, thus hopefully reducing our work load */ char *p, *e, *buf; buf = estrndup(ZSTR_VAL(resource->path), ZSTR_LEN(resource->path)); e = buf + ZSTR_LEN(resource->path); /* find a top level directory we need to create */ while ((p = strrchr(buf, '/'))) { *p = '\0'; php_stream_printf(stream, "CWD %s\r\n", strlen(buf) ? buf : "/"); result = GET_FTP_RESULT(stream); if (result >= 200 && result <= 299) { *p = '/'; break; } } php_stream_printf(stream, "MKD %s\r\n", strlen(buf) ? buf : "/"); result = GET_FTP_RESULT(stream); if (result >= 200 && result <= 299) { if (!p) { p = buf; } /* create any needed directories if the creation of the 1st directory worked */ while (p != e) { if (*p == '\0' && *(p + 1) != '\0') { *p = '/'; php_stream_printf(stream, "MKD %s\r\n", buf); result = GET_FTP_RESULT(stream); if (result < 200 || result > 299) { if (options & REPORT_ERRORS) { php_error_docref(NULL, E_WARNING, "%s", tmp_line); } break; } } ++p; } } efree(buf); } php_url_free(resource); php_stream_close(stream); if (result < 200 || result > 299) { /* Failure */ return 0; } return 1; mkdir_errexit: if (resource) { php_url_free(resource); } if (stream) { php_stream_close(stream); } return 0; } /* }}} */ /* {{{ php_stream_ftp_rmdir */ static int php_stream_ftp_rmdir(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context) { php_stream *stream = NULL; php_url *resource = NULL; int result; char tmp_line[512]; stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, context, NULL, &resource, NULL, NULL); if (!stream) { if (options & REPORT_ERRORS) { php_error_docref(NULL, E_WARNING, "Unable to connect to %s", url); } goto rmdir_errexit; } if (resource->path == NULL) { if (options & REPORT_ERRORS) { php_error_docref(NULL, E_WARNING, "Invalid path provided in %s", url); } goto rmdir_errexit; } php_stream_printf(stream, "RMD %s\r\n", ZSTR_VAL(resource->path)); result = GET_FTP_RESULT(stream); if (result < 200 || result > 299) { if (options & REPORT_ERRORS) { php_error_docref(NULL, E_WARNING, "%s", tmp_line); } goto rmdir_errexit; } php_url_free(resource); php_stream_close(stream); return 1; rmdir_errexit: if (resource) { php_url_free(resource); } if (stream) { php_stream_close(stream); } return 0; } /* }}} */ static const php_stream_wrapper_ops ftp_stream_wops = { php_stream_url_wrap_ftp, php_stream_ftp_stream_close, /* stream_close */ php_stream_ftp_stream_stat, php_stream_ftp_url_stat, /* stat_url */ php_stream_ftp_opendir, /* opendir */ "ftp", php_stream_ftp_unlink, /* unlink */ php_stream_ftp_rename, /* rename */ php_stream_ftp_mkdir, /* mkdir */ php_stream_ftp_rmdir, /* rmdir */ NULL }; PHPAPI const php_stream_wrapper php_stream_ftp_wrapper = { &ftp_stream_wops, NULL, 1 /* is_url */ };