From: Michael Wallner Date: Mon, 13 Jun 2016 12:54:41 +0000 (+0200) Subject: Merge branch 'v2.6.x' X-Git-Tag: RELEASE_3_1_0_BETA1~10 X-Git-Url: https://git.m6w6.name/?p=m6w6%2Fext-http;a=commitdiff_plain;h=43a9a6f8cb56e25c3770d652ce77045f89f68ca6;hp=18871cafc58e15054412aea57b2456ffc0d49713 Merge branch 'v2.6.x' --- diff --git a/src/php_http.c b/src/php_http.c index f9bb4d0..3b3338f 100644 --- a/src/php_http.c +++ b/src/php_http.c @@ -143,6 +143,7 @@ PHP_MINIT_FUNCTION(http) #if PHP_HTTP_HAVE_CURL || SUCCESS != PHP_MINIT_CALL(http_curl) || SUCCESS != PHP_MINIT_CALL(http_client_curl) + || SUCCESS != PHP_MINIT_CALL(http_client_curl_user) #endif || SUCCESS != PHP_MINIT_CALL(http_url) || SUCCESS != PHP_MINIT_CALL(http_env) diff --git a/src/php_http_api.h b/src/php_http_api.h index e8c175f..b264694 100644 --- a/src/php_http_api.h +++ b/src/php_http_api.h @@ -101,6 +101,8 @@ #include "php_http_client_request.h" #include "php_http_client_response.h" #include "php_http_client_curl.h" +#include "php_http_client_curl_user.h" +#include "php_http_client_curl_event.h" #include "php_http_url.h" #include "php_http_version.h" diff --git a/src/php_http_client.c b/src/php_http_client.c index 4b465c0..5d2aafe 100644 --- a/src/php_http_client.c +++ b/src/php_http_client.c @@ -363,19 +363,31 @@ static HashTable *php_http_client_object_get_gc(zval *object, zval **table, int php_http_client_object_t *obj = PHP_HTTP_OBJ(NULL, object); zend_llist_element *el = NULL; HashTable *props = Z_OBJPROP_P(object); - uint32_t count = zend_hash_num_elements(props) + zend_llist_count(&obj->client->responses) + zend_llist_count(&obj->client->requests); + uint32_t count = zend_hash_num_elements(props) + zend_llist_count(&obj->client->responses) + zend_llist_count(&obj->client->requests) + 1; zval *val; *n = 0; *table = obj->gc = erealloc(obj->gc, sizeof(zval) * count); +#if PHP_HTTP_HAVE_CURL + if (obj->client->ops == php_http_client_curl_get_ops()) { + php_http_client_curl_t *curl = obj->client->ctx; + + if (curl->ev_ops == php_http_client_curl_user_ops_get()) { + php_http_client_curl_user_context_t *ctx = curl->ev_ctx; + + ZVAL_COPY_VALUE(&obj->gc[(*n)++], &ctx->user); + } + } +#endif + for (el = obj->client->responses.head; el; el = el->next) { php_http_message_object_t *response_obj = *(php_http_message_object_t **) el->data; ZVAL_OBJ(&obj->gc[(*n)++], &response_obj->zo); } for (el = obj->client->requests.head; el; el = el->next) { - php_http_client_enqueue_t *q = *(php_http_client_enqueue_t **) el->data; + php_http_client_enqueue_t *q = (php_http_client_enqueue_t *) el->data; php_http_message_object_t *request_obj = q->opaque; /* FIXME */ ZVAL_OBJ(&obj->gc[(*n)++], &request_obj->zo); } diff --git a/src/php_http_client_curl.c b/src/php_http_client_curl.c index 423e12e..be17832 100644 --- a/src/php_http_client_curl.c +++ b/src/php_http_client_curl.c @@ -12,30 +12,11 @@ #include "php_http_api.h" #include "php_http_client.h" +#include "php_http_client_curl_event.h" +#include "php_http_client_curl_user.h" #if PHP_HTTP_HAVE_CURL -#if PHP_HTTP_HAVE_EVENT -# if !PHP_HTTP_HAVE_EVENT2 && /* just be really sure */ !(LIBEVENT_VERSION_NUMBER >= 0x02000000) -# include -# define event_base_new event_init -# define event_assign(e, b, s, a, cb, d) do {\ - event_set(e, s, a, cb, d); \ - event_base_set(b, e); \ - } while(0) -# else -# if PHP_HTTP_HAVE_EVENT2 -# include -# include -# else -# error "libevent presence is unknown" -# endif -# endif -# ifndef DBG_EVENTS -# define DBG_EVENTS 0 -# endif -#endif - #ifdef PHP_HTTP_HAVE_OPENSSL # include #endif @@ -43,23 +24,6 @@ # include #endif -typedef struct php_http_client_curl_handle { - CURLM *multi; - CURLSH *share; -} php_http_client_curl_handle_t; - -typedef struct php_http_client_curl { - php_http_client_curl_handle_t *handle; - - int unfinished; /* int because of curl_multi_perform() */ - -#if PHP_HTTP_HAVE_EVENT - struct event_base *evbase; - struct event *timeout; - unsigned useevents:1; -#endif -} php_http_client_curl_t; - typedef struct php_http_client_curl_handler { CURL *handle; php_resource_factory_t *rf; @@ -677,7 +641,7 @@ static php_http_message_t *php_http_curlm_responseparser(php_http_client_curl_ha return response; } -static void php_http_curlm_responsehandler(php_http_client_t *context) +void php_http_client_curl_responsehandler(php_http_client_t *context) { int err_count = 0, remaining = 0; php_http_curle_storage_t *st, *err = NULL; @@ -729,158 +693,26 @@ static void php_http_curlm_responsehandler(php_http_client_t *context) } } -#if PHP_HTTP_HAVE_EVENT - -typedef struct php_http_curlm_event { - struct event evnt; - php_http_client_t *context; -} php_http_curlm_event_t; - -static inline int etoca(short action) { - switch (action & (EV_READ|EV_WRITE)) { - case EV_READ: - return CURL_CSELECT_IN; - break; - case EV_WRITE: - return CURL_CSELECT_OUT; - break; - case EV_READ|EV_WRITE: - return CURL_CSELECT_IN|CURL_CSELECT_OUT; - break; - default: - return 0; - } -} - -static void php_http_curlm_timeout_callback(int socket, short action, void *event_data) -{ - php_http_client_t *context = event_data; - php_http_client_curl_t *curl = context->ctx; - -#if DBG_EVENTS - fprintf(stderr, "T"); -#endif - if (curl->useevents) { - CURLMcode rc; - - /* ignore and use -1,0 on timeout */ - (void) socket; - (void) action; - - while (CURLM_CALL_MULTI_PERFORM == (rc = curl_multi_socket_action(curl->handle->multi, CURL_SOCKET_TIMEOUT, 0, &curl->unfinished))); - - if (CURLM_OK != rc) { - php_error_docref(NULL, E_WARNING, "%s", curl_multi_strerror(rc)); - } - - php_http_curlm_responsehandler(context); - } -} - -static void php_http_curlm_event_callback(int socket, short action, void *event_data) +void php_http_client_curl_loop(php_http_client_t *client, curl_socket_t s, int curl_action) { - php_http_client_t *context = event_data; - php_http_client_curl_t *curl = context->ctx; - -#if DBG_EVENTS - fprintf(stderr, "E"); -#endif - if (curl->useevents) { - CURLMcode rc = CURLM_OK; - - while (CURLM_CALL_MULTI_PERFORM == (rc = curl_multi_socket_action(curl->handle->multi, socket, etoca(action), &curl->unfinished))); - - if (CURLM_OK != rc) { - php_error_docref(NULL, E_WARNING, "%s", curl_multi_strerror(rc)); - } - - php_http_curlm_responsehandler(context); - - /* remove timeout if there are no transfers left */ - if (!curl->unfinished && event_initialized(curl->timeout) && event_pending(curl->timeout, EV_TIMEOUT, NULL)) { - event_del(curl->timeout); - } - } -} - -static int php_http_curlm_socket_callback(CURL *easy, curl_socket_t sock, int action, void *socket_data, void *assign_data) -{ - php_http_client_t *context = socket_data; - php_http_client_curl_t *curl = context->ctx; + CURLMcode rc; + php_http_client_curl_t *curl = client->ctx; #if DBG_EVENTS - fprintf(stderr, "S"); + fprintf(stderr, "H"); #endif - if (curl->useevents) { - int events = EV_PERSIST; - php_http_curlm_event_t *ev = assign_data; - - if (!ev) { - ev = ecalloc(1, sizeof(php_http_curlm_event_t)); - ev->context = context; - curl_multi_assign(curl->handle->multi, sock, ev); - } else { - event_del(&ev->evnt); - } - switch (action) { - case CURL_POLL_IN: - events |= EV_READ; - break; - case CURL_POLL_OUT: - events |= EV_WRITE; - break; - case CURL_POLL_INOUT: - events |= EV_READ|EV_WRITE; - break; - - case CURL_POLL_REMOVE: - efree(ev); - /* no break */ - case CURL_POLL_NONE: - return 0; - - default: - php_error_docref(NULL, E_WARNING, "Unknown socket action %d", action); - return -1; - } + do { + rc = curl_multi_socket_action(curl->handle->multi, s, curl_action, &curl->unfinished); + } while (CURLM_CALL_MULTI_PERFORM == rc); - event_assign(&ev->evnt, curl->evbase, sock, events, php_http_curlm_event_callback, context); - event_add(&ev->evnt, NULL); + if (CURLM_OK != rc) { + php_error_docref(NULL, E_WARNING, "%s", curl_multi_strerror(rc)); } - return 0; + php_http_client_curl_responsehandler(client); } -static void php_http_curlm_timer_callback(CURLM *multi, long timeout_ms, void *timer_data) -{ - php_http_client_t *context = timer_data; - php_http_client_curl_t *curl = context->ctx; - -#if DBG_EVENTS - fprintf(stderr, "\ntimer <- timeout_ms: %ld\n", timeout_ms); -#endif - if (curl->useevents) { - - if (timeout_ms < 0) { - php_http_curlm_timeout_callback(CURL_SOCKET_TIMEOUT, /*EV_READ|EV_WRITE*/0, context); - } else if (timeout_ms > 0 || !event_initialized(curl->timeout) || !event_pending(curl->timeout, EV_TIMEOUT, NULL)) { - struct timeval timeout; - - if (!event_initialized(curl->timeout)) { - event_assign(curl->timeout, curl->evbase, CURL_SOCKET_TIMEOUT, 0, php_http_curlm_timeout_callback, context); - } - - timeout.tv_sec = timeout_ms / 1000; - timeout.tv_usec = (timeout_ms % 1000) * 1000; - - event_add(curl->timeout, &timeout); - } - } -} - -#endif /* HAVE_EVENT */ - /* curl options */ static php_http_options_t php_http_curle_options, php_http_curlm_options; @@ -1746,27 +1578,24 @@ static ZEND_RESULT_CODE php_http_curlm_option_set_pipelining_bl(php_http_option_ } #endif -#if PHP_HTTP_HAVE_EVENT -static inline ZEND_RESULT_CODE php_http_curlm_use_eventloop(php_http_client_t *h, zend_bool enable) +static inline ZEND_RESULT_CODE php_http_curlm_use_eventloop(php_http_client_t *h, php_http_client_curl_ops_t *ev_ops, zval *init_data) { php_http_client_curl_t *curl = h->ctx; + void *ev_ctx; - if ((curl->useevents = enable)) { - if (!curl->evbase) { - curl->evbase = event_base_new(); - } - if (!curl->timeout) { - curl->timeout = ecalloc(1, sizeof(struct event)); + if (ev_ops) { + if (!(ev_ctx = ev_ops->init(h, init_data))) { + return FAILURE; } - curl_multi_setopt(curl->handle->multi, CURLMOPT_SOCKETDATA, h); - curl_multi_setopt(curl->handle->multi, CURLMOPT_SOCKETFUNCTION, php_http_curlm_socket_callback); - curl_multi_setopt(curl->handle->multi, CURLMOPT_TIMERDATA, h); - curl_multi_setopt(curl->handle->multi, CURLMOPT_TIMERFUNCTION, php_http_curlm_timer_callback); + curl->ev_ctx = ev_ctx; + curl->ev_ops = ev_ops; } else { - curl_multi_setopt(curl->handle->multi, CURLMOPT_SOCKETDATA, NULL); - curl_multi_setopt(curl->handle->multi, CURLMOPT_SOCKETFUNCTION, NULL); - curl_multi_setopt(curl->handle->multi, CURLMOPT_TIMERDATA, NULL); - curl_multi_setopt(curl->handle->multi, CURLMOPT_TIMERFUNCTION, NULL); + if (curl->ev_ops) { + if (curl->ev_ctx) { + curl->ev_ops->dtor(&curl->ev_ctx); + } + curl->ev_ops = NULL; + } } return SUCCESS; @@ -1775,10 +1604,18 @@ static inline ZEND_RESULT_CODE php_http_curlm_use_eventloop(php_http_client_t *h static ZEND_RESULT_CODE php_http_curlm_option_set_use_eventloop(php_http_option_t *opt, zval *value, void *userdata) { php_http_client_t *client = userdata; + php_http_client_curl_ops_t *ev_ops = NULL; - return php_http_curlm_use_eventloop(client, value && Z_TYPE_P(value) == IS_TRUE); -} + if (Z_TYPE_P(value) == IS_OBJECT && instanceof_function(Z_OBJCE_P(value), php_http_client_curl_user_get_class_entry())) { + ev_ops = php_http_client_curl_user_ops_get(); +#if PHP_HTTP_HAVE_EVENT + } else if (value && zend_is_true(value)) { + ev_ops = php_http_client_curl_event_ops_get(); #endif + } + + return php_http_curlm_use_eventloop(client, ev_ops, value); +} static ZEND_RESULT_CODE php_http_curlm_option_set_share_cookies(php_http_option_t *opt, zval *value, void *userdata) { @@ -1866,11 +1703,9 @@ static void php_http_curlm_options_init(php_http_options_t *registry) } #endif /* events */ -#if PHP_HTTP_HAVE_EVENT - if ((opt = php_http_option_register(registry, ZEND_STRL("use_eventloop"), 0, _IS_BOOL))) { + if ((opt = php_http_option_register(registry, ZEND_STRL("use_eventloop"), 0, 0))) { opt->setter = php_http_curlm_option_set_use_eventloop; } -#endif /* share */ if ((opt = php_http_option_register(registry, ZEND_STRL("share_cookies"), 0, _IS_BOOL))) { opt->setter = php_http_curlm_option_set_share_cookies; @@ -2219,19 +2054,9 @@ static void php_http_client_curl_dtor(php_http_client_t *h) { php_http_client_curl_t *curl = h->ctx; -#if PHP_HTTP_HAVE_EVENT - if (curl->timeout) { - if (event_initialized(curl->timeout) && event_pending(curl->timeout, EV_TIMEOUT, NULL)) { - event_del(curl->timeout); - } - efree(curl->timeout); - curl->timeout = NULL; - } - if (curl->evbase) { - event_base_free(curl->evbase); - curl->evbase = NULL; + if (curl->ev_ops) { + curl->ev_ops->dtor(&curl->ev_ctx); } -#endif curl->unfinished = 0; php_resource_factory_handle_dtor(h->rf, curl->handle); @@ -2366,17 +2191,6 @@ static void php_http_client_curl_reset(php_http_client_t *h) } } -static inline void php_http_client_curl_get_timeout(php_http_client_curl_t *curl, long max_tout, struct timeval *timeout) -{ - if ((CURLM_OK == curl_multi_timeout(curl->handle->multi, &max_tout)) && (max_tout > 0)) { - timeout->tv_sec = max_tout / 1000; - timeout->tv_usec = (max_tout % 1000) * 1000; - } else { - timeout->tv_sec = 0; - timeout->tv_usec = 1000; - } -} - #ifdef PHP_WIN32 # define SELECT_ERROR SOCKET_ERROR #else @@ -2390,22 +2204,9 @@ static ZEND_RESULT_CODE php_http_client_curl_wait(php_http_client_t *h, struct t struct timeval timeout; php_http_client_curl_t *curl = h->ctx; -#if PHP_HTTP_HAVE_EVENT - if (curl->useevents) { - if (!event_initialized(curl->timeout)) { - event_assign(curl->timeout, curl->evbase, CURL_SOCKET_TIMEOUT, 0, php_http_curlm_timeout_callback, h); - } else if (custom_timeout && timerisset(custom_timeout)) { - event_add(curl->timeout, custom_timeout); - } else if (!event_pending(curl->timeout, EV_TIMEOUT, NULL)) { - php_http_client_curl_get_timeout(curl, 1000, &timeout); - event_add(curl->timeout, &timeout); - } - - event_base_loop(curl->evbase, EVLOOP_ONCE); - - return SUCCESS; + if (curl->ev_ops) { + return curl->ev_ops->wait(curl->ev_ctx, custom_timeout); } -#endif FD_ZERO(&R); FD_ZERO(&W); @@ -2432,14 +2233,13 @@ static int php_http_client_curl_once(php_http_client_t *h) { php_http_client_curl_t *curl = h->ctx; -#if PHP_HTTP_HAVE_EVENT - if (curl->useevents) { - event_base_loop(curl->evbase, EVLOOP_NONBLOCK); - } else -#endif - while (CURLM_CALL_MULTI_PERFORM == curl_multi_perform(curl->handle->multi, &curl->unfinished)); + if (curl->ev_ops) { + curl->ev_ops->once(curl->ev_ctx); + } else { + while (CURLM_CALL_MULTI_PERFORM == curl_multi_perform(curl->handle->multi, &curl->unfinished)); + } - php_http_curlm_responsehandler(h); + php_http_client_curl_responsehandler(h); return curl->unfinished; @@ -2447,36 +2247,21 @@ static int php_http_client_curl_once(php_http_client_t *h) static ZEND_RESULT_CODE php_http_client_curl_exec(php_http_client_t *h) { -#if PHP_HTTP_HAVE_EVENT php_http_client_curl_t *curl = h->ctx; - if (curl->useevents) { - php_http_curlm_timeout_callback(CURL_SOCKET_TIMEOUT, /*EV_READ|EV_WRITE*/0, h); - do { - int ev_rc = event_base_dispatch(curl->evbase); - -#if DBG_EVENTS - fprintf(stderr, "%c", "X.0"[ev_rc+1]); -#endif + if (curl->ev_ops) { + return curl->ev_ops->exec(curl->ev_ctx); + } - if (ev_rc < 0) { - php_error_docref(NULL, E_ERROR, "Error in event_base_dispatch()"); - return FAILURE; - } - } while (curl->unfinished && !EG(exception)); - } else -#endif - { - while (php_http_client_curl_once(h) && !EG(exception)) { - if (SUCCESS != php_http_client_curl_wait(h, NULL)) { + while (php_http_client_curl_once(h) && !EG(exception)) { + if (SUCCESS != php_http_client_curl_wait(h, NULL)) { #ifdef PHP_WIN32 - /* see http://msdn.microsoft.com/library/en-us/winsock/winsock/windows_sockets_error_codes_2.asp */ - php_error_docref(NULL, E_WARNING, "WinSock error: %d", WSAGetLastError()); + /* see http://msdn.microsoft.com/library/en-us/winsock/winsock/windows_sockets_error_codes_2.asp */ + php_error_docref(NULL, E_WARNING, "WinSock error: %d", WSAGetLastError()); #else - php_error_docref(NULL, E_WARNING, "%s", strerror(errno)); + php_error_docref(NULL, E_WARNING, "%s", strerror(errno)); #endif - return FAILURE; - } + return FAILURE; } } @@ -2500,7 +2285,9 @@ static ZEND_RESULT_CODE php_http_client_curl_setopt(php_http_client_t *h, php_ht case PHP_HTTP_CLIENT_OPT_USE_EVENTS: #if PHP_HTTP_HAVE_EVENT - return php_http_curlm_use_eventloop(h, *(zend_bool *) arg); + return php_http_curlm_use_eventloop(h, (*(zend_bool *) arg) + ? php_http_client_curl_event_ops_get() + : NULL, NULL); break; #endif @@ -2598,6 +2385,7 @@ php_http_client_ops_t *php_http_client_curl_get_ops(void) return &php_http_client_curl_ops; } + PHP_MINIT_FUNCTION(http_client_curl) { curl_version_info_data *info; diff --git a/src/php_http_client_curl.h b/src/php_http_client_curl.h index 9128647..abd8f99 100644 --- a/src/php_http_client_curl.h +++ b/src/php_http_client_curl.h @@ -19,8 +19,46 @@ struct php_http_client_curl_globals { php_http_client_driver_t driver; }; +typedef struct php_http_client_curl_handle { + CURLM *multi; + CURLSH *share; +} php_http_client_curl_handle_t; + +typedef struct php_http_client_curl_ops { + void *(*init)(); + void (*dtor)(void **ctx_ptr); + ZEND_RESULT_CODE (*once)(void *ctx); + ZEND_RESULT_CODE (*wait)(void *ctx, struct timeval *custom_timeout); + ZEND_RESULT_CODE (*exec)(void *ctx); +} php_http_client_curl_ops_t; + +typedef struct php_http_client_curl { + php_http_client_curl_handle_t *handle; + + int unfinished; /* int because of curl_multi_perform() */ + + void *ev_ctx; + php_http_client_curl_ops_t *ev_ops; +} php_http_client_curl_t; + +static inline void php_http_client_curl_get_timeout(php_http_client_curl_t *curl, long max_tout, struct timeval *timeout) +{ + if ((CURLM_OK == curl_multi_timeout(curl->handle->multi, &max_tout)) && (max_tout > 0)) { + timeout->tv_sec = max_tout / 1000; + timeout->tv_usec = (max_tout % 1000) * 1000; + } else { + timeout->tv_sec = 0; + timeout->tv_usec = 1000; + } +} + +PHP_HTTP_API void php_http_client_curl_responsehandler(php_http_client_t *client); +PHP_HTTP_API void php_http_client_curl_loop(php_http_client_t *client, curl_socket_t s, int curl_action); +PHP_HTTP_API php_http_client_ops_t *php_http_client_curl_get_ops(void); + PHP_MINIT_FUNCTION(http_client_curl); PHP_MSHUTDOWN_FUNCTION(http_client_curl); + #endif /* PHP_HTTP_HAVE_CURL */ #endif /* PHP_HTTP_CLIENT_CURL_H */ diff --git a/src/php_http_client_curl_event.c b/src/php_http_client_curl_event.c new file mode 100644 index 0000000..e1ba505 --- /dev/null +++ b/src/php_http_client_curl_event.c @@ -0,0 +1,327 @@ +/* + +--------------------------------------------------------------------+ + | PECL :: http | + +--------------------------------------------------------------------+ + | Redistribution and use in source and binary forms, with or without | + | modification, are permitted provided that the conditions mentioned | + | in the accompanying LICENSE file are met. | + +--------------------------------------------------------------------+ + | Copyright (c) 2004-2014, Michael Wallner | + +--------------------------------------------------------------------+ +*/ + +#include "php_http_api.h" + +#if PHP_HTTP_HAVE_CURL +#if PHP_HTTP_HAVE_EVENT +# if !PHP_HTTP_HAVE_EVENT2 && /* just be really sure */ !(LIBEVENT_VERSION_NUMBER >= 0x02000000) +# include +# define event_base_new event_init +# define event_assign(e, b, s, a, cb, d) do {\ + event_set(e, s, a, cb, d); \ + event_base_set(b, e); \ + } while(0) +# else +# if PHP_HTTP_HAVE_EVENT2 +# include +# include +# else +# error "libevent presence is unknown" +# endif +# endif +# ifndef DBG_EVENTS +# define DBG_EVENTS 0 +# endif + +typedef struct php_http_client_curl_event_context { + php_http_client_t *client; + struct event_base *evbase; + struct event *timeout; +} php_http_client_curl_event_context_t; + +typedef struct php_http_client_curl_event_ev { + struct event evnt; + php_http_client_curl_event_context_t *context; +} php_http_client_curl_event_ev_t; + +static inline int etoca(short action) { + switch (action & (EV_READ|EV_WRITE)) { + case EV_READ: + return CURL_CSELECT_IN; + break; + case EV_WRITE: + return CURL_CSELECT_OUT; + break; + case EV_READ|EV_WRITE: + return CURL_CSELECT_IN|CURL_CSELECT_OUT; + break; + default: + return 0; + } +} + +static void php_http_client_curl_event_handler(void *context, curl_socket_t s, int curl_action) +{ + CURLMcode rc; + php_http_client_curl_event_context_t *ctx = context; + php_http_client_curl_t *curl = ctx->client->ctx; + +#if DBG_EVENTS + fprintf(stderr, "H"); +#endif + + do { + rc = curl_multi_socket_action(curl->handle->multi, s, curl_action, &curl->unfinished); + } while (CURLM_CALL_MULTI_PERFORM == rc); + + if (CURLM_OK != rc) { + php_error_docref(NULL, E_WARNING, "%s", curl_multi_strerror(rc)); + } + + php_http_client_curl_responsehandler(ctx->client); +} + +static void php_http_client_curl_event_timeout_callback(int socket, short action, void *event_data) +{ +#if DBG_EVENTS + fprintf(stderr, "T"); +#endif + + /* ignore and use -1,0 on timeout */ + (void) socket; + (void) action; + + php_http_client_curl_event_handler(event_data, CURL_SOCKET_TIMEOUT, 0); +} + +static void php_http_client_curl_event_timer(CURLM *multi, long timeout_ms, void *timer_data) +{ + php_http_client_curl_event_context_t *context = timer_data; + +#if DBG_EVENTS + fprintf(stderr, "\ntimer <- timeout_ms: %ld\n", timeout_ms); +#endif + + if (timeout_ms < 0) { + php_http_client_curl_event_handler(context, CURL_SOCKET_TIMEOUT, 0); + } else if (timeout_ms > 0 || !event_initialized(context->timeout) || !event_pending(context->timeout, EV_TIMEOUT, NULL)) { + struct timeval timeout; + + if (!event_initialized(context->timeout)) { + event_assign(context->timeout, context->evbase, CURL_SOCKET_TIMEOUT, 0, php_http_client_curl_event_timeout_callback, context); + } + + timeout.tv_sec = timeout_ms / 1000; + timeout.tv_usec = (timeout_ms % 1000) * 1000; + + event_add(context->timeout, &timeout); + } +} + +static void php_http_client_curl_event_callback(int socket, short action, void *event_data) +{ + php_http_client_curl_event_context_t *ctx = event_data; + php_http_client_curl_t *curl = ctx->client->ctx; + +#if DBG_EVENTS + fprintf(stderr, "E"); +#endif + + php_http_client_curl_event_handler(event_data, socket, etoca(action)); + + /* remove timeout if there are no transfers left */ + if (!curl->unfinished && event_initialized(ctx->timeout) && event_pending(ctx->timeout, EV_TIMEOUT, NULL)) { + event_del(ctx->timeout); + } +} + +static int php_http_client_curl_event_socket(CURL *easy, curl_socket_t sock, int action, void *socket_data, void *assign_data) +{ + php_http_client_curl_event_context_t *ctx = socket_data; + php_http_client_curl_t *curl = ctx->client->ctx; + int events = EV_PERSIST; + php_http_client_curl_event_ev_t *ev = assign_data; + +#if DBG_EVENTS + fprintf(stderr, "S"); +#endif + + if (!ev) { + ev = ecalloc(1, sizeof(*ev)); + ev->context = ctx; + curl_multi_assign(curl->handle->multi, sock, ev); + } else { + event_del(&ev->evnt); + } + + switch (action) { + case CURL_POLL_IN: + events |= EV_READ; + break; + case CURL_POLL_OUT: + events |= EV_WRITE; + break; + case CURL_POLL_INOUT: + events |= EV_READ|EV_WRITE; + break; + + case CURL_POLL_REMOVE: + efree(ev); + /* no break */ + case CURL_POLL_NONE: + return 0; + + default: + php_error_docref(NULL, E_WARNING, "Unknown socket action %d", action); + return -1; + } + + event_assign(&ev->evnt, ctx->evbase, sock, events, php_http_client_curl_event_callback, ctx); + event_add(&ev->evnt, NULL); + + return 0; +} + +static ZEND_RESULT_CODE php_http_client_curl_event_once(void *context) +{ + php_http_client_curl_event_context_t *ctx = context; + +#if DBG_EVENTS + fprintf(stderr, "O"); +#endif + + if (0 > event_base_loop(ctx->evbase, EVLOOP_NONBLOCK)) { + return FAILURE; + } + return SUCCESS; +} + +static ZEND_RESULT_CODE php_http_client_curl_event_wait(void *context, struct timeval *custom_timeout) +{ + php_http_client_curl_event_context_t *ctx = context; + struct timeval timeout; + +#if DBG_EVENTS + fprintf(stderr, "W"); +#endif + + if (!event_initialized(ctx->timeout)) { + if (0 > event_assign(ctx->timeout, ctx->evbase, CURL_SOCKET_TIMEOUT, 0, php_http_client_curl_event_timeout_callback, ctx)) { + return FAILURE; + } + } else if (custom_timeout && timerisset(custom_timeout)) { + if (0 > event_add(ctx->timeout, custom_timeout)) { + return FAILURE; + } + } else if (!event_pending(ctx->timeout, EV_TIMEOUT, NULL)) { + php_http_client_curl_get_timeout(ctx->client->ctx, 1000, &timeout); + if (0 > event_add(ctx->timeout, &timeout)) { + return FAILURE; + } + } + + if (0 > event_base_loop(ctx->evbase, EVLOOP_ONCE)) { + return FAILURE; + } + + return SUCCESS; +} + +static ZEND_RESULT_CODE php_http_client_curl_event_exec(void *context) +{ + php_http_client_curl_event_context_t *ctx = context; + php_http_client_curl_t *curl = ctx->client->ctx; + +#if DBG_EVENTS + fprintf(stderr, "E"); +#endif + + /* kickstart */ + php_http_client_curl_event_handler(ctx, CURL_SOCKET_TIMEOUT, 0); + + do { + if (0 > event_base_dispatch(ctx->evbase)) { + return FAILURE; + } + } while (curl->unfinished && !EG(exception)); + + return SUCCESS; +} + +static void *php_http_client_curl_event_init(php_http_client_t *client) +{ + php_http_client_curl_t *curl = client->ctx; + php_http_client_curl_event_context_t *ctx; + struct event_base *evb = event_base_new(); + +#if DBG_EVENTS + fprintf(stderr, "I"); +#endif + + if (!evb) { + return NULL; + } + + ctx = ecalloc(1, sizeof(*ctx)); + ctx->client = client; + ctx->evbase = evb; + ctx->timeout = ecalloc(1, sizeof(struct event)); + + curl_multi_setopt(curl->handle->multi, CURLMOPT_SOCKETDATA, ctx); + curl_multi_setopt(curl->handle->multi, CURLMOPT_SOCKETFUNCTION, php_http_client_curl_event_socket); + curl_multi_setopt(curl->handle->multi, CURLMOPT_TIMERDATA, ctx); + curl_multi_setopt(curl->handle->multi, CURLMOPT_TIMERFUNCTION, php_http_client_curl_event_timer); + + return ctx; +} + +static void php_http_client_curl_event_dtor(void **context) +{ + php_http_client_curl_event_context_t *ctx = *context; + php_http_client_curl_t *curl; + +#if DBG_EVENTS + fprintf(stderr, "D"); +#endif + + curl = ctx->client->ctx; + + curl_multi_setopt(curl->handle->multi, CURLMOPT_SOCKETDATA, NULL); + curl_multi_setopt(curl->handle->multi, CURLMOPT_SOCKETFUNCTION, NULL); + curl_multi_setopt(curl->handle->multi, CURLMOPT_TIMERDATA, NULL); + curl_multi_setopt(curl->handle->multi, CURLMOPT_TIMERFUNCTION, NULL); + + if (event_initialized(ctx->timeout) && event_pending(ctx->timeout, EV_TIMEOUT, NULL)) { + event_del(ctx->timeout); + } + efree(ctx->timeout); + event_base_free(ctx->evbase); + + efree(ctx); + *context = NULL; +} + +static php_http_client_curl_ops_t php_http_client_curl_event_ops = { + &php_http_client_curl_event_init, + &php_http_client_curl_event_dtor, + &php_http_client_curl_event_once, + &php_http_client_curl_event_wait, + &php_http_client_curl_event_exec, +}; + +php_http_client_curl_ops_t *php_http_client_curl_event_ops_get() +{ + return &php_http_client_curl_event_ops; +} + +#endif /* PHP_HTTP_HAVE_EVENT */ +#endif /* PHP_HTTP_HAVE_CURL */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/src/php_http_client_curl_event.h b/src/php_http_client_curl_event.h new file mode 100644 index 0000000..4739f62 --- /dev/null +++ b/src/php_http_client_curl_event.h @@ -0,0 +1,33 @@ +/* + +--------------------------------------------------------------------+ + | PECL :: http | + +--------------------------------------------------------------------+ + | Redistribution and use in source and binary forms, with or without | + | modification, are permitted provided that the conditions mentioned | + | in the accompanying LICENSE file are met. | + +--------------------------------------------------------------------+ + | Copyright (c) 2004-2014, Michael Wallner | + +--------------------------------------------------------------------+ +*/ + +#ifndef PHP_HTTP_CLIENT_CURL_EVENT_H +#define PHP_HTTP_CLIENT_CURL_EVENT_H + +#if PHP_HTTP_HAVE_CURL +#if PHP_HTTP_HAVE_EVENT + +php_http_client_curl_ops_t *php_http_client_curl_event_ops_get(); + +#endif +#endif + +#endif + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/src/php_http_client_curl_user.c b/src/php_http_client_curl_user.c new file mode 100644 index 0000000..a30d666 --- /dev/null +++ b/src/php_http_client_curl_user.c @@ -0,0 +1,323 @@ +/* + +--------------------------------------------------------------------+ + | PECL :: http | + +--------------------------------------------------------------------+ + | Redistribution and use in source and binary forms, with or without | + | modification, are permitted provided that the conditions mentioned | + | in the accompanying LICENSE file are met. | + +--------------------------------------------------------------------+ + | Copyright (c) 2004-2014, Michael Wallner | + +--------------------------------------------------------------------+ +*/ + +#include "php_http_api.h" + +#include "php_network.h" +#include "zend_closures.h" + +#if PHP_HTTP_HAVE_CURL + +typedef struct php_http_client_curl_user_ev { + php_stream *socket; + php_http_client_curl_user_context_t *context; +} php_http_client_curl_user_ev_t; + +static void php_http_client_curl_user_handler(INTERNAL_FUNCTION_PARAMETERS) +{ + zval *zstream = NULL, *zclient = NULL; + php_stream *stream = NULL; + long action = 0; + php_socket_t fd = CURL_SOCKET_TIMEOUT; + php_http_client_object_t *client = NULL; + + if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS(), "O|rl", &zclient, php_http_client_get_class_entry(), &zstream, &action)) { + return; + } + + client = PHP_HTTP_OBJ(NULL, zclient); + if (zstream) { + php_stream_from_zval(stream, zstream); + + if (SUCCESS != php_stream_cast(stream, PHP_STREAM_AS_SOCKETD, (void *) &fd, 1)) { + return; + } + } + php_http_client_curl_loop(client->client, fd, action); +} + +static void php_http_client_curl_user_timer(CURLM *multi, long timeout_ms, void *timer_data) +{ + php_http_client_curl_user_context_t *context = timer_data; + +#if DBG_EVENTS + fprintf(stderr, "\ntimer <- timeout_ms: %ld\n", timeout_ms); +#endif + + if (timeout_ms <= 0) { + php_http_client_curl_loop(context->client, CURL_SOCKET_TIMEOUT, 0); + } else if (timeout_ms > 0) { + zval args[1], *ztimeout = &args[0]; + + ZVAL_LONG(ztimeout, timeout_ms); + php_http_object_method_call(&context->timer, &context->user, NULL, 1, args); + zval_ptr_dtor(ztimeout); + } +} + +static int php_http_client_curl_user_socket(CURL *easy, curl_socket_t sock, int action, void *socket_data, void *assign_data) +{ + php_http_client_curl_user_context_t *ctx = socket_data; + php_http_client_curl_t *curl = ctx->client->ctx; + php_http_client_curl_user_ev_t *ev = assign_data; + zval args[2], *zaction = &args[1], *zsocket = &args[0]; + +#if DBG_EVENTS + fprintf(stderr, "S"); +#endif + + if (!ev) { + ev = ecalloc(1, sizeof(*ev)); + ev->context = ctx; + ev->socket = php_stream_sock_open_from_socket(sock, NULL); + + curl_multi_assign(curl->handle->multi, sock, ev); + } + + switch (action) { + case CURL_POLL_IN: + case CURL_POLL_OUT: + case CURL_POLL_INOUT: + case CURL_POLL_REMOVE: + case CURL_POLL_NONE: + php_stream_to_zval(ev->socket, zsocket); + Z_TRY_ADDREF_P(zsocket); + ZVAL_LONG(zaction, action); + php_http_object_method_call(&ctx->socket, &ctx->user, NULL, 2, args); + zval_ptr_dtor(zsocket); + zval_ptr_dtor(zaction); + break; + + default: + php_error_docref(NULL, E_WARNING, "Unknown socket action %d", action); + return -1; + } + + if (action == CURL_POLL_REMOVE && ev) { + php_stream_close(ev->socket); + efree(ev); + curl_multi_assign(curl->handle->multi, sock, NULL); + } + return 0; +} + +static ZEND_RESULT_CODE php_http_client_curl_user_once(void *context) +{ + php_http_client_curl_user_context_t *ctx = context; + +#if DBG_EVENTS + fprintf(stderr, "O"); +#endif + + return php_http_object_method_call(&ctx->once, &ctx->user, NULL, 0, NULL); +} + +static ZEND_RESULT_CODE php_http_client_curl_user_wait(void *context, struct timeval *custom_timeout) +{ + php_http_client_curl_user_context_t *ctx = context; + struct timeval timeout; + zval args[1], *ztimeout = &args[0]; + ZEND_RESULT_CODE rv; + +#if DBG_EVENTS + fprintf(stderr, "W"); +#endif + + if (!custom_timeout || !timerisset(custom_timeout)) { + php_http_client_curl_get_timeout(ctx->client->ctx, 1000, &timeout); + custom_timeout = &timeout; + } + + ZVAL_LONG(ztimeout, custom_timeout->tv_sec * 1000 + custom_timeout->tv_usec / 1000); + rv = php_http_object_method_call(&ctx->wait, &ctx->user, NULL, 1, args); + zval_ptr_dtor(ztimeout); + + return rv; +} + +static ZEND_RESULT_CODE php_http_client_curl_user_exec(void *context) +{ + php_http_client_curl_user_context_t *ctx = context; + php_http_client_curl_t *curl = ctx->client->ctx; + +#if DBG_EVENTS + fprintf(stderr, "E"); +#endif + + /* kickstart */ + php_http_client_curl_loop(ctx->client, CURL_SOCKET_TIMEOUT, 0); + + do { + if (SUCCESS != php_http_object_method_call(&ctx->send, &ctx->user, NULL, 0, NULL)) { + return FAILURE; + } + } while (curl->unfinished && !EG(exception)); + + return SUCCESS; +} + +static void *php_http_client_curl_user_init(php_http_client_t *client, void *user_data) +{ + php_http_client_curl_t *curl = client->ctx; + php_http_client_curl_user_context_t *ctx; + php_http_object_method_t init; + zval args[1], *zclosure = &args[0]; + +#if DBG_EVENTS + fprintf(stderr, "I"); +#endif + + ctx = ecalloc(1, sizeof(*ctx)); + ctx->client = client; + ZVAL_COPY(&ctx->user, user_data); + + memset(&ctx->closure, 0, sizeof(ctx->closure)); + ctx->closure.common.type = ZEND_INTERNAL_FUNCTION; + ctx->closure.common.function_name = zend_string_init(ZEND_STRL("php_http_client_curl_user_handler"), 0); + ctx->closure.internal_function.handler = php_http_client_curl_user_handler; + + zend_create_closure(zclosure, &ctx->closure, NULL, NULL, NULL); + + php_http_object_method_init(&init, &ctx->user, ZEND_STRL("init")); + php_http_object_method_call(&init, &ctx->user, NULL, 1, args); + php_http_object_method_dtor(&init); + zval_ptr_dtor(zclosure); + + php_http_object_method_init(&ctx->timer, &ctx->user, ZEND_STRL("timer")); + php_http_object_method_init(&ctx->socket, &ctx->user, ZEND_STRL("socket")); + php_http_object_method_init(&ctx->once, &ctx->user, ZEND_STRL("once")); + php_http_object_method_init(&ctx->wait, &ctx->user, ZEND_STRL("wait")); + php_http_object_method_init(&ctx->send, &ctx->user, ZEND_STRL("send")); + + curl_multi_setopt(curl->handle->multi, CURLMOPT_SOCKETDATA, ctx); + curl_multi_setopt(curl->handle->multi, CURLMOPT_SOCKETFUNCTION, php_http_client_curl_user_socket); + curl_multi_setopt(curl->handle->multi, CURLMOPT_TIMERDATA, ctx); + curl_multi_setopt(curl->handle->multi, CURLMOPT_TIMERFUNCTION, php_http_client_curl_user_timer); + + return ctx; +} + +static void php_http_client_curl_user_dtor(void **context) +{ + php_http_client_curl_user_context_t *ctx = *context; + php_http_client_curl_t *curl; + +#if DBG_EVENTS + fprintf(stderr, "D"); +#endif + + curl = ctx->client->ctx; + + curl_multi_setopt(curl->handle->multi, CURLMOPT_SOCKETDATA, NULL); + curl_multi_setopt(curl->handle->multi, CURLMOPT_SOCKETFUNCTION, NULL); + curl_multi_setopt(curl->handle->multi, CURLMOPT_TIMERDATA, NULL); + curl_multi_setopt(curl->handle->multi, CURLMOPT_TIMERFUNCTION, NULL); + + php_http_object_method_dtor(&ctx->timer); + php_http_object_method_dtor(&ctx->socket); + php_http_object_method_dtor(&ctx->once); + php_http_object_method_dtor(&ctx->wait); + php_http_object_method_dtor(&ctx->send); + + zend_string_release(ctx->closure.common.function_name); + zval_ptr_dtor(&ctx->user); + + efree(ctx); + *context = NULL; +} + +static php_http_client_curl_ops_t php_http_client_curl_user_ops = { + &php_http_client_curl_user_init, + &php_http_client_curl_user_dtor, + &php_http_client_curl_user_once, + &php_http_client_curl_user_wait, + &php_http_client_curl_user_exec, +}; + +php_http_client_curl_ops_t *php_http_client_curl_user_ops_get() +{ + return &php_http_client_curl_user_ops; +} + +static zend_class_entry *php_http_client_curl_user_class_entry; + +zend_class_entry *php_http_client_curl_user_get_class_entry() +{ + return php_http_client_curl_user_class_entry; +} + +ZEND_BEGIN_ARG_INFO_EX(ai_init, 0, 0, 1) + ZEND_ARG_TYPE_INFO(0, run, IS_CALLABLE, 0) +ZEND_END_ARG_INFO(); +ZEND_BEGIN_ARG_INFO_EX(ai_timer, 0, 0, 1) +#if PHP_VERSION_ID >= 70000 + ZEND_ARG_TYPE_INFO(0, timeout_ms, IS_LONG, 0) +#else + ZEND_ARG_INFO(0, timeout_ms) +#endif +ZEND_END_ARG_INFO(); +ZEND_BEGIN_ARG_INFO_EX(ai_socket, 0, 0, 2) + ZEND_ARG_INFO(0, socket) +#if PHP_VERSION_ID >= 70000 + ZEND_ARG_TYPE_INFO(0, action, IS_LONG, 0) +#else + ZEND_ARG_INFO(0, action) +#endif +ZEND_END_ARG_INFO(); +ZEND_BEGIN_ARG_INFO_EX(ai_once, 0, 0, 0) +ZEND_END_ARG_INFO(); +ZEND_BEGIN_ARG_INFO_EX(ai_wait, 0, 0, 0) +#if PHP_VERSION_ID >= 70000 + ZEND_ARG_TYPE_INFO(0, timeout_ms, IS_LONG, 0) +#else + ZEND_ARG_INFO(0, timeout_ms) +#endif +ZEND_END_ARG_INFO(); +ZEND_BEGIN_ARG_INFO_EX(ai_send, 0, 0, 0) +ZEND_END_ARG_INFO(); + +static zend_function_entry php_http_client_curl_user_methods[] = { + PHP_ABSTRACT_ME(HttpClientCurlUser, init, ai_init) + PHP_ABSTRACT_ME(HttpClientCurlUser, timer, ai_timer) + PHP_ABSTRACT_ME(HttpClientCurlUser, socket, ai_socket) + PHP_ABSTRACT_ME(HttpClientCurlUser, once, ai_once) + PHP_ABSTRACT_ME(HttpClientCurlUser, wait, ai_wait) + PHP_ABSTRACT_ME(HttpClientCulrUser, send, ai_send) + {0} +}; + +PHP_MINIT_FUNCTION(http_client_curl_user) +{ + zend_class_entry ce = {0}; + + INIT_NS_CLASS_ENTRY(ce, "http\\Client\\Curl", "User", php_http_client_curl_user_methods); + php_http_client_curl_user_class_entry = zend_register_internal_interface(&ce); + + zend_declare_class_constant_long(php_http_client_curl_user_class_entry, ZEND_STRL("POLL_NONE"), CURL_POLL_NONE); + zend_declare_class_constant_long(php_http_client_curl_user_class_entry, ZEND_STRL("POLL_IN"), CURL_POLL_IN); + zend_declare_class_constant_long(php_http_client_curl_user_class_entry, ZEND_STRL("POLL_OUT"), CURL_POLL_OUT); + zend_declare_class_constant_long(php_http_client_curl_user_class_entry, ZEND_STRL("POLL_INOUT"), CURL_POLL_INOUT); + zend_declare_class_constant_long(php_http_client_curl_user_class_entry, ZEND_STRL("POLL_REMOVE"), CURL_POLL_REMOVE); + + return SUCCESS; +} + +#endif /* PHP_HTTP_HAVE_CURL */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/src/php_http_client_curl_user.h b/src/php_http_client_curl_user.h new file mode 100644 index 0000000..35f5d6f --- /dev/null +++ b/src/php_http_client_curl_user.h @@ -0,0 +1,105 @@ +/* + +--------------------------------------------------------------------+ + | PECL :: http | + +--------------------------------------------------------------------+ + | Redistribution and use in source and binary forms, with or without | + | modification, are permitted provided that the conditions mentioned | + | in the accompanying LICENSE file are met. | + +--------------------------------------------------------------------+ + | Copyright (c) 2004-2014, Michael Wallner | + +--------------------------------------------------------------------+ +*/ + +#ifndef PHP_HTTP_CLIENT_CURL_USER_H +#define PHP_HTTP_CLIENT_CURL_USER_H + +#if PHP_HTTP_HAVE_CURL + +typedef struct php_http_client_curl_user_context { + php_http_client_t *client; + zval user; + zend_function closure; + php_http_object_method_t timer; + php_http_object_method_t socket; + php_http_object_method_t once; + php_http_object_method_t wait; + php_http_object_method_t send; +} php_http_client_curl_user_context_t; + +PHP_HTTP_API zend_class_entry *php_http_client_curl_user_get_class_entry(); +PHP_HTTP_API php_http_client_curl_ops_t *php_http_client_curl_user_ops_get(); +PHP_MINIT_FUNCTION(http_client_curl_user); + +#endif + +#if 0 +res = stream->res; - ++GC_REFCOUNT(body->res); } else { body->res = php_stream_temp_create(TEMP_STREAM_DEFAULT, 0xffff)->res; } + ++GC_REFCOUNT(body->res); if (body_ptr) { *body_ptr = body; diff --git a/src/php_http_negotiate.c b/src/php_http_negotiate.c index 9fdad26..cd09d37 100644 --- a/src/php_http_negotiate.c +++ b/src/php_http_negotiate.c @@ -12,6 +12,10 @@ #include "php_http_api.h" +#ifndef PHP_HTTP_DEBUG_NEG +# define PHP_HTTP_DEBUG_NEG 0 +#endif + static int php_http_negotiate_sort(const void *first, const void *second) { Bucket *b1 = (Bucket *) first, *b2 = (Bucket *) second; @@ -22,7 +26,7 @@ static int php_http_negotiate_sort(const void *first, const void *second) #define M_PRI 5 #define M_SEC 2 -#define M_ANY 1 +#define M_ANY -1 #define M_NOT 0 #define M_ALL ~0 static inline unsigned php_http_negotiate_match(const char *param_str, size_t param_len, const char *supported_str, size_t supported_len, const char *sep_str, size_t sep_len) @@ -46,7 +50,11 @@ static inline unsigned php_http_negotiate_match(const char *param_str, size_t pa match += M_PRI; } - if (param_sec && supported_sec && !strcasecmp(param_sec, supported_sec)) { + if (param_sec && supported_sec + && ((*(param_sec + sep_len) == '*' || *(supported_sec + sep_len) == '*') + || !strcasecmp(param_sec, supported_sec) + ) + ) { match += M_SEC; } @@ -57,7 +65,7 @@ static inline unsigned php_http_negotiate_match(const char *param_str, size_t pa match += M_ANY; } } -#if 0 +#if PHP_HTTP_DEBUG_NEG fprintf(stderr, "match: %s == %s => %u\n", supported_str, param_str, match); #endif return match; @@ -65,8 +73,9 @@ static inline unsigned php_http_negotiate_match(const char *param_str, size_t pa static int php_http_negotiate_reduce(zval *p, int num_args, va_list args, zend_hash_key *hash_key) { unsigned best_match = 0; + double q = 0; php_http_arrkey_t key; - zval *value, *q = NULL; + zval *value; zend_string *supported = zval_get_string(p); HashTable *params = va_arg(args, HashTable *); HashTable *result = va_arg(args, HashTable *); @@ -77,20 +86,25 @@ static int php_http_negotiate_reduce(zval *p, int num_args, va_list args, zend_h { unsigned match; +#if PHP_HTTP_DEBUG_NEG + fprintf(stderr, "match(%u) > best_match(%u) = %u (q=%f)\n", match, best_match, match>best_match, Z_DVAL_PP(val)); +#endif php_http_arrkey_stringify(&key, NULL); match = php_http_negotiate_match(key.key->val, key.key->len, supported->val, supported->len, sep_str, sep_len); if (match > best_match) { best_match = match; - q = value; + q = Z_DVAL_P(value) - 0.1 / match; } php_http_arrkey_dtor(&key); } ZEND_HASH_FOREACH_END(); - if (q && Z_DVAL_P(q) > 0) { - Z_TRY_ADDREF_P(q); - zend_hash_update(result, supported, q); + if (q > 0) { + zval tmp; + + ZVAL_DOUBLE(&tmp, q); + zend_hash_update(result, supported, &tmp); } zend_string_release(supported); @@ -112,6 +126,7 @@ HashTable *php_http_negotiate(const char *value_str, size_t value_len, HashTable php_http_params_opts_default_get(&opts); opts.input.str = estrndup(value_str, value_len); opts.input.len = value_len; + opts.flags &= ~PHP_HTTP_PARAMS_RFC5987; php_http_params_parse(¶ms, &opts); efree(opts.input.str); @@ -126,7 +141,7 @@ HashTable *php_http_negotiate(const char *value_str, size_t value_len, HashTable && (zq = zend_hash_str_find(Z_ARRVAL_P(arg), ZEND_STRL("q")))) { q = zval_get_double(zq); } else { - q = 1.0 - ++i / 100.0; + q = 1.0 - (((double) ++i) / 100.0); } #if 0 @@ -142,7 +157,7 @@ HashTable *php_http_negotiate(const char *value_str, size_t value_len, HashTable } ZEND_HASH_FOREACH_END(); -#if 0 +#if PHP_HTTP_DEBUG_NEG zend_print_zval_r(&arr, 1); #endif diff --git a/src/php_http_params.c b/src/php_http_params.c index 8db5c35..c9feccb 100644 --- a/src/php_http_params.c +++ b/src/php_http_params.c @@ -253,11 +253,13 @@ static inline void sanitize_key(unsigned flags, const char *str, size_t len, zva return; } - eos = &Z_STRVAL_P(zv)[Z_STRLEN_P(zv)-1]; - if (*eos == '*') { - *eos = '\0'; - *rfc5987 = 1; - Z_STRLEN_P(zv) -= 1; + if (flags & PHP_HTTP_PARAMS_RFC5987) { + eos = &Z_STRVAL_P(zv)[Z_STRLEN_P(zv)-1]; + if (*eos == '*') { + *eos = '\0'; + *rfc5987 = 1; + Z_STRLEN_P(zv) -= 1; + } } if (flags & PHP_HTTP_PARAMS_URLENCODED) { @@ -554,6 +556,8 @@ static void push_param(HashTable *params, php_http_params_state_t *state, const if (state->val.str) { if (0 < (state->val.len = state->input.str - state->val.str)) { sanitize_value(opts->flags, state->val.str, state->val.len, state->current.val, state->rfc5987); + } else { + ZVAL_EMPTY_STRING(state->current.val); } state->rfc5987 = 0; } else if (state->arg.str) { diff --git a/src/php_http_querystring.c b/src/php_http_querystring.c index ea84d8d..d45cd49 100644 --- a/src/php_http_querystring.c +++ b/src/php_http_querystring.c @@ -171,6 +171,25 @@ static int apply_querystring(zval *val) return ZEND_HASH_APPLY_KEEP; } +static int apply_querystring_filter(zval *val) +{ + switch (Z_TYPE_P(val)) { + case IS_NULL: + return ZEND_HASH_APPLY_REMOVE; + case IS_ARRAY: + case IS_OBJECT: + zend_hash_apply(HASH_OF(val), apply_querystring_filter); + if (!zend_hash_num_elements(HASH_OF(val))) { + return ZEND_HASH_APPLY_REMOVE; + } + break; + default: + break; + } + + return ZEND_HASH_APPLY_KEEP; +} + ZEND_RESULT_CODE php_http_querystring_parse(HashTable *ht, const char *str, size_t len) { ZEND_RESULT_CODE rv = FAILURE; @@ -200,7 +219,7 @@ ZEND_RESULT_CODE php_http_querystring_parse(HashTable *ht, const char *str, size zval_ptr_dtor(&arr); } - ZVAL_NULL(&opts.defval); + ZVAL_TRUE(&opts.defval); if (php_http_params_parse(ht, &opts)) { zend_hash_apply(ht, apply_querystring); @@ -224,7 +243,9 @@ ZEND_RESULT_CODE php_http_querystring_update(zval *qarray, zval *params, zval *o } /* modify qarray */ - if (params) { + if (!params) { + zend_hash_apply(Z_ARRVAL_P(qarray), apply_querystring_filter); + } else { HashTable *ht; php_http_arrkey_t key; zval zv, *params_entry, *qarray_entry; @@ -276,7 +297,7 @@ ZEND_RESULT_CODE php_http_querystring_update(zval *qarray, zval *params, zval *o ZVAL_DUP(entry, qarray_entry); convert_to_array(entry); php_http_querystring_update(entry, params_entry, NULL); - } else if ((FAILURE == is_equal_function(&equal, qarray_entry, params_entry)) || Z_TYPE(equal) != IS_TRUE) { + } else if ((FAILURE == is_identical_function(&equal, qarray_entry, params_entry)) || Z_TYPE(equal) != IS_TRUE) { Z_TRY_ADDREF_P(params_entry); entry = params_entry; } diff --git a/tests/bug61444.phpt b/tests/bug61444.phpt index 28d267c..5362c71 100644 --- a/tests/bug61444.phpt +++ b/tests/bug61444.phpt @@ -29,7 +29,7 @@ DONE --EXPECT-- http://www.example.com/foobar?bar.baz=blah&utm_source=google&utm_campaign=somethingelse&blat -http://www.example.com/foobar?bar.baz=blah&utm_source=changed&utm_campaign=somethingelse +http://www.example.com/foobar?bar.baz=blah&utm_source=changed&utm_campaign=somethingelse&blat http://www.google.com/foobar?bar.baz=blah&utm_source=google&utm_campaign=somethingelse&blat diff --git a/tests/client018.phpt b/tests/client018.phpt index c3ca9f9..884a3c6 100644 --- a/tests/client018.phpt +++ b/tests/client018.phpt @@ -12,6 +12,10 @@ include "helper/server.inc"; echo "Test\n"; +function dump($response) { + echo $response->getHeader("X-Req"),"\n"; +} + server("pipeline.inc", function($port, $stdin, $stdout, $stderr) { /* tell the server we're about to send 3 pipelined messages */ fputs($stdin, "3\n"); @@ -20,37 +24,21 @@ server("pipeline.inc", function($port, $stdin, $stdout, $stderr) { $client->configure(array("pipelining" => true, "max_host_connections" => 0)); /* this is just to let curl know the server may be capable of pipelining */ - $client->enqueue(new http\Client\Request("GET", "http://localhost:$port")); + $client->enqueue(new http\Client\Request("GET", "http://localhost:$port"), "dump"); $client->send(); - $client->enqueue(new http\Client\Request("GET", "http://localhost:$port/1")); - $client->enqueue(new http\Client\Request("GET", "http://localhost:$port/2")); - $client->enqueue(new http\Client\Request("GET", "http://localhost:$port/3")); + $client->enqueue(new http\Client\Request("GET", "http://localhost:$port/1"), "dump"); + $client->enqueue(new http\Client\Request("GET", "http://localhost:$port/2"), "dump"); + $client->enqueue(new http\Client\Request("GET", "http://localhost:$port/3"), "dump"); $client->send(); - - while (($response = $client->getResponse())) { - echo $response; - } }); ?> ===DONE=== --EXPECT-- Test -HTTP/1.1 200 OK -X-Req: /3 -Etag: "" -X-Original-Transfer-Encoding: chunked -HTTP/1.1 200 OK -X-Req: /2 -Etag: "" -X-Original-Transfer-Encoding: chunked -HTTP/1.1 200 OK -X-Req: /1 -Etag: "" -X-Original-Transfer-Encoding: chunked -HTTP/1.1 200 OK -X-Req: / -Etag: "" -X-Original-Transfer-Encoding: chunked +/ +/1 +/2 +/3 ===DONE=== diff --git a/tests/client022.phpt b/tests/client022.phpt index 255de08..e3f7f11 100644 --- a/tests/client022.phpt +++ b/tests/client022.phpt @@ -18,7 +18,7 @@ nghttpd(function($port) { $client->setOptions([ "protocol" => http\Client\Curl\HTTP_VERSION_2_0, "ssl" => [ - "cainfo" => __DIR__."/helper/http2.crt", + "cafile" => __DIR__."/helper/http2.crt", ] ]); $client->enqueue(new http\Client\Request("GET", "https://localhost:$port")); diff --git a/tests/client028.phpt b/tests/client028.phpt new file mode 100644 index 0000000..27ab2b6 --- /dev/null +++ b/tests/client028.phpt @@ -0,0 +1,141 @@ +--TEST-- +client curl user handler +--SKIPIF-- + +--FILE-- + [], + "W" => [] + ]; + private $R = []; + private $W = []; + private $timeout = 1000; + + function __construct(http\Client $client) { + $this->client = $client; + } + + function init(callable $run) { + $this->run = $run; + } + + function timer(int $timeout_ms) { + echo "T"; + $this->timeout = $timeout_ms; + } + + function socket($socket, int $action) { + echo "S"; + + switch ($action) { + case self::POLL_NONE: + break; + case self::POLL_REMOVE: + if (false !== ($r = array_search($socket, $this->fds["R"], true))) { + echo "U"; + unset($this->fds["R"][$r]); + } + + if (false !== ($w = array_search($socket, $this->fds["W"], true))) { + echo "U"; + unset($this->fds["W"][$w]); + } + + break; + + default: + if ($action & self::POLL_IN) { + if (!in_array($socket, $this->fds["R"], true)) { + $this->fds["R"][] = $socket; + } + } + if ($action & self::POLL_OUT) { + if (!in_array($socket, $this->fds["W"], true)) { + $this->fds["W"][] = $socket; + } + } + break; + } + } + + function once() { + echo "O"; + + foreach ($this->W as $w) { + call_user_func($this->run, $this->client, $w, self::POLL_OUT); + } + foreach ($this->R as $r) { + call_user_func($this->run, $this->client, $r, self::POLL_IN); + } + return count($this->client); + } + + function wait(int $timeout_ms = null) { + echo "W"; + + if ($timeout_ms === null) { + $timeout_ms = $this->timeout; + } + $ts = floor($timeout_ms / 1000); + $tu = ($timeout_ms % 1000) * 1000; + + extract($this->fds); + + if (($wfds = count($R) + count($W))) { + $nfds = stream_select($R, $W, $E, $ts, $tu); + } else { + $nfds = 0; + } + $this->R = (array) $R; + $this->W = (array) $W; + + if ($nfds === false) { + return false; + } + if (!$nfds) { + if (!$wfds) { + echo "S"; + time_nanosleep($ts, $tu*1000); + } + call_user_func($this->run, $this->client); + } + + return true; + } + + function send() { + $this->wait(); + $this->once(); + } +} + + +include "helper/server.inc"; + +server("proxy.inc", function($port) { + $client = new http\Client; + $client->configure([ + "use_eventloop" => new UserHandler($client) + ]); + $client->enqueue(new http\Client\Request("GET", "http://localhost:$port/"), function($r) { + var_dump($r->getResponseCode()); + }); + $client->send(); +}); + +?> +===DONE=== +--EXPECTREGEX-- +Test +T[WST]*(O[WST]*)+U+int\(200\) +===DONE=== diff --git a/tests/header005.phpt b/tests/header005.phpt index 07c27cb..49a9609 100644 --- a/tests/header005.phpt +++ b/tests/header005.phpt @@ -9,12 +9,14 @@ include "skipif.inc"; echo "Test\n"; +function r($v) { return round($v, 2); } + $a = new http\Header("Accept", "text/html, text/plain;q=0.5, */*;q=0"); var_dump("text/html" === $a->negotiate(array("text/plain","text/html"))); var_dump("text/html" === $a->negotiate(array("text/plain","text/html"), $rs)); -var_dump(array("text/html"=>0.99, "text/plain"=>0.5) === $rs); +var_dump(array("text/html"=>0.99, "text/plain"=>0.5) === array_map("r", $rs)); var_dump("text/plain" === $a->negotiate(array("foo/bar", "text/plain"), $rs)); -var_dump(array("text/plain"=>0.5) === $rs); +var_dump(array("text/plain"=>0.5) === array_map("r", $rs)); var_dump("foo/bar" === $a->negotiate(array("foo/bar"), $rs)); var_dump(array() === $rs); diff --git a/tests/helper/pipeline.inc b/tests/helper/pipeline.inc index a6b1699..6f3674c 100644 --- a/tests/helper/pipeline.inc +++ b/tests/helper/pipeline.inc @@ -23,8 +23,8 @@ serve(function($client) { /* pipelined messages */ $req = array(); for ($i=0; $i < $count; ++ $i) { - $req[] = new http\Message($client, false); - logger("Read request no. %d from client %d", $i+1, (int) $client); + $req[] = $m = new http\Message($client, false); + logger("Read request no. %d from client %d (%s)", $i+1, (int) $client, $m->getRequestUrl()); } foreach ($req as $i => $msg) { respond($client, $msg); diff --git a/tests/negotiate001.phpt b/tests/negotiate001.phpt index 0e553f5..ff1644b 100644 --- a/tests/negotiate001.phpt +++ b/tests/negotiate001.phpt @@ -11,43 +11,48 @@ HTTP_ACCEPT_LANGUAGE=de-DE,de-AT;q=0.9,en;q=0.8,fr;q=0 CONTENT TYPE CHARSET ENCODING LANGUAGE CUSTOM @@ -55,7 +60,7 @@ CUSTOM DONE --EXPECT-- @@ -103,7 +108,7 @@ LANGUAGE de: Array ( - [de] => 0.99 + [de] => 0.97 [en] => 0.8 ) de-DE: Array @@ -122,7 +127,7 @@ CUSTOM a.b: Array ( [a.b] => 0.9 - [a.x] => 0.1 - [c.e] => 0.1 + [a.x] => 0.08 + [c.e] => 0.08 ) DONE diff --git a/tests/params017.phpt b/tests/params017.phpt index b3587e5..95e8c54 100644 --- a/tests/params017.phpt +++ b/tests/params017.phpt @@ -19,7 +19,7 @@ $p = current(http\Header::parse($link, "http\\Header"))->getParams( http\Params::DEF_PARAM_SEP, http\Params::DEF_ARG_SEP, http\Params::DEF_VAL_SEP, - http\Params::PARSE_RFC5988 | http\Params::PARSE_ESCAPED + http\Params::PARSE_RFC5987 | http\Params::PARSE_RFC5988 | http\Params::PARSE_ESCAPED ); var_dump($p->params); var_dump((string)$p); diff --git a/tests/url002.phpt b/tests/url002.phpt index 704ede2..de336a7 100644 --- a/tests/url002.phpt +++ b/tests/url002.phpt @@ -13,7 +13,7 @@ $u = "http://user:pass@www.example.com:8080/path/file.ext". var_dump($u === (string) new http\Url($u)); $url = new http\Url($u, - array("path" => "changed", "query" => "foo=&added=this"), + array("path" => "changed", "query" => "bar&foo=&added=this"), http\Url::JOIN_PATH | http\Url::JOIN_QUERY | http\Url::STRIP_AUTH | @@ -40,6 +40,6 @@ NULL string(15) "www.example.com" int(8080) string(13) "/path/changed" -string(38) "more%5B0%5D=1&more%5B1%5D=2&added=this" +string(47) "foo=&more%5B0%5D=1&more%5B1%5D=2&bar&added=this" NULL DONE