mirror of
https://github.com/php/php-src.git
synced 2025-08-19 17:04:47 +02:00

The $Id$ keywords were used in Subversion where they can be substituted with filename, last revision number change, last changed date, and last user who changed it. In Git this functionality is different and can be done with Git attribute ident. These need to be defined manually for each file in the .gitattributes file and are afterwards replaced with 40-character hexadecimal blob object name which is based only on the particular file contents. This patch simplifies handling of $Id$ keywords by removing them since they are not used anymore.
2261 lines
43 KiB
C
2261 lines
43 KiB
C
/*
|
|
+----------------------------------------------------------------------+
|
|
| PHP Version 7 |
|
|
+----------------------------------------------------------------------+
|
|
| Copyright (c) 1997-2018 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: |
|
|
| http://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: Andrew Skalski <askalski@chek.com> |
|
|
| Stefan Esser <sesser@php.net> (resume functions) |
|
|
+----------------------------------------------------------------------+
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "php.h"
|
|
|
|
#if HAVE_FTP
|
|
|
|
#include <stdio.h>
|
|
#include <ctype.h>
|
|
#include <stdlib.h>
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
#include <fcntl.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#ifdef PHP_WIN32
|
|
#include <winsock2.h>
|
|
#else
|
|
#ifdef HAVE_SYS_TYPES_H
|
|
#include <sys/types.h>
|
|
#endif
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
#include <netdb.h>
|
|
#endif
|
|
#include <errno.h>
|
|
|
|
#if HAVE_SYS_TIME_H
|
|
#include <sys/time.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_SYS_SELECT_H
|
|
#include <sys/select.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_FTP_SSL
|
|
#include <openssl/ssl.h>
|
|
#endif
|
|
|
|
#include "ftp.h"
|
|
#include "ext/standard/fsock.h"
|
|
|
|
/* sends an ftp command, returns true on success, false on error.
|
|
* it sends the string "cmd args\r\n" if args is non-null, or
|
|
* "cmd\r\n" if args is null
|
|
*/
|
|
static int ftp_putcmd( ftpbuf_t *ftp,
|
|
const char *cmd,
|
|
const size_t cmd_len,
|
|
const char *args,
|
|
const size_t args_len);
|
|
|
|
/* wrapper around send/recv to handle timeouts */
|
|
static int my_send(ftpbuf_t *ftp, php_socket_t s, void *buf, size_t len);
|
|
static int my_recv(ftpbuf_t *ftp, php_socket_t s, void *buf, size_t len);
|
|
static int my_accept(ftpbuf_t *ftp, php_socket_t s, struct sockaddr *addr, socklen_t *addrlen);
|
|
|
|
/* reads a line the socket , returns true on success, false on error */
|
|
static int ftp_readline(ftpbuf_t *ftp);
|
|
|
|
/* reads an ftp response, returns true on success, false on error */
|
|
static int ftp_getresp(ftpbuf_t *ftp);
|
|
|
|
/* sets the ftp transfer type */
|
|
static int ftp_type(ftpbuf_t *ftp, ftptype_t type);
|
|
|
|
/* opens up a data stream */
|
|
static databuf_t* ftp_getdata(ftpbuf_t *ftp);
|
|
|
|
/* accepts the data connection, returns updated data buffer */
|
|
static databuf_t* data_accept(databuf_t *data, ftpbuf_t *ftp);
|
|
|
|
/* closes the data connection, returns NULL */
|
|
static databuf_t* data_close(ftpbuf_t *ftp, databuf_t *data);
|
|
|
|
/* generic file lister */
|
|
static char** ftp_genlist(ftpbuf_t *ftp, const char *cmd, const size_t cmd_len, const char *path, const size_t path_len);
|
|
|
|
/* IP and port conversion box */
|
|
union ipbox {
|
|
struct in_addr ia[2];
|
|
unsigned short s[4];
|
|
unsigned char c[8];
|
|
};
|
|
|
|
/* {{{ ftp_open
|
|
*/
|
|
ftpbuf_t*
|
|
ftp_open(const char *host, short port, zend_long timeout_sec)
|
|
{
|
|
ftpbuf_t *ftp;
|
|
socklen_t size;
|
|
struct timeval tv;
|
|
|
|
|
|
/* alloc the ftp structure */
|
|
ftp = ecalloc(1, sizeof(*ftp));
|
|
|
|
tv.tv_sec = timeout_sec;
|
|
tv.tv_usec = 0;
|
|
|
|
ftp->fd = php_network_connect_socket_to_host(host,
|
|
(unsigned short) (port ? port : 21), SOCK_STREAM,
|
|
0, &tv, NULL, NULL, NULL, 0, STREAM_SOCKOP_NONE);
|
|
if (ftp->fd == -1) {
|
|
goto bail;
|
|
}
|
|
|
|
/* Default Settings */
|
|
ftp->timeout_sec = timeout_sec;
|
|
ftp->nb = 0;
|
|
|
|
size = sizeof(ftp->localaddr);
|
|
memset(&ftp->localaddr, 0, size);
|
|
if (getsockname(ftp->fd, (struct sockaddr*) &ftp->localaddr, &size) != 0) {
|
|
php_error_docref(NULL, E_WARNING, "getsockname failed: %s (%d)", strerror(errno), errno);
|
|
goto bail;
|
|
}
|
|
|
|
if (!ftp_getresp(ftp) || ftp->resp != 220) {
|
|
goto bail;
|
|
}
|
|
|
|
return ftp;
|
|
|
|
bail:
|
|
if (ftp->fd != -1) {
|
|
closesocket(ftp->fd);
|
|
}
|
|
efree(ftp);
|
|
return NULL;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ ftp_close
|
|
*/
|
|
ftpbuf_t*
|
|
ftp_close(ftpbuf_t *ftp)
|
|
{
|
|
if (ftp == NULL) {
|
|
return NULL;
|
|
}
|
|
if (ftp->data) {
|
|
data_close(ftp, ftp->data);
|
|
}
|
|
if (ftp->stream && ftp->closestream) {
|
|
php_stream_close(ftp->stream);
|
|
}
|
|
if (ftp->fd != -1) {
|
|
#ifdef HAVE_FTP_SSL
|
|
if (ftp->ssl_active) {
|
|
SSL_shutdown(ftp->ssl_handle);
|
|
SSL_free(ftp->ssl_handle);
|
|
}
|
|
#endif
|
|
closesocket(ftp->fd);
|
|
}
|
|
ftp_gc(ftp);
|
|
efree(ftp);
|
|
return NULL;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ ftp_gc
|
|
*/
|
|
void
|
|
ftp_gc(ftpbuf_t *ftp)
|
|
{
|
|
if (ftp == NULL) {
|
|
return;
|
|
}
|
|
if (ftp->pwd) {
|
|
efree(ftp->pwd);
|
|
ftp->pwd = NULL;
|
|
}
|
|
if (ftp->syst) {
|
|
efree(ftp->syst);
|
|
ftp->syst = NULL;
|
|
}
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ ftp_quit
|
|
*/
|
|
int
|
|
ftp_quit(ftpbuf_t *ftp)
|
|
{
|
|
if (ftp == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
if (!ftp_putcmd(ftp, "QUIT", sizeof("QUIT")-1, NULL, (size_t) 0)) {
|
|
return 0;
|
|
}
|
|
if (!ftp_getresp(ftp) || ftp->resp != 221) {
|
|
return 0;
|
|
}
|
|
|
|
if (ftp->pwd) {
|
|
efree(ftp->pwd);
|
|
ftp->pwd = NULL;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ ftp_login
|
|
*/
|
|
int
|
|
ftp_login(ftpbuf_t *ftp, const char *user, const size_t user_len, const char *pass, const size_t pass_len)
|
|
{
|
|
#ifdef HAVE_FTP_SSL
|
|
SSL_CTX *ctx = NULL;
|
|
long ssl_ctx_options = SSL_OP_ALL;
|
|
int err, res;
|
|
zend_bool retry;
|
|
#endif
|
|
if (ftp == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
#ifdef HAVE_FTP_SSL
|
|
if (ftp->use_ssl && !ftp->ssl_active) {
|
|
if (!ftp_putcmd(ftp, "AUTH", sizeof("AUTH")-1, "TLS", sizeof("TLS")-1)) {
|
|
return 0;
|
|
}
|
|
if (!ftp_getresp(ftp)) {
|
|
return 0;
|
|
}
|
|
|
|
if (ftp->resp != 234) {
|
|
if (!ftp_putcmd(ftp, "AUTH", sizeof("AUTH")-1, "SSL", sizeof("SSL")-1)) {
|
|
return 0;
|
|
}
|
|
if (!ftp_getresp(ftp)) {
|
|
return 0;
|
|
}
|
|
|
|
if (ftp->resp != 334) {
|
|
return 0;
|
|
} else {
|
|
ftp->old_ssl = 1;
|
|
ftp->use_ssl_for_data = 1;
|
|
}
|
|
}
|
|
|
|
ctx = SSL_CTX_new(SSLv23_client_method());
|
|
if (ctx == NULL) {
|
|
php_error_docref(NULL, E_WARNING, "failed to create the SSL context");
|
|
return 0;
|
|
}
|
|
|
|
#if OPENSSL_VERSION_NUMBER >= 0x0090605fL
|
|
ssl_ctx_options &= ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS;
|
|
#endif
|
|
SSL_CTX_set_options(ctx, ssl_ctx_options);
|
|
|
|
/* allow SSL to re-use sessions */
|
|
SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_BOTH);
|
|
|
|
ftp->ssl_handle = SSL_new(ctx);
|
|
if (ftp->ssl_handle == NULL) {
|
|
php_error_docref(NULL, E_WARNING, "failed to create the SSL handle");
|
|
SSL_CTX_free(ctx);
|
|
return 0;
|
|
}
|
|
|
|
SSL_set_fd(ftp->ssl_handle, ftp->fd);
|
|
|
|
do {
|
|
res = SSL_connect(ftp->ssl_handle);
|
|
err = SSL_get_error(ftp->ssl_handle, res);
|
|
|
|
/* TODO check if handling other error codes would make sense */
|
|
switch (err) {
|
|
case SSL_ERROR_NONE:
|
|
retry = 0;
|
|
break;
|
|
|
|
case SSL_ERROR_ZERO_RETURN:
|
|
retry = 0;
|
|
SSL_shutdown(ftp->ssl_handle);
|
|
break;
|
|
|
|
case SSL_ERROR_WANT_READ:
|
|
case SSL_ERROR_WANT_WRITE: {
|
|
php_pollfd p;
|
|
int i;
|
|
|
|
p.fd = ftp->fd;
|
|
p.events = (err == SSL_ERROR_WANT_READ) ? (POLLIN|POLLPRI) : POLLOUT;
|
|
p.revents = 0;
|
|
|
|
i = php_poll2(&p, 1, 300);
|
|
|
|
retry = i > 0;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
php_error_docref(NULL, E_WARNING, "SSL/TLS handshake failed");
|
|
SSL_shutdown(ftp->ssl_handle);
|
|
SSL_free(ftp->ssl_handle);
|
|
return 0;
|
|
}
|
|
} while (retry);
|
|
|
|
ftp->ssl_active = 1;
|
|
|
|
if (!ftp->old_ssl) {
|
|
|
|
/* set protection buffersize to zero */
|
|
if (!ftp_putcmd(ftp, "PBSZ", sizeof("PBSZ")-1, "0", sizeof("0")-1)) {
|
|
return 0;
|
|
}
|
|
if (!ftp_getresp(ftp)) {
|
|
return 0;
|
|
}
|
|
|
|
/* enable data conn encryption */
|
|
if (!ftp_putcmd(ftp, "PROT", sizeof("PROT")-1, "P", sizeof("P")-1)) {
|
|
return 0;
|
|
}
|
|
if (!ftp_getresp(ftp)) {
|
|
return 0;
|
|
}
|
|
|
|
ftp->use_ssl_for_data = (ftp->resp >= 200 && ftp->resp <=299);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (!ftp_putcmd(ftp, "USER", sizeof("USER")-1, user, user_len)) {
|
|
return 0;
|
|
}
|
|
if (!ftp_getresp(ftp)) {
|
|
return 0;
|
|
}
|
|
if (ftp->resp == 230) {
|
|
return 1;
|
|
}
|
|
if (ftp->resp != 331) {
|
|
return 0;
|
|
}
|
|
if (!ftp_putcmd(ftp, "PASS", sizeof("PASS")-1, pass, pass_len)) {
|
|
return 0;
|
|
}
|
|
if (!ftp_getresp(ftp)) {
|
|
return 0;
|
|
}
|
|
return (ftp->resp == 230);
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ ftp_reinit
|
|
*/
|
|
int
|
|
ftp_reinit(ftpbuf_t *ftp)
|
|
{
|
|
if (ftp == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
ftp_gc(ftp);
|
|
|
|
ftp->nb = 0;
|
|
|
|
if (!ftp_putcmd(ftp, "REIN", sizeof("REIN")-1, NULL, (size_t) 0)) {
|
|
return 0;
|
|
}
|
|
if (!ftp_getresp(ftp) || ftp->resp != 220) {
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ ftp_syst
|
|
*/
|
|
const char*
|
|
ftp_syst(ftpbuf_t *ftp)
|
|
{
|
|
char *syst, *end;
|
|
|
|
if (ftp == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
/* default to cached value */
|
|
if (ftp->syst) {
|
|
return ftp->syst;
|
|
}
|
|
if (!ftp_putcmd(ftp, "SYST", sizeof("SYST")-1, NULL, (size_t) 0)) {
|
|
return NULL;
|
|
}
|
|
if (!ftp_getresp(ftp) || ftp->resp != 215) {
|
|
return NULL;
|
|
}
|
|
syst = ftp->inbuf;
|
|
while (*syst == ' ') {
|
|
syst++;
|
|
}
|
|
if ((end = strchr(syst, ' '))) {
|
|
*end = 0;
|
|
}
|
|
ftp->syst = estrdup(syst);
|
|
if (end) {
|
|
*end = ' ';
|
|
}
|
|
return ftp->syst;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ ftp_pwd
|
|
*/
|
|
const char*
|
|
ftp_pwd(ftpbuf_t *ftp)
|
|
{
|
|
char *pwd, *end;
|
|
|
|
if (ftp == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
/* default to cached value */
|
|
if (ftp->pwd) {
|
|
return ftp->pwd;
|
|
}
|
|
if (!ftp_putcmd(ftp, "PWD", sizeof("PWD")-1, NULL, (size_t) 0)) {
|
|
return NULL;
|
|
}
|
|
if (!ftp_getresp(ftp) || ftp->resp != 257) {
|
|
return NULL;
|
|
}
|
|
/* copy out the pwd from response */
|
|
if ((pwd = strchr(ftp->inbuf, '"')) == NULL) {
|
|
return NULL;
|
|
}
|
|
if ((end = strrchr(++pwd, '"')) == NULL) {
|
|
return NULL;
|
|
}
|
|
ftp->pwd = estrndup(pwd, end - pwd);
|
|
|
|
return ftp->pwd;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ ftp_exec
|
|
*/
|
|
int
|
|
ftp_exec(ftpbuf_t *ftp, const char *cmd, const size_t cmd_len)
|
|
{
|
|
if (ftp == NULL) {
|
|
return 0;
|
|
}
|
|
if (!ftp_putcmd(ftp, "SITE EXEC", sizeof("SITE EXEC")-1, cmd, cmd_len)) {
|
|
return 0;
|
|
}
|
|
if (!ftp_getresp(ftp) || ftp->resp != 200) {
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ ftp_raw
|
|
*/
|
|
void
|
|
ftp_raw(ftpbuf_t *ftp, const char *cmd, const size_t cmd_len, zval *return_value)
|
|
{
|
|
if (ftp == NULL || cmd == NULL) {
|
|
RETURN_NULL();
|
|
}
|
|
if (!ftp_putcmd(ftp, cmd, cmd_len, NULL, (size_t) 0)) {
|
|
RETURN_NULL();
|
|
}
|
|
array_init(return_value);
|
|
while (ftp_readline(ftp)) {
|
|
add_next_index_string(return_value, ftp->inbuf);
|
|
if (isdigit(ftp->inbuf[0]) && isdigit(ftp->inbuf[1]) && isdigit(ftp->inbuf[2]) && ftp->inbuf[3] == ' ') {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ ftp_chdir
|
|
*/
|
|
int
|
|
ftp_chdir(ftpbuf_t *ftp, const char *dir, const size_t dir_len)
|
|
{
|
|
if (ftp == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
if (ftp->pwd) {
|
|
efree(ftp->pwd);
|
|
ftp->pwd = NULL;
|
|
}
|
|
|
|
if (!ftp_putcmd(ftp, "CWD", sizeof("CWD")-1, dir, dir_len)) {
|
|
return 0;
|
|
}
|
|
if (!ftp_getresp(ftp) || ftp->resp != 250) {
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ ftp_cdup
|
|
*/
|
|
int
|
|
ftp_cdup(ftpbuf_t *ftp)
|
|
{
|
|
if (ftp == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
if (ftp->pwd) {
|
|
efree(ftp->pwd);
|
|
ftp->pwd = NULL;
|
|
}
|
|
|
|
if (!ftp_putcmd(ftp, "CDUP", sizeof("CDUP")-1, NULL, (size_t) 0)) {
|
|
return 0;
|
|
}
|
|
if (!ftp_getresp(ftp) || ftp->resp != 250) {
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ ftp_mkdir
|
|
*/
|
|
zend_string*
|
|
ftp_mkdir(ftpbuf_t *ftp, const char *dir, const size_t dir_len)
|
|
{
|
|
char *mkd, *end;
|
|
zend_string *ret;
|
|
|
|
if (ftp == NULL) {
|
|
return NULL;
|
|
}
|
|
if (!ftp_putcmd(ftp, "MKD", sizeof("MKD")-1, dir, dir_len)) {
|
|
return NULL;
|
|
}
|
|
if (!ftp_getresp(ftp) || ftp->resp != 257) {
|
|
return NULL;
|
|
}
|
|
/* copy out the dir from response */
|
|
if ((mkd = strchr(ftp->inbuf, '"')) == NULL) {
|
|
return zend_string_init(dir, dir_len, 0);
|
|
}
|
|
if ((end = strrchr(++mkd, '"')) == NULL) {
|
|
return NULL;
|
|
}
|
|
*end = 0;
|
|
ret = zend_string_init(mkd, end - mkd, 0);
|
|
*end = '"';
|
|
|
|
return ret;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ ftp_rmdir
|
|
*/
|
|
int
|
|
ftp_rmdir(ftpbuf_t *ftp, const char *dir, const size_t dir_len)
|
|
{
|
|
if (ftp == NULL) {
|
|
return 0;
|
|
}
|
|
if (!ftp_putcmd(ftp, "RMD", sizeof("RMD")-1, dir, dir_len)) {
|
|
return 0;
|
|
}
|
|
if (!ftp_getresp(ftp) || ftp->resp != 250) {
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ ftp_chmod
|
|
*/
|
|
int
|
|
ftp_chmod(ftpbuf_t *ftp, const int mode, const char *filename, const int filename_len)
|
|
{
|
|
char *buffer;
|
|
size_t buffer_len;
|
|
|
|
if (ftp == NULL || filename_len <= 0) {
|
|
return 0;
|
|
}
|
|
|
|
buffer_len = spprintf(&buffer, 0, "CHMOD %o %s", mode, filename);
|
|
|
|
if (!buffer) {
|
|
return 0;
|
|
}
|
|
|
|
if (!ftp_putcmd(ftp, "SITE", sizeof("SITE")-1, buffer, buffer_len)) {
|
|
efree(buffer);
|
|
return 0;
|
|
}
|
|
|
|
efree(buffer);
|
|
|
|
if (!ftp_getresp(ftp) || ftp->resp != 200) {
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ ftp_alloc
|
|
*/
|
|
int
|
|
ftp_alloc(ftpbuf_t *ftp, const zend_long size, zend_string **response)
|
|
{
|
|
char buffer[64];
|
|
int buffer_len;
|
|
|
|
if (ftp == NULL || size <= 0) {
|
|
return 0;
|
|
}
|
|
|
|
buffer_len = snprintf(buffer, sizeof(buffer) - 1, ZEND_LONG_FMT, size);
|
|
|
|
if (buffer_len < 0) {
|
|
return 0;
|
|
}
|
|
|
|
if (!ftp_putcmd(ftp, "ALLO", sizeof("ALLO")-1, buffer, buffer_len)) {
|
|
return 0;
|
|
}
|
|
|
|
if (!ftp_getresp(ftp)) {
|
|
return 0;
|
|
}
|
|
|
|
if (response) {
|
|
*response = zend_string_init(ftp->inbuf, strlen(ftp->inbuf), 0);
|
|
}
|
|
|
|
if (ftp->resp < 200 || ftp->resp >= 300) {
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ ftp_nlist
|
|
*/
|
|
char**
|
|
ftp_nlist(ftpbuf_t *ftp, const char *path, const size_t path_len)
|
|
{
|
|
return ftp_genlist(ftp, "NLST", sizeof("NLST")-1, path, path_len);
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ ftp_list
|
|
*/
|
|
char**
|
|
ftp_list(ftpbuf_t *ftp, const char *path, const size_t path_len, int recursive)
|
|
{
|
|
return ftp_genlist(ftp, ((recursive) ? "LIST -R" : "LIST"), ((recursive) ? sizeof("LIST -R")-1 : sizeof("LIST")-1), path, path_len);
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ ftp_mlsd
|
|
*/
|
|
char**
|
|
ftp_mlsd(ftpbuf_t *ftp, const char *path, const size_t path_len)
|
|
{
|
|
return ftp_genlist(ftp, "MLSD", sizeof("MLSD")-1, path, path_len);
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ ftp_mlsd_parse_line
|
|
*/
|
|
int
|
|
ftp_mlsd_parse_line(HashTable *ht, const char *input) {
|
|
|
|
zval zstr;
|
|
const char *end = input + strlen(input);
|
|
|
|
const char *sp = memchr(input, ' ', end - input);
|
|
if (!sp) {
|
|
php_error_docref(NULL, E_WARNING, "Missing pathname in MLSD response");
|
|
return FAILURE;
|
|
}
|
|
|
|
/* Extract pathname */
|
|
ZVAL_STRINGL(&zstr, sp + 1, end - sp - 1);
|
|
zend_hash_str_update(ht, "name", sizeof("name")-1, &zstr);
|
|
end = sp;
|
|
|
|
while (input < end) {
|
|
const char *semi, *eq;
|
|
|
|
/* Find end of fact */
|
|
semi = memchr(input, ';', end - input);
|
|
if (!semi) {
|
|
php_error_docref(NULL, E_WARNING, "Malformed fact in MLSD response");
|
|
return FAILURE;
|
|
}
|
|
|
|
/* Separate fact key and value */
|
|
eq = memchr(input, '=', semi - input);
|
|
if (!eq) {
|
|
php_error_docref(NULL, E_WARNING, "Malformed fact in MLSD response");
|
|
return FAILURE;
|
|
}
|
|
|
|
ZVAL_STRINGL(&zstr, eq + 1, semi - eq - 1);
|
|
zend_hash_str_update(ht, input, eq - input, &zstr);
|
|
input = semi + 1;
|
|
}
|
|
|
|
return SUCCESS;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ ftp_type
|
|
*/
|
|
int
|
|
ftp_type(ftpbuf_t *ftp, ftptype_t type)
|
|
{
|
|
const char *typechar;
|
|
|
|
if (ftp == NULL) {
|
|
return 0;
|
|
}
|
|
if (type == ftp->type) {
|
|
return 1;
|
|
}
|
|
if (type == FTPTYPE_ASCII) {
|
|
typechar = "A";
|
|
} else if (type == FTPTYPE_IMAGE) {
|
|
typechar = "I";
|
|
} else {
|
|
return 0;
|
|
}
|
|
if (!ftp_putcmd(ftp, "TYPE", sizeof("TYPE")-1, typechar, 1)) {
|
|
return 0;
|
|
}
|
|
if (!ftp_getresp(ftp) || ftp->resp != 200) {
|
|
return 0;
|
|
}
|
|
ftp->type = type;
|
|
|
|
return 1;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ ftp_pasv
|
|
*/
|
|
int
|
|
ftp_pasv(ftpbuf_t *ftp, int pasv)
|
|
{
|
|
char *ptr;
|
|
union ipbox ipbox;
|
|
unsigned long b[6];
|
|
socklen_t n;
|
|
struct sockaddr *sa;
|
|
struct sockaddr_in *sin;
|
|
|
|
if (ftp == NULL) {
|
|
return 0;
|
|
}
|
|
if (pasv && ftp->pasv == 2) {
|
|
return 1;
|
|
}
|
|
ftp->pasv = 0;
|
|
if (!pasv) {
|
|
return 1;
|
|
}
|
|
n = sizeof(ftp->pasvaddr);
|
|
memset(&ftp->pasvaddr, 0, n);
|
|
sa = (struct sockaddr *) &ftp->pasvaddr;
|
|
|
|
if (getpeername(ftp->fd, sa, &n) < 0) {
|
|
return 0;
|
|
}
|
|
|
|
#if HAVE_IPV6
|
|
if (sa->sa_family == AF_INET6) {
|
|
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) sa;
|
|
char *endptr, delimiter;
|
|
|
|
/* try EPSV first */
|
|
if (!ftp_putcmd(ftp, "EPSV", sizeof("EPSV")-1, NULL, (size_t) 0)) {
|
|
return 0;
|
|
}
|
|
if (!ftp_getresp(ftp)) {
|
|
return 0;
|
|
}
|
|
if (ftp->resp == 229) {
|
|
/* parse out the port */
|
|
for (ptr = ftp->inbuf; *ptr && *ptr != '('; ptr++);
|
|
if (!*ptr) {
|
|
return 0;
|
|
}
|
|
delimiter = *++ptr;
|
|
for (n = 0; *ptr && n < 3; ptr++) {
|
|
if (*ptr == delimiter) {
|
|
n++;
|
|
}
|
|
}
|
|
|
|
sin6->sin6_port = htons((unsigned short) strtoul(ptr, &endptr, 10));
|
|
if (ptr == endptr || *endptr != delimiter) {
|
|
return 0;
|
|
}
|
|
ftp->pasv = 2;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/* fall back to PASV */
|
|
#endif
|
|
|
|
if (!ftp_putcmd(ftp, "PASV", sizeof("PASV")-1, NULL, (size_t) 0)) {
|
|
return 0;
|
|
}
|
|
if (!ftp_getresp(ftp) || ftp->resp != 227) {
|
|
return 0;
|
|
}
|
|
/* parse out the IP and port */
|
|
for (ptr = ftp->inbuf; *ptr && !isdigit(*ptr); ptr++);
|
|
n = sscanf(ptr, "%lu,%lu,%lu,%lu,%lu,%lu", &b[0], &b[1], &b[2], &b[3], &b[4], &b[5]);
|
|
if (n != 6) {
|
|
return 0;
|
|
}
|
|
for (n = 0; n < 6; n++) {
|
|
ipbox.c[n] = (unsigned char) b[n];
|
|
}
|
|
sin = (struct sockaddr_in *) sa;
|
|
if (ftp->usepasvaddress) {
|
|
sin->sin_addr = ipbox.ia[0];
|
|
}
|
|
sin->sin_port = ipbox.s[2];
|
|
|
|
ftp->pasv = 2;
|
|
|
|
return 1;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ ftp_get
|
|
*/
|
|
int
|
|
ftp_get(ftpbuf_t *ftp, php_stream *outstream, const char *path, const size_t path_len, ftptype_t type, zend_long resumepos)
|
|
{
|
|
databuf_t *data = NULL;
|
|
size_t rcvd;
|
|
char arg[11];
|
|
|
|
if (ftp == NULL) {
|
|
return 0;
|
|
}
|
|
if (!ftp_type(ftp, type)) {
|
|
goto bail;
|
|
}
|
|
|
|
if ((data = ftp_getdata(ftp)) == NULL) {
|
|
goto bail;
|
|
}
|
|
|
|
ftp->data = data;
|
|
|
|
if (resumepos > 0) {
|
|
int arg_len = snprintf(arg, sizeof(arg), ZEND_LONG_FMT, resumepos);
|
|
|
|
if (arg_len < 0) {
|
|
goto bail;
|
|
}
|
|
if (!ftp_putcmd(ftp, "REST", sizeof("REST")-1, arg, arg_len)) {
|
|
goto bail;
|
|
}
|
|
if (!ftp_getresp(ftp) || (ftp->resp != 350)) {
|
|
goto bail;
|
|
}
|
|
}
|
|
|
|
if (!ftp_putcmd(ftp, "RETR", sizeof("RETR")-1, path, path_len)) {
|
|
goto bail;
|
|
}
|
|
if (!ftp_getresp(ftp) || (ftp->resp != 150 && ftp->resp != 125)) {
|
|
goto bail;
|
|
}
|
|
|
|
if ((data = data_accept(data, ftp)) == NULL) {
|
|
goto bail;
|
|
}
|
|
|
|
while ((rcvd = my_recv(ftp, data->fd, data->buf, FTP_BUFSIZE))) {
|
|
if (rcvd == (size_t)-1) {
|
|
goto bail;
|
|
}
|
|
|
|
if (type == FTPTYPE_ASCII) {
|
|
#ifndef PHP_WIN32
|
|
char *s;
|
|
#endif
|
|
char *ptr = data->buf;
|
|
char *e = ptr + rcvd;
|
|
/* logic depends on the OS EOL
|
|
* Win32 -> \r\n
|
|
* Everything Else \n
|
|
*/
|
|
#ifdef PHP_WIN32
|
|
php_stream_write(outstream, ptr, (e - ptr));
|
|
ptr = e;
|
|
#else
|
|
while (e > ptr && (s = memchr(ptr, '\r', (e - ptr)))) {
|
|
php_stream_write(outstream, ptr, (s - ptr));
|
|
if (*(s + 1) == '\n') {
|
|
s++;
|
|
php_stream_putc(outstream, '\n');
|
|
}
|
|
ptr = s + 1;
|
|
}
|
|
#endif
|
|
if (ptr < e) {
|
|
php_stream_write(outstream, ptr, (e - ptr));
|
|
}
|
|
} else if (rcvd != php_stream_write(outstream, data->buf, rcvd)) {
|
|
goto bail;
|
|
}
|
|
}
|
|
|
|
ftp->data = data = data_close(ftp, data);
|
|
|
|
if (!ftp_getresp(ftp) || (ftp->resp != 226 && ftp->resp != 250)) {
|
|
goto bail;
|
|
}
|
|
|
|
return 1;
|
|
bail:
|
|
ftp->data = data_close(ftp, data);
|
|
return 0;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ ftp_put
|
|
*/
|
|
int
|
|
ftp_put(ftpbuf_t *ftp, const char *path, const size_t path_len, php_stream *instream, ftptype_t type, zend_long startpos)
|
|
{
|
|
databuf_t *data = NULL;
|
|
zend_long size;
|
|
char *ptr;
|
|
int ch;
|
|
char arg[11];
|
|
|
|
if (ftp == NULL) {
|
|
return 0;
|
|
}
|
|
if (!ftp_type(ftp, type)) {
|
|
goto bail;
|
|
}
|
|
if ((data = ftp_getdata(ftp)) == NULL) {
|
|
goto bail;
|
|
}
|
|
ftp->data = data;
|
|
|
|
if (startpos > 0) {
|
|
int arg_len = snprintf(arg, sizeof(arg), ZEND_LONG_FMT, startpos);
|
|
|
|
if (arg_len < 0) {
|
|
goto bail;
|
|
}
|
|
if (!ftp_putcmd(ftp, "REST", sizeof("REST")-1, arg, arg_len)) {
|
|
goto bail;
|
|
}
|
|
if (!ftp_getresp(ftp) || (ftp->resp != 350)) {
|
|
goto bail;
|
|
}
|
|
}
|
|
|
|
if (!ftp_putcmd(ftp, "STOR", sizeof("STOR")-1, path, path_len)) {
|
|
goto bail;
|
|
}
|
|
if (!ftp_getresp(ftp) || (ftp->resp != 150 && ftp->resp != 125)) {
|
|
goto bail;
|
|
}
|
|
if ((data = data_accept(data, ftp)) == NULL) {
|
|
goto bail;
|
|
}
|
|
|
|
size = 0;
|
|
ptr = data->buf;
|
|
while (!php_stream_eof(instream) && (ch = php_stream_getc(instream))!=EOF) {
|
|
/* flush if necessary */
|
|
if (FTP_BUFSIZE - size < 2) {
|
|
if (my_send(ftp, data->fd, data->buf, size) != size) {
|
|
goto bail;
|
|
}
|
|
ptr = data->buf;
|
|
size = 0;
|
|
}
|
|
|
|
if (ch == '\n' && type == FTPTYPE_ASCII) {
|
|
*ptr++ = '\r';
|
|
size++;
|
|
}
|
|
|
|
*ptr++ = ch;
|
|
size++;
|
|
}
|
|
|
|
if (size && my_send(ftp, data->fd, data->buf, size) != size) {
|
|
goto bail;
|
|
}
|
|
ftp->data = data = data_close(ftp, data);
|
|
|
|
if (!ftp_getresp(ftp) || (ftp->resp != 226 && ftp->resp != 250 && ftp->resp != 200)) {
|
|
goto bail;
|
|
}
|
|
return 1;
|
|
bail:
|
|
ftp->data = data_close(ftp, data);
|
|
return 0;
|
|
}
|
|
/* }}} */
|
|
|
|
|
|
/* {{{ ftp_append
|
|
*/
|
|
int
|
|
ftp_append(ftpbuf_t *ftp, const char *path, const size_t path_len, php_stream *instream, ftptype_t type)
|
|
{
|
|
databuf_t *data = NULL;
|
|
zend_long size;
|
|
char *ptr;
|
|
int ch;
|
|
|
|
if (ftp == NULL) {
|
|
return 0;
|
|
}
|
|
if (!ftp_type(ftp, type)) {
|
|
goto bail;
|
|
}
|
|
if ((data = ftp_getdata(ftp)) == NULL) {
|
|
goto bail;
|
|
}
|
|
ftp->data = data;
|
|
|
|
if (!ftp_putcmd(ftp, "APPE", sizeof("APPE")-1, path, path_len)) {
|
|
goto bail;
|
|
}
|
|
if (!ftp_getresp(ftp) || (ftp->resp != 150 && ftp->resp != 125)) {
|
|
goto bail;
|
|
}
|
|
if ((data = data_accept(data, ftp)) == NULL) {
|
|
goto bail;
|
|
}
|
|
|
|
size = 0;
|
|
ptr = data->buf;
|
|
while (!php_stream_eof(instream) && (ch = php_stream_getc(instream))!=EOF) {
|
|
/* flush if necessary */
|
|
if (FTP_BUFSIZE - size < 2) {
|
|
if (my_send(ftp, data->fd, data->buf, size) != size) {
|
|
goto bail;
|
|
}
|
|
ptr = data->buf;
|
|
size = 0;
|
|
}
|
|
|
|
if (ch == '\n' && type == FTPTYPE_ASCII) {
|
|
*ptr++ = '\r';
|
|
size++;
|
|
}
|
|
|
|
*ptr++ = ch;
|
|
size++;
|
|
}
|
|
|
|
if (size && my_send(ftp, data->fd, data->buf, size) != size) {
|
|
goto bail;
|
|
}
|
|
ftp->data = data = data_close(ftp, data);
|
|
|
|
if (!ftp_getresp(ftp) || (ftp->resp != 226 && ftp->resp != 250 && ftp->resp != 200)) {
|
|
goto bail;
|
|
}
|
|
return 1;
|
|
bail:
|
|
ftp->data = data_close(ftp, data);
|
|
return 0;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ ftp_size
|
|
*/
|
|
zend_long
|
|
ftp_size(ftpbuf_t *ftp, const char *path, const size_t path_len)
|
|
{
|
|
if (ftp == NULL) {
|
|
return -1;
|
|
}
|
|
if (!ftp_type(ftp, FTPTYPE_IMAGE)) {
|
|
return -1;
|
|
}
|
|
if (!ftp_putcmd(ftp, "SIZE", sizeof("SIZE")-1, path, path_len)) {
|
|
return -1;
|
|
}
|
|
if (!ftp_getresp(ftp) || ftp->resp != 213) {
|
|
return -1;
|
|
}
|
|
return atol(ftp->inbuf);
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ ftp_mdtm
|
|
*/
|
|
time_t
|
|
ftp_mdtm(ftpbuf_t *ftp, const char *path, const size_t path_len)
|
|
{
|
|
time_t stamp;
|
|
struct tm *gmt, tmbuf;
|
|
struct tm tm;
|
|
char *ptr;
|
|
int n;
|
|
|
|
if (ftp == NULL) {
|
|
return -1;
|
|
}
|
|
if (!ftp_putcmd(ftp, "MDTM", sizeof("MDTM")-1, path, path_len)) {
|
|
return -1;
|
|
}
|
|
if (!ftp_getresp(ftp) || ftp->resp != 213) {
|
|
return -1;
|
|
}
|
|
/* parse out the timestamp */
|
|
for (ptr = ftp->inbuf; *ptr && !isdigit(*ptr); ptr++);
|
|
n = sscanf(ptr, "%4u%2u%2u%2u%2u%2u", &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
|
|
if (n != 6) {
|
|
return -1;
|
|
}
|
|
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) {
|
|
return -1;
|
|
}
|
|
gmt->tm_isdst = -1;
|
|
|
|
/* apply the GMT offset */
|
|
tm.tm_sec += stamp - mktime(gmt);
|
|
tm.tm_isdst = gmt->tm_isdst;
|
|
|
|
stamp = mktime(&tm);
|
|
|
|
return stamp;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ ftp_delete
|
|
*/
|
|
int
|
|
ftp_delete(ftpbuf_t *ftp, const char *path, const size_t path_len)
|
|
{
|
|
if (ftp == NULL) {
|
|
return 0;
|
|
}
|
|
if (!ftp_putcmd(ftp, "DELE", sizeof("DELE")-1, path, path_len)) {
|
|
return 0;
|
|
}
|
|
if (!ftp_getresp(ftp) || ftp->resp != 250) {
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ ftp_rename
|
|
*/
|
|
int
|
|
ftp_rename(ftpbuf_t *ftp, const char *src, const size_t src_len, const char *dest, const size_t dest_len)
|
|
{
|
|
if (ftp == NULL) {
|
|
return 0;
|
|
}
|
|
if (!ftp_putcmd(ftp, "RNFR", sizeof("RNFR")-1, src, src_len)) {
|
|
return 0;
|
|
}
|
|
if (!ftp_getresp(ftp) || ftp->resp != 350) {
|
|
return 0;
|
|
}
|
|
if (!ftp_putcmd(ftp, "RNTO", sizeof("RNTO")-1, dest, dest_len)) {
|
|
return 0;
|
|
}
|
|
if (!ftp_getresp(ftp) || ftp->resp != 250) {
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ ftp_site
|
|
*/
|
|
int
|
|
ftp_site(ftpbuf_t *ftp, const char *cmd, const size_t cmd_len)
|
|
{
|
|
if (ftp == NULL) {
|
|
return 0;
|
|
}
|
|
if (!ftp_putcmd(ftp, "SITE", sizeof("SITE")-1, cmd, cmd_len)) {
|
|
return 0;
|
|
}
|
|
if (!ftp_getresp(ftp) || ftp->resp < 200 || ftp->resp >= 300) {
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
/* }}} */
|
|
|
|
/* static functions */
|
|
|
|
/* {{{ ftp_putcmd
|
|
*/
|
|
int
|
|
ftp_putcmd(ftpbuf_t *ftp, const char *cmd, const size_t cmd_len, const char *args, const size_t args_len)
|
|
{
|
|
int size;
|
|
char *data;
|
|
|
|
if (strpbrk(cmd, "\r\n")) {
|
|
return 0;
|
|
}
|
|
/* build the output buffer */
|
|
if (args && args[0]) {
|
|
/* "cmd args\r\n\0" */
|
|
if (cmd_len + args_len + 4 > FTP_BUFSIZE) {
|
|
return 0;
|
|
}
|
|
if (strpbrk(args, "\r\n")) {
|
|
return 0;
|
|
}
|
|
size = slprintf(ftp->outbuf, sizeof(ftp->outbuf), "%s %s\r\n", cmd, args);
|
|
} else {
|
|
/* "cmd\r\n\0" */
|
|
if (cmd_len + 3 > FTP_BUFSIZE) {
|
|
return 0;
|
|
}
|
|
size = slprintf(ftp->outbuf, sizeof(ftp->outbuf), "%s\r\n", cmd);
|
|
}
|
|
|
|
data = ftp->outbuf;
|
|
|
|
/* Clear the extra-lines buffer */
|
|
ftp->extra = NULL;
|
|
|
|
if (my_send(ftp, ftp->fd, data, size) != size) {
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ ftp_readline
|
|
*/
|
|
int
|
|
ftp_readline(ftpbuf_t *ftp)
|
|
{
|
|
long size, rcvd;
|
|
char *data, *eol;
|
|
|
|
/* shift the extra to the front */
|
|
size = FTP_BUFSIZE;
|
|
rcvd = 0;
|
|
if (ftp->extra) {
|
|
memmove(ftp->inbuf, ftp->extra, ftp->extralen);
|
|
rcvd = ftp->extralen;
|
|
}
|
|
|
|
data = ftp->inbuf;
|
|
|
|
do {
|
|
size -= rcvd;
|
|
for (eol = data; rcvd; rcvd--, eol++) {
|
|
if (*eol == '\r') {
|
|
*eol = 0;
|
|
ftp->extra = eol + 1;
|
|
if (rcvd > 1 && *(eol + 1) == '\n') {
|
|
ftp->extra++;
|
|
rcvd--;
|
|
}
|
|
if ((ftp->extralen = --rcvd) == 0) {
|
|
ftp->extra = NULL;
|
|
}
|
|
return 1;
|
|
} else if (*eol == '\n') {
|
|
*eol = 0;
|
|
ftp->extra = eol + 1;
|
|
if ((ftp->extralen = --rcvd) == 0) {
|
|
ftp->extra = NULL;
|
|
}
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
data = eol;
|
|
if ((rcvd = my_recv(ftp, ftp->fd, data, size)) < 1) {
|
|
return 0;
|
|
}
|
|
} while (size);
|
|
|
|
return 0;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ ftp_getresp
|
|
*/
|
|
int
|
|
ftp_getresp(ftpbuf_t *ftp)
|
|
{
|
|
if (ftp == NULL) {
|
|
return 0;
|
|
}
|
|
ftp->resp = 0;
|
|
|
|
while (1) {
|
|
|
|
if (!ftp_readline(ftp)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Break out when the end-tag is found */
|
|
if (isdigit(ftp->inbuf[0]) && isdigit(ftp->inbuf[1]) && isdigit(ftp->inbuf[2]) && ftp->inbuf[3] == ' ') {
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* translate the tag */
|
|
if (!isdigit(ftp->inbuf[0]) || !isdigit(ftp->inbuf[1]) || !isdigit(ftp->inbuf[2])) {
|
|
return 0;
|
|
}
|
|
|
|
ftp->resp = 100 * (ftp->inbuf[0] - '0') + 10 * (ftp->inbuf[1] - '0') + (ftp->inbuf[2] - '0');
|
|
|
|
memmove(ftp->inbuf, ftp->inbuf + 4, FTP_BUFSIZE - 4);
|
|
|
|
if (ftp->extra) {
|
|
ftp->extra -= 4;
|
|
}
|
|
return 1;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ my_send
|
|
*/
|
|
int
|
|
my_send(ftpbuf_t *ftp, php_socket_t s, void *buf, size_t len)
|
|
{
|
|
zend_long size, sent;
|
|
int n;
|
|
#ifdef HAVE_FTP_SSL
|
|
int err;
|
|
zend_bool retry = 0;
|
|
SSL *handle = NULL;
|
|
php_socket_t fd;
|
|
#endif
|
|
|
|
|
|
size = len;
|
|
while (size) {
|
|
n = php_pollfd_for_ms(s, POLLOUT, ftp->timeout_sec * 1000);
|
|
|
|
if (n < 1) {
|
|
#ifdef PHP_WIN32
|
|
if (n == 0) {
|
|
_set_errno(ETIMEDOUT);
|
|
}
|
|
#else
|
|
if (n == 0) {
|
|
errno = ETIMEDOUT;
|
|
}
|
|
#endif
|
|
return -1;
|
|
}
|
|
|
|
#ifdef HAVE_FTP_SSL
|
|
if (ftp->use_ssl && ftp->fd == s && ftp->ssl_active) {
|
|
handle = ftp->ssl_handle;
|
|
fd = ftp->fd;
|
|
} else if (ftp->use_ssl && ftp->fd != s && ftp->use_ssl_for_data && ftp->data->ssl_active) {
|
|
handle = ftp->data->ssl_handle;
|
|
fd = ftp->data->fd;
|
|
}
|
|
|
|
if (handle) {
|
|
do {
|
|
sent = SSL_write(handle, buf, size);
|
|
err = SSL_get_error(handle, sent);
|
|
|
|
switch (err) {
|
|
case SSL_ERROR_NONE:
|
|
retry = 0;
|
|
break;
|
|
|
|
case SSL_ERROR_ZERO_RETURN:
|
|
retry = 0;
|
|
SSL_shutdown(handle);
|
|
break;
|
|
|
|
case SSL_ERROR_WANT_READ:
|
|
case SSL_ERROR_WANT_CONNECT: {
|
|
php_pollfd p;
|
|
int i;
|
|
|
|
p.fd = fd;
|
|
p.events = POLLOUT;
|
|
p.revents = 0;
|
|
|
|
i = php_poll2(&p, 1, 300);
|
|
|
|
retry = i > 0;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
php_error_docref(NULL, E_WARNING, "SSL write failed");
|
|
return -1;
|
|
}
|
|
} while (retry);
|
|
} else {
|
|
#endif
|
|
sent = send(s, buf, size, 0);
|
|
#ifdef HAVE_FTP_SSL
|
|
}
|
|
#endif
|
|
if (sent == -1) {
|
|
return -1;
|
|
}
|
|
|
|
buf = (char*) buf + sent;
|
|
size -= sent;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ my_recv
|
|
*/
|
|
int
|
|
my_recv(ftpbuf_t *ftp, php_socket_t s, void *buf, size_t len)
|
|
{
|
|
int n, nr_bytes;
|
|
#ifdef HAVE_FTP_SSL
|
|
int err;
|
|
zend_bool retry = 0;
|
|
SSL *handle = NULL;
|
|
php_socket_t fd;
|
|
#endif
|
|
|
|
n = php_pollfd_for_ms(s, PHP_POLLREADABLE, ftp->timeout_sec * 1000);
|
|
if (n < 1) {
|
|
#ifdef PHP_WIN32
|
|
if (n == 0) {
|
|
_set_errno(ETIMEDOUT);
|
|
}
|
|
#else
|
|
if (n == 0) {
|
|
errno = ETIMEDOUT;
|
|
}
|
|
#endif
|
|
return -1;
|
|
}
|
|
|
|
#ifdef HAVE_FTP_SSL
|
|
if (ftp->use_ssl && ftp->fd == s && ftp->ssl_active) {
|
|
handle = ftp->ssl_handle;
|
|
fd = ftp->fd;
|
|
} else if (ftp->use_ssl && ftp->fd != s && ftp->use_ssl_for_data && ftp->data->ssl_active) {
|
|
handle = ftp->data->ssl_handle;
|
|
fd = ftp->data->fd;
|
|
}
|
|
|
|
if (handle) {
|
|
do {
|
|
nr_bytes = SSL_read(handle, buf, len);
|
|
err = SSL_get_error(handle, nr_bytes);
|
|
|
|
switch (err) {
|
|
case SSL_ERROR_NONE:
|
|
retry = 0;
|
|
break;
|
|
|
|
case SSL_ERROR_ZERO_RETURN:
|
|
retry = 0;
|
|
SSL_shutdown(handle);
|
|
break;
|
|
|
|
case SSL_ERROR_WANT_READ:
|
|
case SSL_ERROR_WANT_CONNECT: {
|
|
php_pollfd p;
|
|
int i;
|
|
|
|
p.fd = fd;
|
|
p.events = POLLIN|POLLPRI;
|
|
p.revents = 0;
|
|
|
|
i = php_poll2(&p, 1, 300);
|
|
|
|
retry = i > 0;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
php_error_docref(NULL, E_WARNING, "SSL read failed");
|
|
return -1;
|
|
}
|
|
} while (retry);
|
|
} else {
|
|
#endif
|
|
nr_bytes = recv(s, buf, len, 0);
|
|
#ifdef HAVE_FTP_SSL
|
|
}
|
|
#endif
|
|
return (nr_bytes);
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ data_available
|
|
*/
|
|
int
|
|
data_available(ftpbuf_t *ftp, php_socket_t s)
|
|
{
|
|
int n;
|
|
|
|
n = php_pollfd_for_ms(s, PHP_POLLREADABLE, 1000);
|
|
if (n < 1) {
|
|
#ifdef PHP_WIN32
|
|
if (n == 0) {
|
|
_set_errno(ETIMEDOUT);
|
|
}
|
|
#else
|
|
if (n == 0) {
|
|
errno = ETIMEDOUT;
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
/* }}} */
|
|
/* {{{ data_writeable
|
|
*/
|
|
int
|
|
data_writeable(ftpbuf_t *ftp, php_socket_t s)
|
|
{
|
|
int n;
|
|
|
|
n = php_pollfd_for_ms(s, POLLOUT, 1000);
|
|
if (n < 1) {
|
|
#ifdef PHP_WIN32
|
|
if (n == 0) {
|
|
_set_errno(ETIMEDOUT);
|
|
}
|
|
#else
|
|
if (n == 0) {
|
|
errno = ETIMEDOUT;
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ my_accept
|
|
*/
|
|
int
|
|
my_accept(ftpbuf_t *ftp, php_socket_t s, struct sockaddr *addr, socklen_t *addrlen)
|
|
{
|
|
int n;
|
|
|
|
n = php_pollfd_for_ms(s, PHP_POLLREADABLE, ftp->timeout_sec * 1000);
|
|
if (n < 1) {
|
|
#ifdef PHP_WIN32
|
|
if (n == 0) {
|
|
_set_errno(ETIMEDOUT);
|
|
}
|
|
#else
|
|
if (n == 0) {
|
|
errno = ETIMEDOUT;
|
|
}
|
|
#endif
|
|
return -1;
|
|
}
|
|
|
|
return accept(s, addr, addrlen);
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ ftp_getdata
|
|
*/
|
|
databuf_t*
|
|
ftp_getdata(ftpbuf_t *ftp)
|
|
{
|
|
int fd = -1;
|
|
databuf_t *data;
|
|
php_sockaddr_storage addr;
|
|
struct sockaddr *sa;
|
|
socklen_t size;
|
|
union ipbox ipbox;
|
|
char arg[sizeof("255, 255, 255, 255, 255, 255")];
|
|
struct timeval tv;
|
|
int arg_len;
|
|
|
|
|
|
/* ask for a passive connection if we need one */
|
|
if (ftp->pasv && !ftp_pasv(ftp, 1)) {
|
|
return NULL;
|
|
}
|
|
/* alloc the data structure */
|
|
data = ecalloc(1, sizeof(*data));
|
|
data->listener = -1;
|
|
data->fd = -1;
|
|
data->type = ftp->type;
|
|
|
|
sa = (struct sockaddr *) &ftp->localaddr;
|
|
/* bind/listen */
|
|
if ((fd = socket(sa->sa_family, SOCK_STREAM, 0)) == SOCK_ERR) {
|
|
php_error_docref(NULL, E_WARNING, "socket() failed: %s (%d)", strerror(errno), errno);
|
|
goto bail;
|
|
}
|
|
|
|
/* passive connection handler */
|
|
if (ftp->pasv) {
|
|
/* clear the ready status */
|
|
ftp->pasv = 1;
|
|
|
|
/* connect */
|
|
/* Win 95/98 seems not to like size > sizeof(sockaddr_in) */
|
|
size = php_sockaddr_size(&ftp->pasvaddr);
|
|
tv.tv_sec = ftp->timeout_sec;
|
|
tv.tv_usec = 0;
|
|
if (php_connect_nonb(fd, (struct sockaddr*) &ftp->pasvaddr, size, &tv) == -1) {
|
|
php_error_docref(NULL, E_WARNING, "php_connect_nonb() failed: %s (%d)", strerror(errno), errno);
|
|
goto bail;
|
|
}
|
|
|
|
data->fd = fd;
|
|
|
|
ftp->data = data;
|
|
return data;
|
|
}
|
|
|
|
|
|
/* active (normal) connection */
|
|
|
|
/* bind to a local address */
|
|
php_any_addr(sa->sa_family, &addr, 0);
|
|
size = php_sockaddr_size(&addr);
|
|
|
|
if (bind(fd, (struct sockaddr*) &addr, size) != 0) {
|
|
php_error_docref(NULL, E_WARNING, "bind() failed: %s (%d)", strerror(errno), errno);
|
|
goto bail;
|
|
}
|
|
|
|
if (getsockname(fd, (struct sockaddr*) &addr, &size) != 0) {
|
|
php_error_docref(NULL, E_WARNING, "getsockname() failed: %s (%d)", strerror(errno), errno);
|
|
goto bail;
|
|
}
|
|
|
|
if (listen(fd, 5) != 0) {
|
|
php_error_docref(NULL, E_WARNING, "listen() failed: %s (%d)", strerror(errno), errno);
|
|
goto bail;
|
|
}
|
|
|
|
data->listener = fd;
|
|
|
|
#if HAVE_IPV6 && HAVE_INET_NTOP
|
|
if (sa->sa_family == AF_INET6) {
|
|
/* need to use EPRT */
|
|
char eprtarg[INET6_ADDRSTRLEN + sizeof("|x||xxxxx|")];
|
|
char out[INET6_ADDRSTRLEN];
|
|
int eprtarg_len;
|
|
inet_ntop(AF_INET6, &((struct sockaddr_in6*) sa)->sin6_addr, out, sizeof(out));
|
|
eprtarg_len = snprintf(eprtarg, sizeof(eprtarg), "|2|%s|%hu|", out, ntohs(((struct sockaddr_in6 *) &addr)->sin6_port));
|
|
|
|
if (eprtarg_len < 0) {
|
|
goto bail;
|
|
}
|
|
|
|
if (!ftp_putcmd(ftp, "EPRT", sizeof("EPRT")-1, eprtarg, eprtarg_len)) {
|
|
goto bail;
|
|
}
|
|
|
|
if (!ftp_getresp(ftp) || ftp->resp != 200) {
|
|
goto bail;
|
|
}
|
|
|
|
ftp->data = data;
|
|
return data;
|
|
}
|
|
#endif
|
|
|
|
/* send the PORT */
|
|
ipbox.ia[0] = ((struct sockaddr_in*) sa)->sin_addr;
|
|
ipbox.s[2] = ((struct sockaddr_in*) &addr)->sin_port;
|
|
arg_len = snprintf(arg, sizeof(arg), "%u,%u,%u,%u,%u,%u", ipbox.c[0], ipbox.c[1], ipbox.c[2], ipbox.c[3], ipbox.c[4], ipbox.c[5]);
|
|
|
|
if (arg_len < 0) {
|
|
goto bail;
|
|
}
|
|
if (!ftp_putcmd(ftp, "PORT", sizeof("PORT")-1, arg, arg_len)) {
|
|
goto bail;
|
|
}
|
|
if (!ftp_getresp(ftp) || ftp->resp != 200) {
|
|
goto bail;
|
|
}
|
|
|
|
ftp->data = data;
|
|
return data;
|
|
|
|
bail:
|
|
if (fd != -1) {
|
|
closesocket(fd);
|
|
}
|
|
efree(data);
|
|
return NULL;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ data_accept
|
|
*/
|
|
databuf_t*
|
|
data_accept(databuf_t *data, ftpbuf_t *ftp)
|
|
{
|
|
php_sockaddr_storage addr;
|
|
socklen_t size;
|
|
|
|
#ifdef HAVE_FTP_SSL
|
|
SSL_CTX *ctx;
|
|
SSL_SESSION *session;
|
|
int err, res;
|
|
zend_bool retry;
|
|
#endif
|
|
|
|
if (data->fd != -1) {
|
|
goto data_accepted;
|
|
}
|
|
size = sizeof(addr);
|
|
data->fd = my_accept(ftp, data->listener, (struct sockaddr*) &addr, &size);
|
|
closesocket(data->listener);
|
|
data->listener = -1;
|
|
|
|
if (data->fd == -1) {
|
|
efree(data);
|
|
return NULL;
|
|
}
|
|
|
|
data_accepted:
|
|
#ifdef HAVE_FTP_SSL
|
|
|
|
/* now enable ssl if we need to */
|
|
if (ftp->use_ssl && ftp->use_ssl_for_data) {
|
|
ctx = SSL_get_SSL_CTX(ftp->ssl_handle);
|
|
if (ctx == NULL) {
|
|
php_error_docref(NULL, E_WARNING, "data_accept: failed to retrieve the existing SSL context");
|
|
return 0;
|
|
}
|
|
|
|
data->ssl_handle = SSL_new(ctx);
|
|
if (data->ssl_handle == NULL) {
|
|
php_error_docref(NULL, E_WARNING, "data_accept: failed to create the SSL handle");
|
|
return 0;
|
|
}
|
|
|
|
SSL_set_fd(data->ssl_handle, data->fd);
|
|
|
|
if (ftp->old_ssl) {
|
|
SSL_copy_session_id(data->ssl_handle, ftp->ssl_handle);
|
|
}
|
|
|
|
/* get the session from the control connection so we can re-use it */
|
|
session = SSL_get_session(ftp->ssl_handle);
|
|
if (session == NULL) {
|
|
php_error_docref(NULL, E_WARNING, "data_accept: failed to retrieve the existing SSL session");
|
|
SSL_free(data->ssl_handle);
|
|
return 0;
|
|
}
|
|
|
|
/* and set it on the data connection */
|
|
res = SSL_set_session(data->ssl_handle, session);
|
|
if (res == 0) {
|
|
php_error_docref(NULL, E_WARNING, "data_accept: failed to set the existing SSL session");
|
|
SSL_free(data->ssl_handle);
|
|
return 0;
|
|
}
|
|
|
|
do {
|
|
res = SSL_connect(data->ssl_handle);
|
|
err = SSL_get_error(data->ssl_handle, res);
|
|
|
|
switch (err) {
|
|
case SSL_ERROR_NONE:
|
|
retry = 0;
|
|
break;
|
|
|
|
case SSL_ERROR_ZERO_RETURN:
|
|
retry = 0;
|
|
SSL_shutdown(data->ssl_handle);
|
|
break;
|
|
|
|
case SSL_ERROR_WANT_READ:
|
|
case SSL_ERROR_WANT_WRITE: {
|
|
php_pollfd p;
|
|
int i;
|
|
|
|
p.fd = ftp->fd;
|
|
p.events = (err == SSL_ERROR_WANT_READ) ? (POLLIN|POLLPRI) : POLLOUT;
|
|
p.revents = 0;
|
|
|
|
i = php_poll2(&p, 1, 300);
|
|
|
|
retry = i > 0;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
php_error_docref(NULL, E_WARNING, "data_accept: SSL/TLS handshake failed");
|
|
SSL_shutdown(data->ssl_handle);
|
|
SSL_free(data->ssl_handle);
|
|
return 0;
|
|
}
|
|
} while (retry);
|
|
|
|
data->ssl_active = 1;
|
|
}
|
|
|
|
#endif
|
|
|
|
return data;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ data_close
|
|
*/
|
|
databuf_t*
|
|
data_close(ftpbuf_t *ftp, databuf_t *data)
|
|
{
|
|
if (data == NULL) {
|
|
return NULL;
|
|
}
|
|
if (data->listener != -1) {
|
|
#ifdef HAVE_FTP_SSL
|
|
if (data->ssl_active) {
|
|
/* don't free the data context, it's the same as the control */
|
|
SSL_shutdown(data->ssl_handle);
|
|
SSL_free(data->ssl_handle);
|
|
data->ssl_active = 0;
|
|
}
|
|
#endif
|
|
closesocket(data->listener);
|
|
}
|
|
if (data->fd != -1) {
|
|
#ifdef HAVE_FTP_SSL
|
|
if (data->ssl_active) {
|
|
/* don't free the data context, it's the same as the control */
|
|
SSL_shutdown(data->ssl_handle);
|
|
SSL_free(data->ssl_handle);
|
|
data->ssl_active = 0;
|
|
}
|
|
#endif
|
|
closesocket(data->fd);
|
|
}
|
|
if (ftp) {
|
|
ftp->data = NULL;
|
|
}
|
|
efree(data);
|
|
return NULL;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ ftp_genlist
|
|
*/
|
|
char**
|
|
ftp_genlist(ftpbuf_t *ftp, const char *cmd, const size_t cmd_len, const char *path, const size_t path_len)
|
|
{
|
|
php_stream *tmpstream = NULL;
|
|
databuf_t *data = NULL;
|
|
char *ptr;
|
|
int ch, lastch;
|
|
size_t size, rcvd;
|
|
size_t lines;
|
|
char **ret = NULL;
|
|
char **entry;
|
|
char *text;
|
|
|
|
|
|
if ((tmpstream = php_stream_fopen_tmpfile()) == NULL) {
|
|
php_error_docref(NULL, E_WARNING, "Unable to create temporary file. Check permissions in temporary files directory.");
|
|
return NULL;
|
|
}
|
|
|
|
if (!ftp_type(ftp, FTPTYPE_ASCII)) {
|
|
goto bail;
|
|
}
|
|
|
|
if ((data = ftp_getdata(ftp)) == NULL) {
|
|
goto bail;
|
|
}
|
|
ftp->data = data;
|
|
|
|
if (!ftp_putcmd(ftp, cmd, cmd_len, path, path_len)) {
|
|
goto bail;
|
|
}
|
|
if (!ftp_getresp(ftp) || (ftp->resp != 150 && ftp->resp != 125 && ftp->resp != 226)) {
|
|
goto bail;
|
|
}
|
|
|
|
/* some servers don't open a ftp-data connection if the directory is empty */
|
|
if (ftp->resp == 226) {
|
|
ftp->data = data_close(ftp, data);
|
|
php_stream_close(tmpstream);
|
|
return ecalloc(1, sizeof(char*));
|
|
}
|
|
|
|
/* pull data buffer into tmpfile */
|
|
if ((data = data_accept(data, ftp)) == NULL) {
|
|
goto bail;
|
|
}
|
|
size = 0;
|
|
lines = 0;
|
|
lastch = 0;
|
|
while ((rcvd = my_recv(ftp, data->fd, data->buf, FTP_BUFSIZE))) {
|
|
if (rcvd == (size_t)-1 || rcvd > ((size_t)(-1))-size) {
|
|
goto bail;
|
|
}
|
|
|
|
php_stream_write(tmpstream, data->buf, rcvd);
|
|
|
|
size += rcvd;
|
|
for (ptr = data->buf; rcvd; rcvd--, ptr++) {
|
|
if (*ptr == '\n' && lastch == '\r') {
|
|
lines++;
|
|
}
|
|
lastch = *ptr;
|
|
}
|
|
}
|
|
|
|
ftp->data = data_close(ftp, data);
|
|
|
|
php_stream_rewind(tmpstream);
|
|
|
|
ret = safe_emalloc((lines + 1), sizeof(char*), size);
|
|
|
|
entry = ret;
|
|
text = (char*) (ret + lines + 1);
|
|
*entry = text;
|
|
lastch = 0;
|
|
while ((ch = php_stream_getc(tmpstream)) != EOF) {
|
|
if (ch == '\n' && lastch == '\r') {
|
|
*(text - 1) = 0;
|
|
*++entry = text;
|
|
} else {
|
|
*text++ = ch;
|
|
}
|
|
lastch = ch;
|
|
}
|
|
*entry = NULL;
|
|
|
|
php_stream_close(tmpstream);
|
|
|
|
if (!ftp_getresp(ftp) || (ftp->resp != 226 && ftp->resp != 250)) {
|
|
efree(ret);
|
|
return NULL;
|
|
}
|
|
|
|
return ret;
|
|
bail:
|
|
ftp->data = data_close(ftp, data);
|
|
php_stream_close(tmpstream);
|
|
if (ret)
|
|
efree(ret);
|
|
return NULL;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ ftp_nb_get
|
|
*/
|
|
int
|
|
ftp_nb_get(ftpbuf_t *ftp, php_stream *outstream, const char *path, const size_t path_len, ftptype_t type, zend_long resumepos)
|
|
{
|
|
databuf_t *data = NULL;
|
|
char arg[11];
|
|
|
|
if (ftp == NULL) {
|
|
return PHP_FTP_FAILED;
|
|
}
|
|
|
|
if (!ftp_type(ftp, type)) {
|
|
goto bail;
|
|
}
|
|
|
|
if ((data = ftp_getdata(ftp)) == NULL) {
|
|
goto bail;
|
|
}
|
|
|
|
if (resumepos>0) {
|
|
int arg_len = snprintf(arg, sizeof(arg), ZEND_LONG_FMT, resumepos);
|
|
|
|
if (arg_len < 0) {
|
|
goto bail;
|
|
}
|
|
if (!ftp_putcmd(ftp, "REST", sizeof("REST")-1, arg, arg_len)) {
|
|
goto bail;
|
|
}
|
|
if (!ftp_getresp(ftp) || (ftp->resp != 350)) {
|
|
goto bail;
|
|
}
|
|
}
|
|
|
|
if (!ftp_putcmd(ftp, "RETR", sizeof("RETR")-1, path, path_len)) {
|
|
goto bail;
|
|
}
|
|
if (!ftp_getresp(ftp) || (ftp->resp != 150 && ftp->resp != 125)) {
|
|
goto bail;
|
|
}
|
|
|
|
if ((data = data_accept(data, ftp)) == NULL) {
|
|
goto bail;
|
|
}
|
|
|
|
ftp->data = data;
|
|
ftp->stream = outstream;
|
|
ftp->lastch = 0;
|
|
ftp->nb = 1;
|
|
|
|
return (ftp_nb_continue_read(ftp));
|
|
|
|
bail:
|
|
ftp->data = data_close(ftp, data);
|
|
return PHP_FTP_FAILED;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ ftp_nb_continue_read
|
|
*/
|
|
int
|
|
ftp_nb_continue_read(ftpbuf_t *ftp)
|
|
{
|
|
databuf_t *data = NULL;
|
|
char *ptr;
|
|
int lastch;
|
|
size_t rcvd;
|
|
ftptype_t type;
|
|
|
|
data = ftp->data;
|
|
|
|
/* check if there is already more data */
|
|
if (!data_available(ftp, data->fd)) {
|
|
return PHP_FTP_MOREDATA;
|
|
}
|
|
|
|
type = ftp->type;
|
|
|
|
lastch = ftp->lastch;
|
|
if ((rcvd = my_recv(ftp, data->fd, data->buf, FTP_BUFSIZE))) {
|
|
if (rcvd == (size_t)-1) {
|
|
goto bail;
|
|
}
|
|
|
|
if (type == FTPTYPE_ASCII) {
|
|
for (ptr = data->buf; rcvd; rcvd--, ptr++) {
|
|
if (lastch == '\r' && *ptr != '\n') {
|
|
php_stream_putc(ftp->stream, '\r');
|
|
}
|
|
if (*ptr != '\r') {
|
|
php_stream_putc(ftp->stream, *ptr);
|
|
}
|
|
lastch = *ptr;
|
|
}
|
|
} else if (rcvd != php_stream_write(ftp->stream, data->buf, rcvd)) {
|
|
goto bail;
|
|
}
|
|
|
|
ftp->lastch = lastch;
|
|
return PHP_FTP_MOREDATA;
|
|
}
|
|
|
|
if (type == FTPTYPE_ASCII && lastch == '\r') {
|
|
php_stream_putc(ftp->stream, '\r');
|
|
}
|
|
|
|
ftp->data = data = data_close(ftp, data);
|
|
|
|
if (!ftp_getresp(ftp) || (ftp->resp != 226 && ftp->resp != 250)) {
|
|
goto bail;
|
|
}
|
|
|
|
ftp->nb = 0;
|
|
return PHP_FTP_FINISHED;
|
|
bail:
|
|
ftp->nb = 0;
|
|
ftp->data = data_close(ftp, data);
|
|
return PHP_FTP_FAILED;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ ftp_nb_put
|
|
*/
|
|
int
|
|
ftp_nb_put(ftpbuf_t *ftp, const char *path, const size_t path_len, php_stream *instream, ftptype_t type, zend_long startpos)
|
|
{
|
|
databuf_t *data = NULL;
|
|
char arg[11];
|
|
|
|
if (ftp == NULL) {
|
|
return 0;
|
|
}
|
|
if (!ftp_type(ftp, type)) {
|
|
goto bail;
|
|
}
|
|
if ((data = ftp_getdata(ftp)) == NULL) {
|
|
goto bail;
|
|
}
|
|
if (startpos > 0) {
|
|
int arg_len = snprintf(arg, sizeof(arg), ZEND_LONG_FMT, startpos);
|
|
|
|
if (arg_len < 0) {
|
|
goto bail;
|
|
}
|
|
if (!ftp_putcmd(ftp, "REST", sizeof("REST")-1, arg, arg_len)) {
|
|
goto bail;
|
|
}
|
|
if (!ftp_getresp(ftp) || (ftp->resp != 350)) {
|
|
goto bail;
|
|
}
|
|
}
|
|
|
|
if (!ftp_putcmd(ftp, "STOR", sizeof("STOR")-1, path, path_len)) {
|
|
goto bail;
|
|
}
|
|
if (!ftp_getresp(ftp) || (ftp->resp != 150 && ftp->resp != 125)) {
|
|
goto bail;
|
|
}
|
|
if ((data = data_accept(data, ftp)) == NULL) {
|
|
goto bail;
|
|
}
|
|
ftp->data = data;
|
|
ftp->stream = instream;
|
|
ftp->lastch = 0;
|
|
ftp->nb = 1;
|
|
|
|
return (ftp_nb_continue_write(ftp));
|
|
|
|
bail:
|
|
ftp->data = data_close(ftp, data);
|
|
return PHP_FTP_FAILED;
|
|
}
|
|
/* }}} */
|
|
|
|
|
|
/* {{{ ftp_nb_continue_write
|
|
*/
|
|
int
|
|
ftp_nb_continue_write(ftpbuf_t *ftp)
|
|
{
|
|
long size;
|
|
char *ptr;
|
|
int ch;
|
|
|
|
/* check if we can write more data */
|
|
if (!data_writeable(ftp, ftp->data->fd)) {
|
|
return PHP_FTP_MOREDATA;
|
|
}
|
|
|
|
size = 0;
|
|
ptr = ftp->data->buf;
|
|
while (!php_stream_eof(ftp->stream) && (ch = php_stream_getc(ftp->stream)) != EOF) {
|
|
|
|
if (ch == '\n' && ftp->type == FTPTYPE_ASCII) {
|
|
*ptr++ = '\r';
|
|
size++;
|
|
}
|
|
|
|
*ptr++ = ch;
|
|
size++;
|
|
|
|
/* flush if necessary */
|
|
if (FTP_BUFSIZE - size < 2) {
|
|
if (my_send(ftp, ftp->data->fd, ftp->data->buf, size) != size) {
|
|
goto bail;
|
|
}
|
|
return PHP_FTP_MOREDATA;
|
|
}
|
|
}
|
|
|
|
if (size && my_send(ftp, ftp->data->fd, ftp->data->buf, size) != size) {
|
|
goto bail;
|
|
}
|
|
ftp->data = data_close(ftp, ftp->data);
|
|
|
|
if (!ftp_getresp(ftp) || (ftp->resp != 226 && ftp->resp != 250)) {
|
|
goto bail;
|
|
}
|
|
ftp->nb = 0;
|
|
return PHP_FTP_FINISHED;
|
|
bail:
|
|
ftp->data = data_close(ftp, ftp->data);
|
|
ftp->nb = 0;
|
|
return PHP_FTP_FAILED;
|
|
}
|
|
/* }}} */
|
|
|
|
#endif /* HAVE_FTP */
|
|
|
|
/*
|
|
* Local variables:
|
|
* tab-width: 4
|
|
* c-basic-offset: 4
|
|
* End:
|
|
* vim600: sw=4 ts=4 fdm=marker
|
|
* vim<600: sw=4 ts=4
|
|
*/
|