static size_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 0; } 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(); stream->eof = (nr_bytes == 0 || (nr_bytes == -1 && err != EWOULDBLOCK && err != EAGAIN)); if (nr_bytes > 0) { php_stream_notify_progress_increment(PHP_STREAM_CONTEXT(stream), nr_bytes, 0); } if (nr_bytes < 0) { nr_bytes = 0; } return nr_bytes; }
/* {{{ Generic socket stream operations */ static size_t php_sockop_write(php_stream *stream, const char *buf, size_t count) { php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; int 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=%ld %s", (zend_long)count, err, estr); efree(estr); } if (didwrite > 0) { php_stream_notify_progress_increment(PHP_STREAM_CONTEXT(stream), didwrite, 0); } if (didwrite < 0) { didwrite = 0; } return didwrite; }
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 /* 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_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; }
php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, const char *path, const char *mode, int options, zend_string **opened_path, php_stream_context *context, int redirect_max, int flags STREAMS_DC) /* {{{ */ { php_stream *stream = NULL; php_url *resource = NULL; int use_ssl; int use_proxy = 0; zend_string *tmp = NULL; char *ua_str = NULL; zval *ua_zval = NULL, *tmpzval = NULL, ssl_proxy_peer_name; int body = 0; char location[HTTP_HEADER_BLOCK_SIZE]; zval response_header; int reqok = 0; char *http_header_line = NULL; char tmp_line[128]; size_t chunk_size = 0, file_size = 0; int eol_detect = 0; char *transport_string; zend_string *errstr = NULL; size_t transport_len; int have_header = 0; zend_bool request_fulluri = 0, ignore_errors = 0; struct timeval timeout; char *user_headers = NULL; int header_init = ((flags & HTTP_WRAPPER_HEADER_INIT) != 0); int redirected = ((flags & HTTP_WRAPPER_REDIRECTED) != 0); zend_bool follow_location = 1; php_stream_filter *transfer_encoding = NULL; int response_code; zend_array *symbol_table; smart_str req_buf = {0}; zend_bool custom_request_method; ZVAL_UNDEF(&response_header); tmp_line[0] = '\0'; if (redirect_max < 1) { php_stream_wrapper_log_error(wrapper, options, "Redirection limit reached, aborting"); return NULL; } resource = php_url_parse(path); if (resource == NULL) { return NULL; } if (strncasecmp(resource->scheme, "http", sizeof("http")) && strncasecmp(resource->scheme, "https", sizeof("https"))) { if (!context || (tmpzval = php_stream_context_get_option(context, wrapper->wops->label, "proxy")) == NULL || Z_TYPE_P(tmpzval) != IS_STRING || Z_STRLEN_P(tmpzval) <= 0) { php_url_free(resource); return php_stream_open_wrapper_ex(path, mode, REPORT_ERRORS, NULL, context); } /* Called from a non-http wrapper with http proxying requested (i.e. ftp) */ request_fulluri = 1; use_ssl = 0; use_proxy = 1; transport_len = Z_STRLEN_P(tmpzval); transport_string = estrndup(Z_STRVAL_P(tmpzval), Z_STRLEN_P(tmpzval)); } else { /* Normal http request (possibly with proxy) */ if (strpbrk(mode, "awx+")) { php_stream_wrapper_log_error(wrapper, options, "HTTP wrapper does not support writeable connections"); php_url_free(resource); return NULL; } use_ssl = resource->scheme && (strlen(resource->scheme) > 4) && resource->scheme[4] == 's'; /* choose default ports */ if (use_ssl && resource->port == 0) resource->port = 443; else if (resource->port == 0) resource->port = 80; if (context && (tmpzval = php_stream_context_get_option(context, wrapper->wops->label, "proxy")) != NULL && Z_TYPE_P(tmpzval) == IS_STRING && Z_STRLEN_P(tmpzval) > 0) { use_proxy = 1; transport_len = Z_STRLEN_P(tmpzval); transport_string = estrndup(Z_STRVAL_P(tmpzval), Z_STRLEN_P(tmpzval)); } else { transport_len = spprintf(&transport_string, 0, "%s://%s:%d", use_ssl ? "ssl" : "tcp", resource->host, resource->port); } } if (context && (tmpzval = php_stream_context_get_option(context, wrapper->wops->label, "timeout")) != NULL) { double d = zval_get_double(tmpzval); #ifndef PHP_WIN32 timeout.tv_sec = (time_t) d; timeout.tv_usec = (size_t) ((d - timeout.tv_sec) * 1000000); #else timeout.tv_sec = (long) d; timeout.tv_usec = (long) ((d - timeout.tv_sec) * 1000000); #endif } else { #ifndef PHP_WIN32 timeout.tv_sec = FG(default_socket_timeout); #else timeout.tv_sec = (long)FG(default_socket_timeout); #endif timeout.tv_usec = 0; } stream = php_stream_xport_create(transport_string, transport_len, options, STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT, NULL, &timeout, context, &errstr, NULL); if (stream) { php_stream_set_option(stream, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &timeout); } if (errstr) { php_stream_wrapper_log_error(wrapper, options, "%s", ZSTR_VAL(errstr)); zend_string_release(errstr); errstr = NULL; } efree(transport_string); if (stream && use_proxy && use_ssl) { smart_str header = {0}; /* Set peer_name or name verification will try to use the proxy server name */ if (!context || (tmpzval = php_stream_context_get_option(context, "ssl", "peer_name")) == NULL) { ZVAL_STRING(&ssl_proxy_peer_name, resource->host); php_stream_context_set_option(PHP_STREAM_CONTEXT(stream), "ssl", "peer_name", &ssl_proxy_peer_name); zval_ptr_dtor(&ssl_proxy_peer_name); } smart_str_appendl(&header, "CONNECT ", sizeof("CONNECT ")-1); smart_str_appends(&header, resource->host); smart_str_appendc(&header, ':'); smart_str_append_unsigned(&header, resource->port); smart_str_appendl(&header, " HTTP/1.0\r\n", sizeof(" HTTP/1.0\r\n")-1); /* check if we have Proxy-Authorization header */ if (context && (tmpzval = php_stream_context_get_option(context, "http", "header")) != NULL) { char *s, *p; if (Z_TYPE_P(tmpzval) == IS_ARRAY) { zval *tmpheader = NULL; ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(tmpzval), tmpheader) { if (Z_TYPE_P(tmpheader) == IS_STRING) { s = Z_STRVAL_P(tmpheader); do { while (*s == ' ' || *s == '\t') s++; p = s; while (*p != 0 && *p != ':' && *p != '\r' && *p !='\n') p++; if (*p == ':') { p++; if (p - s == sizeof("Proxy-Authorization:") - 1 && zend_binary_strcasecmp(s, sizeof("Proxy-Authorization:") - 1, "Proxy-Authorization:", sizeof("Proxy-Authorization:") - 1) == 0) { while (*p != 0 && *p != '\r' && *p !='\n') p++; smart_str_appendl(&header, s, p - s); smart_str_appendl(&header, "\r\n", sizeof("\r\n")-1); goto finish; } else { while (*p != 0 && *p != '\r' && *p !='\n') p++; } } s = p; while (*s == '\r' || *s == '\n') s++; } while (*s != 0); } } ZEND_HASH_FOREACH_END(); } else if (Z_TYPE_P(tmpzval) == IS_STRING && Z_STRLEN_P(tmpzval)) {
static PHP_HTTP_FILTER_FUNCTION(chunked_decode) { int out_avail = 0; php_stream_bucket *ptr, *nxt; PHP_HTTP_FILTER_BUFFER(chunked_decode) *buffer = Z_PTR(this->abstract); if (bytes_consumed) { *bytes_consumed = 0; } /* fetch available bucket data */ for (ptr = buckets_in->head; ptr; ptr = nxt) { if (bytes_consumed) { *bytes_consumed += ptr->buflen; } if (PHP_HTTP_BUFFER_NOMEM == php_http_buffer_append(PHP_HTTP_BUFFER(buffer), ptr->buf, ptr->buflen)) { return PSFS_ERR_FATAL; } nxt = ptr->next; php_stream_bucket_unlink(ptr); php_stream_bucket_delref(ptr); } if (!php_http_buffer_fix(PHP_HTTP_BUFFER(buffer))) { return PSFS_ERR_FATAL; } /* we have data in our buffer */ while (PHP_HTTP_BUFFER(buffer)->used) { /* we already know the size of the chunk and are waiting for data */ if (buffer->hexlen) { /* not enough data buffered */ if (PHP_HTTP_BUFFER(buffer)->used < buffer->hexlen) { /* flush anyway? */ if (flags & PSFS_FLAG_FLUSH_INC) { /* flush all data (should only be chunk data) */ out_avail = 1; NEW_BUCKET(PHP_HTTP_BUFFER(buffer)->data, PHP_HTTP_BUFFER(buffer)->used); /* waiting for less data now */ buffer->hexlen -= PHP_HTTP_BUFFER(buffer)->used; /* no more buffered data */ php_http_buffer_reset(PHP_HTTP_BUFFER(buffer)); /* break */ } /* we have too less data and don't need to flush */ else { break; } } /* we seem to have all data of the chunk */ else { out_avail = 1; NEW_BUCKET(PHP_HTTP_BUFFER(buffer)->data, buffer->hexlen); /* remove outgoing data from the buffer */ php_http_buffer_cut(PHP_HTTP_BUFFER(buffer), 0, buffer->hexlen); /* reset hexlen */ buffer->hexlen = 0; /* continue */ } } /* we don't know the length of the chunk yet */ else { size_t off = 0; /* ignore preceeding CRLFs (too loose?) */ while (off < PHP_HTTP_BUFFER(buffer)->used && ( PHP_HTTP_BUFFER(buffer)->data[off] == '\n' || PHP_HTTP_BUFFER(buffer)->data[off] == '\r')) { ++off; } if (off) { php_http_buffer_cut(PHP_HTTP_BUFFER(buffer), 0, off); } /* still data there? */ if (PHP_HTTP_BUFFER(buffer)->used) { int eollen; const char *eolstr; /* we need eol, so we can be sure we have all hex digits */ php_http_buffer_fix(PHP_HTTP_BUFFER(buffer)); if ((eolstr = php_http_locate_bin_eol(PHP_HTTP_BUFFER(buffer)->data, PHP_HTTP_BUFFER(buffer)->used, &eollen))) { char *stop = NULL; /* read in chunk size */ buffer->hexlen = strtoul(PHP_HTTP_BUFFER(buffer)->data, &stop, 16); /* if strtoul() stops at the beginning of the buffered data there's something oddly wrong, i.e. bad input */ if (stop == PHP_HTTP_BUFFER(buffer)->data) { return PSFS_ERR_FATAL; } /* cut out <chunk size hex><chunk extension><eol> */ php_http_buffer_cut(PHP_HTTP_BUFFER(buffer), 0, eolstr + eollen - PHP_HTTP_BUFFER(buffer)->data); /* buffer->hexlen is 0 now or contains the size of the next chunk */ if (!buffer->hexlen) { php_stream_notify_info(PHP_STREAM_CONTEXT(stream), PHP_STREAM_NOTIFY_COMPLETED, NULL, 0); break; } /* continue */ } else { /* we have not enough data buffered to read in chunk size */ break; } } /* break */ } } /* flush before close, but only if we are already waiting for more data */ if (PHP_HTTP_FILTER_IS_CLOSING(stream, flags) && buffer->hexlen && PHP_HTTP_BUFFER(buffer)->used) { out_avail = 1; NEW_BUCKET(PHP_HTTP_BUFFER(buffer)->data, PHP_HTTP_BUFFER(buffer)->used); php_http_buffer_reset(PHP_HTTP_BUFFER(buffer)); buffer->hexlen = 0; } return out_avail ? PSFS_PASS_ON : PSFS_FEED_ME; }