mirror of
https://github.com/php/php-src.git
synced 2024-12-01 13:54:10 +08:00
d59aac58b3
The php_stream_read() and php_stream_write() functions now return an ssize_t value, with negative results indicating failure. Functions like fread() and fwrite() will return false in that case. As a special case, EWOULDBLOCK and EAGAIN on non-blocking streams should not be regarded as error conditions, and be reported as successful zero-length reads/writes instead. The handling of EINTR remains unclear and is internally inconsistent (e.g. some code-paths will automatically retry on EINTR, while some won't). I'm landing this now to make sure the stream wrapper ops API changes make it into 7.4 -- however, if the user-facing changes turn out to be problematic we have the option of clamping negative returns to zero in php_stream_read() and php_stream_write() to restore the old behavior in a relatively non-intrusive manner.
928 lines
25 KiB
C
928 lines
25 KiB
C
/*
|
|
+----------------------------------------------------------------------+
|
|
| PHP Version 7 |
|
|
+----------------------------------------------------------------------+
|
|
| 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: |
|
|
| 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. |
|
|
+----------------------------------------------------------------------+
|
|
| Author: Wez Furlong <wez@thebrainroom.com> |
|
|
+----------------------------------------------------------------------+
|
|
*/
|
|
|
|
#include "php.h"
|
|
#include "ext/standard/file.h"
|
|
#include "streams/php_streams_int.h"
|
|
#include "php_network.h"
|
|
|
|
#if defined(PHP_WIN32) || defined(__riscos__)
|
|
# undef AF_UNIX
|
|
#endif
|
|
|
|
#if defined(AF_UNIX)
|
|
#include <sys/un.h>
|
|
#endif
|
|
|
|
#ifndef MSG_DONTWAIT
|
|
# define MSG_DONTWAIT 0
|
|
#endif
|
|
|
|
#ifndef MSG_PEEK
|
|
# define MSG_PEEK 0
|
|
#endif
|
|
|
|
#ifdef PHP_WIN32
|
|
/* send/recv family on windows expects int */
|
|
# define XP_SOCK_BUF_SIZE(sz) (((sz) > INT_MAX) ? INT_MAX : (int)(sz))
|
|
#else
|
|
# define XP_SOCK_BUF_SIZE(sz) (sz)
|
|
#endif
|
|
|
|
const php_stream_ops php_stream_generic_socket_ops;
|
|
PHPAPI const php_stream_ops php_stream_socket_ops;
|
|
const php_stream_ops php_stream_udp_socket_ops;
|
|
#ifdef AF_UNIX
|
|
const php_stream_ops php_stream_unix_socket_ops;
|
|
const php_stream_ops php_stream_unixdg_socket_ops;
|
|
#endif
|
|
|
|
|
|
static int php_tcp_sockop_set_option(php_stream *stream, int option, int value, void *ptrparam);
|
|
|
|
/* {{{ Generic socket stream operations */
|
|
static ssize_t php_sockop_write(php_stream *stream, const char *buf, size_t count)
|
|
{
|
|
php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract;
|
|
ssize_t didwrite;
|
|
struct timeval *ptimeout;
|
|
|
|
if (!sock || sock->socket == -1) {
|
|
return 0;
|
|
}
|
|
|
|
if (sock->timeout.tv_sec == -1)
|
|
ptimeout = NULL;
|
|
else
|
|
ptimeout = &sock->timeout;
|
|
|
|
retry:
|
|
didwrite = send(sock->socket, buf, XP_SOCK_BUF_SIZE(count), (sock->is_blocked && ptimeout) ? MSG_DONTWAIT : 0);
|
|
|
|
if (didwrite <= 0) {
|
|
int err = php_socket_errno();
|
|
char *estr;
|
|
|
|
if (sock->is_blocked && (err == EWOULDBLOCK || err == EAGAIN)) {
|
|
int retval;
|
|
|
|
sock->timeout_event = 0;
|
|
|
|
do {
|
|
retval = php_pollfd_for(sock->socket, POLLOUT, ptimeout);
|
|
|
|
if (retval == 0) {
|
|
sock->timeout_event = 1;
|
|
break;
|
|
}
|
|
|
|
if (retval > 0) {
|
|
/* writable now; retry */
|
|
goto retry;
|
|
}
|
|
|
|
err = php_socket_errno();
|
|
} while (err == EINTR);
|
|
}
|
|
estr = php_socket_strerror(err, NULL, 0);
|
|
php_error_docref(NULL, E_NOTICE, "send of " ZEND_LONG_FMT " bytes failed with errno=%d %s",
|
|
(zend_long)count, err, estr);
|
|
efree(estr);
|
|
}
|
|
|
|
if (didwrite > 0) {
|
|
php_stream_notify_progress_increment(PHP_STREAM_CONTEXT(stream), didwrite, 0);
|
|
}
|
|
|
|
return didwrite;
|
|
}
|
|
|
|
static void php_sock_stream_wait_for_data(php_stream *stream, php_netstream_data_t *sock)
|
|
{
|
|
int retval;
|
|
struct timeval *ptimeout;
|
|
|
|
if (!sock || sock->socket == -1) {
|
|
return;
|
|
}
|
|
|
|
sock->timeout_event = 0;
|
|
|
|
if (sock->timeout.tv_sec == -1)
|
|
ptimeout = NULL;
|
|
else
|
|
ptimeout = &sock->timeout;
|
|
|
|
while(1) {
|
|
retval = php_pollfd_for(sock->socket, PHP_POLLREADABLE, ptimeout);
|
|
|
|
if (retval == 0)
|
|
sock->timeout_event = 1;
|
|
|
|
if (retval >= 0)
|
|
break;
|
|
|
|
if (php_socket_errno() != EINTR)
|
|
break;
|
|
}
|
|
}
|
|
|
|
static ssize_t php_sockop_read(php_stream *stream, char *buf, size_t count)
|
|
{
|
|
php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract;
|
|
ssize_t nr_bytes = 0;
|
|
int err;
|
|
|
|
if (!sock || sock->socket == -1) {
|
|
return -1;
|
|
}
|
|
|
|
if (sock->is_blocked) {
|
|
php_sock_stream_wait_for_data(stream, sock);
|
|
if (sock->timeout_event)
|
|
return 0;
|
|
}
|
|
|
|
nr_bytes = recv(sock->socket, buf, XP_SOCK_BUF_SIZE(count), (sock->is_blocked && sock->timeout.tv_sec != -1) ? MSG_DONTWAIT : 0);
|
|
err = php_socket_errno();
|
|
|
|
if (nr_bytes < 0) {
|
|
if (err == EAGAIN || err == EWOULDBLOCK) {
|
|
nr_bytes = 0;
|
|
} else {
|
|
stream->eof = 1;
|
|
}
|
|
} else if (nr_bytes == 0) {
|
|
stream->eof = 1;
|
|
}
|
|
|
|
if (nr_bytes > 0) {
|
|
php_stream_notify_progress_increment(PHP_STREAM_CONTEXT(stream), nr_bytes, 0);
|
|
}
|
|
|
|
return nr_bytes;
|
|
}
|
|
|
|
|
|
static int php_sockop_close(php_stream *stream, int close_handle)
|
|
{
|
|
php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract;
|
|
#ifdef PHP_WIN32
|
|
int n;
|
|
#endif
|
|
|
|
if (!sock) {
|
|
return 0;
|
|
}
|
|
|
|
if (close_handle) {
|
|
|
|
#ifdef PHP_WIN32
|
|
if (sock->socket == -1)
|
|
sock->socket = SOCK_ERR;
|
|
#endif
|
|
if (sock->socket != SOCK_ERR) {
|
|
#ifdef PHP_WIN32
|
|
/* prevent more data from coming in */
|
|
shutdown(sock->socket, SHUT_RD);
|
|
|
|
/* try to make sure that the OS sends all data before we close the connection.
|
|
* Essentially, we are waiting for the socket to become writeable, which means
|
|
* that all pending data has been sent.
|
|
* We use a small timeout which should encourage the OS to send the data,
|
|
* but at the same time avoid hanging indefinitely.
|
|
* */
|
|
do {
|
|
n = php_pollfd_for_ms(sock->socket, POLLOUT, 500);
|
|
} while (n == -1 && php_socket_errno() == EINTR);
|
|
#endif
|
|
closesocket(sock->socket);
|
|
sock->socket = SOCK_ERR;
|
|
}
|
|
|
|
}
|
|
|
|
pefree(sock, php_stream_is_persistent(stream));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int php_sockop_flush(php_stream *stream)
|
|
{
|
|
#if 0
|
|
php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract;
|
|
return fsync(sock->socket);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static int php_sockop_stat(php_stream *stream, php_stream_statbuf *ssb)
|
|
{
|
|
#if ZEND_WIN32
|
|
return 0;
|
|
#else
|
|
php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract;
|
|
|
|
return zend_fstat(sock->socket, &ssb->sb);
|
|
#endif
|
|
}
|
|
|
|
static inline int sock_sendto(php_netstream_data_t *sock, const char *buf, size_t buflen, int flags,
|
|
struct sockaddr *addr, socklen_t addrlen
|
|
)
|
|
{
|
|
int ret;
|
|
if (addr) {
|
|
ret = sendto(sock->socket, buf, XP_SOCK_BUF_SIZE(buflen), flags, addr, XP_SOCK_BUF_SIZE(addrlen));
|
|
|
|
return (ret == SOCK_CONN_ERR) ? -1 : ret;
|
|
}
|
|
#ifdef PHP_WIN32
|
|
return ((ret = send(sock->socket, buf, buflen > INT_MAX ? INT_MAX : (int)buflen, flags)) == SOCK_CONN_ERR) ? -1 : ret;
|
|
#else
|
|
return ((ret = send(sock->socket, buf, buflen, flags)) == SOCK_CONN_ERR) ? -1 : ret;
|
|
#endif
|
|
}
|
|
|
|
static inline int sock_recvfrom(php_netstream_data_t *sock, char *buf, size_t buflen, int flags,
|
|
zend_string **textaddr,
|
|
struct sockaddr **addr, socklen_t *addrlen
|
|
)
|
|
{
|
|
int ret;
|
|
int want_addr = textaddr || addr;
|
|
|
|
if (want_addr) {
|
|
php_sockaddr_storage sa;
|
|
socklen_t sl = sizeof(sa);
|
|
ret = recvfrom(sock->socket, buf, XP_SOCK_BUF_SIZE(buflen), flags, (struct sockaddr*)&sa, &sl);
|
|
ret = (ret == SOCK_CONN_ERR) ? -1 : ret;
|
|
if (sl) {
|
|
php_network_populate_name_from_sockaddr((struct sockaddr*)&sa, sl,
|
|
textaddr, addr, addrlen);
|
|
} else {
|
|
if (textaddr) {
|
|
*textaddr = ZSTR_EMPTY_ALLOC();
|
|
}
|
|
if (addr) {
|
|
*addr = NULL;
|
|
*addrlen = 0;
|
|
}
|
|
}
|
|
} else {
|
|
ret = recv(sock->socket, buf, XP_SOCK_BUF_SIZE(buflen), flags);
|
|
ret = (ret == SOCK_CONN_ERR) ? -1 : ret;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int php_sockop_set_option(php_stream *stream, int option, int value, void *ptrparam)
|
|
{
|
|
int oldmode, flags;
|
|
php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract;
|
|
php_stream_xport_param *xparam;
|
|
|
|
if (!sock) {
|
|
return PHP_STREAM_OPTION_RETURN_NOTIMPL;
|
|
}
|
|
|
|
switch(option) {
|
|
case PHP_STREAM_OPTION_CHECK_LIVENESS:
|
|
{
|
|
struct timeval tv;
|
|
char buf;
|
|
int alive = 1;
|
|
|
|
if (value == -1) {
|
|
if (sock->timeout.tv_sec == -1) {
|
|
tv.tv_sec = FG(default_socket_timeout);
|
|
tv.tv_usec = 0;
|
|
} else {
|
|
tv = sock->timeout;
|
|
}
|
|
} else {
|
|
tv.tv_sec = value;
|
|
tv.tv_usec = 0;
|
|
}
|
|
|
|
if (sock->socket == -1) {
|
|
alive = 0;
|
|
} else if (php_pollfd_for(sock->socket, PHP_POLLREADABLE|POLLPRI, &tv) > 0) {
|
|
#ifdef PHP_WIN32
|
|
int ret;
|
|
#else
|
|
ssize_t ret;
|
|
#endif
|
|
int err;
|
|
|
|
ret = recv(sock->socket, &buf, sizeof(buf), MSG_PEEK);
|
|
err = php_socket_errno();
|
|
if (0 == ret || /* the counterpart did properly shutdown*/
|
|
(0 > ret && err != EWOULDBLOCK && err != EAGAIN && err != EMSGSIZE)) { /* there was an unrecoverable error */
|
|
alive = 0;
|
|
}
|
|
}
|
|
return alive ? PHP_STREAM_OPTION_RETURN_OK : PHP_STREAM_OPTION_RETURN_ERR;
|
|
}
|
|
|
|
case PHP_STREAM_OPTION_BLOCKING:
|
|
oldmode = sock->is_blocked;
|
|
if (SUCCESS == php_set_sock_blocking(sock->socket, value)) {
|
|
sock->is_blocked = value;
|
|
return oldmode;
|
|
}
|
|
return PHP_STREAM_OPTION_RETURN_ERR;
|
|
|
|
case PHP_STREAM_OPTION_READ_TIMEOUT:
|
|
sock->timeout = *(struct timeval*)ptrparam;
|
|
sock->timeout_event = 0;
|
|
return PHP_STREAM_OPTION_RETURN_OK;
|
|
|
|
case PHP_STREAM_OPTION_META_DATA_API:
|
|
add_assoc_bool((zval *)ptrparam, "timed_out", sock->timeout_event);
|
|
add_assoc_bool((zval *)ptrparam, "blocked", sock->is_blocked);
|
|
add_assoc_bool((zval *)ptrparam, "eof", stream->eof);
|
|
return PHP_STREAM_OPTION_RETURN_OK;
|
|
|
|
case PHP_STREAM_OPTION_XPORT_API:
|
|
xparam = (php_stream_xport_param *)ptrparam;
|
|
|
|
switch (xparam->op) {
|
|
case STREAM_XPORT_OP_LISTEN:
|
|
xparam->outputs.returncode = (listen(sock->socket, xparam->inputs.backlog) == 0) ? 0: -1;
|
|
return PHP_STREAM_OPTION_RETURN_OK;
|
|
|
|
case STREAM_XPORT_OP_GET_NAME:
|
|
xparam->outputs.returncode = php_network_get_sock_name(sock->socket,
|
|
xparam->want_textaddr ? &xparam->outputs.textaddr : NULL,
|
|
xparam->want_addr ? &xparam->outputs.addr : NULL,
|
|
xparam->want_addr ? &xparam->outputs.addrlen : NULL
|
|
);
|
|
return PHP_STREAM_OPTION_RETURN_OK;
|
|
|
|
case STREAM_XPORT_OP_GET_PEER_NAME:
|
|
xparam->outputs.returncode = php_network_get_peer_name(sock->socket,
|
|
xparam->want_textaddr ? &xparam->outputs.textaddr : NULL,
|
|
xparam->want_addr ? &xparam->outputs.addr : NULL,
|
|
xparam->want_addr ? &xparam->outputs.addrlen : NULL
|
|
);
|
|
return PHP_STREAM_OPTION_RETURN_OK;
|
|
|
|
case STREAM_XPORT_OP_SEND:
|
|
flags = 0;
|
|
if ((xparam->inputs.flags & STREAM_OOB) == STREAM_OOB) {
|
|
flags |= MSG_OOB;
|
|
}
|
|
xparam->outputs.returncode = sock_sendto(sock,
|
|
xparam->inputs.buf, xparam->inputs.buflen,
|
|
flags,
|
|
xparam->inputs.addr,
|
|
xparam->inputs.addrlen);
|
|
if (xparam->outputs.returncode == -1) {
|
|
char *err = php_socket_strerror(php_socket_errno(), NULL, 0);
|
|
php_error_docref(NULL, E_WARNING,
|
|
"%s\n", err);
|
|
efree(err);
|
|
}
|
|
return PHP_STREAM_OPTION_RETURN_OK;
|
|
|
|
case STREAM_XPORT_OP_RECV:
|
|
flags = 0;
|
|
if ((xparam->inputs.flags & STREAM_OOB) == STREAM_OOB) {
|
|
flags |= MSG_OOB;
|
|
}
|
|
if ((xparam->inputs.flags & STREAM_PEEK) == STREAM_PEEK) {
|
|
flags |= MSG_PEEK;
|
|
}
|
|
xparam->outputs.returncode = sock_recvfrom(sock,
|
|
xparam->inputs.buf, xparam->inputs.buflen,
|
|
flags,
|
|
xparam->want_textaddr ? &xparam->outputs.textaddr : NULL,
|
|
xparam->want_addr ? &xparam->outputs.addr : NULL,
|
|
xparam->want_addr ? &xparam->outputs.addrlen : NULL
|
|
);
|
|
return PHP_STREAM_OPTION_RETURN_OK;
|
|
|
|
|
|
#ifdef HAVE_SHUTDOWN
|
|
# ifndef SHUT_RD
|
|
# define SHUT_RD 0
|
|
# endif
|
|
# ifndef SHUT_WR
|
|
# define SHUT_WR 1
|
|
# endif
|
|
# ifndef SHUT_RDWR
|
|
# define SHUT_RDWR 2
|
|
# endif
|
|
case STREAM_XPORT_OP_SHUTDOWN: {
|
|
static const int shutdown_how[] = {SHUT_RD, SHUT_WR, SHUT_RDWR};
|
|
|
|
xparam->outputs.returncode = shutdown(sock->socket, shutdown_how[xparam->how]);
|
|
return PHP_STREAM_OPTION_RETURN_OK;
|
|
}
|
|
#endif
|
|
|
|
default:
|
|
return PHP_STREAM_OPTION_RETURN_NOTIMPL;
|
|
}
|
|
|
|
default:
|
|
return PHP_STREAM_OPTION_RETURN_NOTIMPL;
|
|
}
|
|
}
|
|
|
|
static int php_sockop_cast(php_stream *stream, int castas, void **ret)
|
|
{
|
|
php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract;
|
|
|
|
if (!sock) {
|
|
return FAILURE;
|
|
}
|
|
|
|
switch(castas) {
|
|
case PHP_STREAM_AS_STDIO:
|
|
if (ret) {
|
|
*(FILE**)ret = fdopen(sock->socket, stream->mode);
|
|
if (*ret)
|
|
return SUCCESS;
|
|
return FAILURE;
|
|
}
|
|
return SUCCESS;
|
|
case PHP_STREAM_AS_FD_FOR_SELECT:
|
|
case PHP_STREAM_AS_FD:
|
|
case PHP_STREAM_AS_SOCKETD:
|
|
if (ret)
|
|
*(php_socket_t *)ret = sock->socket;
|
|
return SUCCESS;
|
|
default:
|
|
return FAILURE;
|
|
}
|
|
}
|
|
/* }}} */
|
|
|
|
/* These may look identical, but we need them this way so that
|
|
* we can determine which type of socket we are dealing with
|
|
* by inspecting stream->ops.
|
|
* A "useful" side-effect is that the user's scripts can then
|
|
* make similar decisions using stream_get_meta_data.
|
|
* */
|
|
const php_stream_ops php_stream_generic_socket_ops = {
|
|
php_sockop_write, php_sockop_read,
|
|
php_sockop_close, php_sockop_flush,
|
|
"generic_socket",
|
|
NULL, /* seek */
|
|
php_sockop_cast,
|
|
php_sockop_stat,
|
|
php_sockop_set_option,
|
|
};
|
|
|
|
|
|
const php_stream_ops php_stream_socket_ops = {
|
|
php_sockop_write, php_sockop_read,
|
|
php_sockop_close, php_sockop_flush,
|
|
"tcp_socket",
|
|
NULL, /* seek */
|
|
php_sockop_cast,
|
|
php_sockop_stat,
|
|
php_tcp_sockop_set_option,
|
|
};
|
|
|
|
const php_stream_ops php_stream_udp_socket_ops = {
|
|
php_sockop_write, php_sockop_read,
|
|
php_sockop_close, php_sockop_flush,
|
|
"udp_socket",
|
|
NULL, /* seek */
|
|
php_sockop_cast,
|
|
php_sockop_stat,
|
|
php_tcp_sockop_set_option,
|
|
};
|
|
|
|
#ifdef AF_UNIX
|
|
const php_stream_ops php_stream_unix_socket_ops = {
|
|
php_sockop_write, php_sockop_read,
|
|
php_sockop_close, php_sockop_flush,
|
|
"unix_socket",
|
|
NULL, /* seek */
|
|
php_sockop_cast,
|
|
php_sockop_stat,
|
|
php_tcp_sockop_set_option,
|
|
};
|
|
const php_stream_ops php_stream_unixdg_socket_ops = {
|
|
php_sockop_write, php_sockop_read,
|
|
php_sockop_close, php_sockop_flush,
|
|
"udg_socket",
|
|
NULL, /* seek */
|
|
php_sockop_cast,
|
|
php_sockop_stat,
|
|
php_tcp_sockop_set_option,
|
|
};
|
|
#endif
|
|
|
|
|
|
/* network socket operations */
|
|
|
|
#ifdef AF_UNIX
|
|
static inline int parse_unix_address(php_stream_xport_param *xparam, struct sockaddr_un *unix_addr)
|
|
{
|
|
memset(unix_addr, 0, sizeof(*unix_addr));
|
|
unix_addr->sun_family = AF_UNIX;
|
|
|
|
/* we need to be binary safe on systems that support an abstract
|
|
* namespace */
|
|
if (xparam->inputs.namelen >= sizeof(unix_addr->sun_path)) {
|
|
/* On linux, when the path begins with a NUL byte we are
|
|
* referring to an abstract namespace. In theory we should
|
|
* allow an extra byte below, since we don't need the NULL.
|
|
* BUT, to get into this branch of code, the name is too long,
|
|
* so we don't care. */
|
|
xparam->inputs.namelen = sizeof(unix_addr->sun_path) - 1;
|
|
php_error_docref(NULL, E_NOTICE,
|
|
"socket path exceeded the maximum allowed length of %lu bytes "
|
|
"and was truncated", (unsigned long)sizeof(unix_addr->sun_path));
|
|
}
|
|
|
|
memcpy(unix_addr->sun_path, xparam->inputs.name, xparam->inputs.namelen);
|
|
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
static inline char *parse_ip_address_ex(const char *str, size_t str_len, int *portno, int get_err, zend_string **err)
|
|
{
|
|
char *colon;
|
|
char *host = NULL;
|
|
|
|
#ifdef HAVE_IPV6
|
|
char *p;
|
|
|
|
if (*(str) == '[' && str_len > 1) {
|
|
/* IPV6 notation to specify raw address with port (i.e. [fe80::1]:80) */
|
|
p = memchr(str + 1, ']', str_len - 2);
|
|
if (!p || *(p + 1) != ':') {
|
|
if (get_err) {
|
|
*err = strpprintf(0, "Failed to parse IPv6 address \"%s\"", str);
|
|
}
|
|
return NULL;
|
|
}
|
|
*portno = atoi(p + 2);
|
|
return estrndup(str + 1, p - str - 1);
|
|
}
|
|
#endif
|
|
if (str_len) {
|
|
colon = memchr(str, ':', str_len - 1);
|
|
} else {
|
|
colon = NULL;
|
|
}
|
|
if (colon) {
|
|
*portno = atoi(colon + 1);
|
|
host = estrndup(str, colon - str);
|
|
} else {
|
|
if (get_err) {
|
|
*err = strpprintf(0, "Failed to parse address \"%s\"", str);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
return host;
|
|
}
|
|
|
|
static inline char *parse_ip_address(php_stream_xport_param *xparam, int *portno)
|
|
{
|
|
return parse_ip_address_ex(xparam->inputs.name, xparam->inputs.namelen, portno, xparam->want_errortext, &xparam->outputs.error_text);
|
|
}
|
|
|
|
static inline int php_tcp_sockop_bind(php_stream *stream, php_netstream_data_t *sock,
|
|
php_stream_xport_param *xparam)
|
|
{
|
|
char *host = NULL;
|
|
int portno, err;
|
|
long sockopts = STREAM_SOCKOP_NONE;
|
|
zval *tmpzval = NULL;
|
|
|
|
#ifdef AF_UNIX
|
|
if (stream->ops == &php_stream_unix_socket_ops || stream->ops == &php_stream_unixdg_socket_ops) {
|
|
struct sockaddr_un unix_addr;
|
|
|
|
sock->socket = socket(PF_UNIX, stream->ops == &php_stream_unix_socket_ops ? SOCK_STREAM : SOCK_DGRAM, 0);
|
|
|
|
if (sock->socket == SOCK_ERR) {
|
|
if (xparam->want_errortext) {
|
|
xparam->outputs.error_text = strpprintf(0, "Failed to create unix%s socket %s",
|
|
stream->ops == &php_stream_unix_socket_ops ? "" : "datagram",
|
|
strerror(errno));
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
parse_unix_address(xparam, &unix_addr);
|
|
|
|
return bind(sock->socket, (const struct sockaddr *)&unix_addr,
|
|
(socklen_t) XtOffsetOf(struct sockaddr_un, sun_path) + xparam->inputs.namelen);
|
|
}
|
|
#endif
|
|
|
|
host = parse_ip_address(xparam, &portno);
|
|
|
|
if (host == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
#ifdef IPV6_V6ONLY
|
|
if (PHP_STREAM_CONTEXT(stream)
|
|
&& (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "ipv6_v6only")) != NULL
|
|
&& Z_TYPE_P(tmpzval) != IS_NULL
|
|
) {
|
|
sockopts |= STREAM_SOCKOP_IPV6_V6ONLY;
|
|
sockopts |= STREAM_SOCKOP_IPV6_V6ONLY_ENABLED * zend_is_true(tmpzval);
|
|
}
|
|
#endif
|
|
|
|
#ifdef SO_REUSEPORT
|
|
if (PHP_STREAM_CONTEXT(stream)
|
|
&& (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_reuseport")) != NULL
|
|
&& zend_is_true(tmpzval)
|
|
) {
|
|
sockopts |= STREAM_SOCKOP_SO_REUSEPORT;
|
|
}
|
|
#endif
|
|
|
|
#ifdef SO_BROADCAST
|
|
if (stream->ops == &php_stream_udp_socket_ops /* SO_BROADCAST is only applicable for UDP */
|
|
&& PHP_STREAM_CONTEXT(stream)
|
|
&& (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_broadcast")) != NULL
|
|
&& zend_is_true(tmpzval)
|
|
) {
|
|
sockopts |= STREAM_SOCKOP_SO_BROADCAST;
|
|
}
|
|
#endif
|
|
|
|
sock->socket = php_network_bind_socket_to_local_addr(host, portno,
|
|
stream->ops == &php_stream_udp_socket_ops ? SOCK_DGRAM : SOCK_STREAM,
|
|
sockopts,
|
|
xparam->want_errortext ? &xparam->outputs.error_text : NULL,
|
|
&err
|
|
);
|
|
|
|
if (host) {
|
|
efree(host);
|
|
}
|
|
|
|
return sock->socket == -1 ? -1 : 0;
|
|
}
|
|
|
|
static inline int php_tcp_sockop_connect(php_stream *stream, php_netstream_data_t *sock,
|
|
php_stream_xport_param *xparam)
|
|
{
|
|
char *host = NULL, *bindto = NULL;
|
|
int portno, bindport = 0;
|
|
int err = 0;
|
|
int ret;
|
|
zval *tmpzval = NULL;
|
|
long sockopts = STREAM_SOCKOP_NONE;
|
|
|
|
#ifdef AF_UNIX
|
|
if (stream->ops == &php_stream_unix_socket_ops || stream->ops == &php_stream_unixdg_socket_ops) {
|
|
struct sockaddr_un unix_addr;
|
|
|
|
sock->socket = socket(PF_UNIX, stream->ops == &php_stream_unix_socket_ops ? SOCK_STREAM : SOCK_DGRAM, 0);
|
|
|
|
if (sock->socket == SOCK_ERR) {
|
|
if (xparam->want_errortext) {
|
|
xparam->outputs.error_text = strpprintf(0, "Failed to create unix socket");
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
parse_unix_address(xparam, &unix_addr);
|
|
|
|
ret = php_network_connect_socket(sock->socket,
|
|
(const struct sockaddr *)&unix_addr, (socklen_t) XtOffsetOf(struct sockaddr_un, sun_path) + xparam->inputs.namelen,
|
|
xparam->op == STREAM_XPORT_OP_CONNECT_ASYNC, xparam->inputs.timeout,
|
|
xparam->want_errortext ? &xparam->outputs.error_text : NULL,
|
|
&err);
|
|
|
|
xparam->outputs.error_code = err;
|
|
|
|
goto out;
|
|
}
|
|
#endif
|
|
|
|
host = parse_ip_address(xparam, &portno);
|
|
|
|
if (host == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
if (PHP_STREAM_CONTEXT(stream) && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "bindto")) != NULL) {
|
|
if (Z_TYPE_P(tmpzval) != IS_STRING) {
|
|
if (xparam->want_errortext) {
|
|
xparam->outputs.error_text = strpprintf(0, "local_addr context option is not a string.");
|
|
}
|
|
efree(host);
|
|
return -1;
|
|
}
|
|
bindto = parse_ip_address_ex(Z_STRVAL_P(tmpzval), Z_STRLEN_P(tmpzval), &bindport, xparam->want_errortext, &xparam->outputs.error_text);
|
|
}
|
|
|
|
#ifdef SO_BROADCAST
|
|
if (stream->ops == &php_stream_udp_socket_ops /* SO_BROADCAST is only applicable for UDP */
|
|
&& PHP_STREAM_CONTEXT(stream)
|
|
&& (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_broadcast")) != NULL
|
|
&& zend_is_true(tmpzval)
|
|
) {
|
|
sockopts |= STREAM_SOCKOP_SO_BROADCAST;
|
|
}
|
|
#endif
|
|
|
|
if (stream->ops != &php_stream_udp_socket_ops /* TCP_NODELAY is only applicable for TCP */
|
|
#ifdef AF_UNIX
|
|
&& stream->ops != &php_stream_unix_socket_ops
|
|
&& stream->ops != &php_stream_unixdg_socket_ops
|
|
#endif
|
|
&& PHP_STREAM_CONTEXT(stream)
|
|
&& (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_nodelay")) != NULL
|
|
&& zend_is_true(tmpzval)
|
|
) {
|
|
sockopts |= STREAM_SOCKOP_TCP_NODELAY;
|
|
}
|
|
|
|
/* Note: the test here for php_stream_udp_socket_ops is important, because we
|
|
* want the default to be TCP sockets so that the openssl extension can
|
|
* re-use this code. */
|
|
|
|
sock->socket = php_network_connect_socket_to_host(host, portno,
|
|
stream->ops == &php_stream_udp_socket_ops ? SOCK_DGRAM : SOCK_STREAM,
|
|
xparam->op == STREAM_XPORT_OP_CONNECT_ASYNC,
|
|
xparam->inputs.timeout,
|
|
xparam->want_errortext ? &xparam->outputs.error_text : NULL,
|
|
&err,
|
|
bindto,
|
|
bindport,
|
|
sockopts
|
|
);
|
|
|
|
ret = sock->socket == -1 ? -1 : 0;
|
|
xparam->outputs.error_code = err;
|
|
|
|
if (host) {
|
|
efree(host);
|
|
}
|
|
if (bindto) {
|
|
efree(bindto);
|
|
}
|
|
|
|
#ifdef AF_UNIX
|
|
out:
|
|
#endif
|
|
|
|
if (ret >= 0 && xparam->op == STREAM_XPORT_OP_CONNECT_ASYNC && err == EINPROGRESS) {
|
|
/* indicates pending connection */
|
|
return 1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static inline int php_tcp_sockop_accept(php_stream *stream, php_netstream_data_t *sock,
|
|
php_stream_xport_param *xparam STREAMS_DC)
|
|
{
|
|
int clisock;
|
|
zend_bool nodelay = 0;
|
|
zval *tmpzval = NULL;
|
|
|
|
xparam->outputs.client = NULL;
|
|
|
|
if ((NULL != PHP_STREAM_CONTEXT(stream)) &&
|
|
(tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_nodelay")) != NULL &&
|
|
zend_is_true(tmpzval)) {
|
|
nodelay = 1;
|
|
}
|
|
|
|
clisock = php_network_accept_incoming(sock->socket,
|
|
xparam->want_textaddr ? &xparam->outputs.textaddr : NULL,
|
|
xparam->want_addr ? &xparam->outputs.addr : NULL,
|
|
xparam->want_addr ? &xparam->outputs.addrlen : NULL,
|
|
xparam->inputs.timeout,
|
|
xparam->want_errortext ? &xparam->outputs.error_text : NULL,
|
|
&xparam->outputs.error_code,
|
|
nodelay);
|
|
|
|
if (clisock >= 0) {
|
|
php_netstream_data_t *clisockdata = (php_netstream_data_t*) emalloc(sizeof(*clisockdata));
|
|
|
|
memcpy(clisockdata, sock, sizeof(*clisockdata));
|
|
clisockdata->socket = clisock;
|
|
|
|
xparam->outputs.client = php_stream_alloc_rel(stream->ops, clisockdata, NULL, "r+");
|
|
if (xparam->outputs.client) {
|
|
xparam->outputs.client->ctx = stream->ctx;
|
|
if (stream->ctx) {
|
|
GC_ADDREF(stream->ctx);
|
|
}
|
|
}
|
|
}
|
|
|
|
return xparam->outputs.client == NULL ? -1 : 0;
|
|
}
|
|
|
|
static int php_tcp_sockop_set_option(php_stream *stream, int option, int value, void *ptrparam)
|
|
{
|
|
php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract;
|
|
php_stream_xport_param *xparam;
|
|
|
|
switch(option) {
|
|
case PHP_STREAM_OPTION_XPORT_API:
|
|
xparam = (php_stream_xport_param *)ptrparam;
|
|
|
|
switch(xparam->op) {
|
|
case STREAM_XPORT_OP_CONNECT:
|
|
case STREAM_XPORT_OP_CONNECT_ASYNC:
|
|
xparam->outputs.returncode = php_tcp_sockop_connect(stream, sock, xparam);
|
|
return PHP_STREAM_OPTION_RETURN_OK;
|
|
|
|
case STREAM_XPORT_OP_BIND:
|
|
xparam->outputs.returncode = php_tcp_sockop_bind(stream, sock, xparam);
|
|
return PHP_STREAM_OPTION_RETURN_OK;
|
|
|
|
|
|
case STREAM_XPORT_OP_ACCEPT:
|
|
xparam->outputs.returncode = php_tcp_sockop_accept(stream, sock, xparam STREAMS_CC);
|
|
return PHP_STREAM_OPTION_RETURN_OK;
|
|
default:
|
|
/* fall through */
|
|
;
|
|
}
|
|
}
|
|
return php_sockop_set_option(stream, option, value, ptrparam);
|
|
}
|
|
|
|
|
|
PHPAPI php_stream *php_stream_generic_socket_factory(const char *proto, size_t protolen,
|
|
const char *resourcename, size_t resourcenamelen,
|
|
const char *persistent_id, int options, int flags,
|
|
struct timeval *timeout,
|
|
php_stream_context *context STREAMS_DC)
|
|
{
|
|
php_stream *stream = NULL;
|
|
php_netstream_data_t *sock;
|
|
const php_stream_ops *ops;
|
|
|
|
/* which type of socket ? */
|
|
if (strncmp(proto, "tcp", protolen) == 0) {
|
|
ops = &php_stream_socket_ops;
|
|
} else if (strncmp(proto, "udp", protolen) == 0) {
|
|
ops = &php_stream_udp_socket_ops;
|
|
}
|
|
#ifdef AF_UNIX
|
|
else if (strncmp(proto, "unix", protolen) == 0) {
|
|
ops = &php_stream_unix_socket_ops;
|
|
} else if (strncmp(proto, "udg", protolen) == 0) {
|
|
ops = &php_stream_unixdg_socket_ops;
|
|
}
|
|
#endif
|
|
else {
|
|
/* should never happen */
|
|
return NULL;
|
|
}
|
|
|
|
sock = pemalloc(sizeof(php_netstream_data_t), persistent_id ? 1 : 0);
|
|
memset(sock, 0, sizeof(php_netstream_data_t));
|
|
|
|
sock->is_blocked = 1;
|
|
sock->timeout.tv_sec = FG(default_socket_timeout);
|
|
sock->timeout.tv_usec = 0;
|
|
|
|
/* we don't know the socket until we have determined if we are binding or
|
|
* connecting */
|
|
sock->socket = -1;
|
|
|
|
stream = php_stream_alloc_rel(ops, sock, persistent_id, "r+");
|
|
|
|
if (stream == NULL) {
|
|
pefree(sock, persistent_id ? 1 : 0);
|
|
return NULL;
|
|
}
|
|
|
|
if (flags == 0) {
|
|
return stream;
|
|
}
|
|
|
|
return stream;
|
|
}
|