Merge branch 'v2.6.x'
authorMichael Wallner <mike@php.net>
Mon, 13 Jun 2016 12:54:41 +0000 (14:54 +0200)
committerMichael Wallner <mike@php.net>
Mon, 13 Jun 2016 12:54:41 +0000 (14:54 +0200)
15 files changed:
1  2 
src/php_http.c
src/php_http_api.h
src/php_http_client.c
src/php_http_client_curl.c
src/php_http_client_curl.h
src/php_http_client_curl_event.c
src/php_http_client_curl_event.h
src/php_http_client_curl_user.c
src/php_http_client_curl_user.h
src/php_http_message_body.c
src/php_http_negotiate.c
src/php_http_params.c
src/php_http_querystring.c
tests/client028.phpt
tests/negotiate001.phpt

diff --cc src/php_http.c
Simple merge
  #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"
  
@@@ -348,62 -345,35 +348,74 @@@ php_http_client_object_t *php_http_clie
  
        o->client = client;
  
 -      if (ptr) {
 -              *ptr = o;
 -      }
 +      o->zo.handlers = &php_http_client_object_handlers;
  
 -      o->zv.handle = zend_objects_store_put(o, NULL, php_http_client_object_free, NULL TSRMLS_CC);
 -      o->zv.handlers = &php_http_client_object_handlers;
 +      return o;
 +}
  
 -      return o->zv;
 +zend_object *php_http_client_object_new(zend_class_entry *ce)
 +{
 +      return &php_http_client_object_new_ex(ce, NULL)->zo;
  }
  
 -zend_object_value php_http_client_object_new(zend_class_entry *ce TSRMLS_DC)
 +static HashTable *php_http_client_object_get_gc(zval *object, zval **table, int *n)
  {
 -      return php_http_client_object_new_ex(ce, NULL, NULL TSRMLS_CC);
 +      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);
 +      }
 +
 +      ZEND_HASH_FOREACH_VAL(props, val)
 +      {
 +              ZVAL_COPY_VALUE(&obj->gc[(*n)++], val);
 +      }
 +      ZEND_HASH_FOREACH_END();
 +
 +      return NULL;
  }
  
 -static void handle_history(zval *zclient, php_http_message_t *request, php_http_message_t *response TSRMLS_DC)
 +static void handle_history(zval *zclient, php_http_message_t *request, php_http_message_t *response)
  {
 -      zval *new_hist, *old_hist = zend_read_property(php_http_client_class_entry, zclient, ZEND_STRL("history"), 0 TSRMLS_CC);
 -      php_http_message_t *zipped = php_http_message_zip(response, request);
 -      zend_object_value ov = php_http_message_object_new_ex(php_http_message_class_entry, zipped, NULL TSRMLS_CC);
 +      zval new_hist, old_hist_tmp, *old_hist = zend_read_property(php_http_client_class_entry, zclient, ZEND_STRL("history"), 0, &old_hist_tmp);
 +      php_http_message_t *req_copy = php_http_message_copy(request, NULL);
 +      php_http_message_t *res_copy = php_http_message_copy(response, NULL);
 +      php_http_message_t *zipped = php_http_message_zip(res_copy, req_copy);
 +      php_http_message_object_t *obj = php_http_message_object_new_ex(php_http_message_get_class_entry(), zipped);
  
 -      MAKE_STD_ZVAL(new_hist);
 -      ZVAL_OBJVAL(new_hist, ov, 0);
 +      ZVAL_OBJ(&new_hist, &obj->zo);
  
        if (Z_TYPE_P(old_hist) == IS_OBJECT) {
 -              php_http_message_object_prepend(new_hist, old_hist, 1 TSRMLS_CC);
 +              php_http_message_object_prepend(&new_hist, old_hist, 1);
        }
  
 -      zend_update_property(php_http_client_class_entry, zclient, ZEND_STRL("history"), new_hist TSRMLS_CC);
 +      zend_update_property(php_http_client_class_entry, zclient, ZEND_STRL("history"), &new_hist);
        zval_ptr_dtor(&new_hist);
  }
  
@@@ -729,158 -668,27 +693,26 @@@ void php_http_client_curl_responsehandl
        }
  }
  
- #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;
 -      TSRMLS_FETCH_FROM_CTX(client->ts);
  
  #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 TSRMLS_CC, E_WARNING, "%s",  curl_multi_strerror(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;
@@@ -1775,10 -1582,19 +1604,18 @@@ static inline ZEND_RESULT_CODE php_http
  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;
 -      TSRMLS_FETCH_FROM_CTX(client->ts);
+       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_class_entry TSRMLS_CC)) {
++      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 && z_is_true(value)) {
++      } 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,20 -1684,18 +1703,18 @@@ static void php_http_curlm_options_init
        }
  #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))) {
 +      if ((opt = php_http_option_register(registry, ZEND_STRL("share_cookies"), 0, _IS_BOOL))) {
                opt->setter = php_http_curlm_option_set_share_cookies;
 -              ZVAL_BOOL(&opt->defval, 1);
 +              ZVAL_TRUE(&opt->defval);
        }
  #if PHP_HTTP_CURL_VERSION(7,23,0)
 -      if ((opt = php_http_option_register(registry, ZEND_STRL("share_ssl"), 0, IS_BOOL))) {
 +      if ((opt = php_http_option_register(registry, ZEND_STRL("share_ssl"), 0, _IS_BOOL))) {
                opt->setter = php_http_curlm_option_set_share_ssl;
 -              ZVAL_BOOL(&opt->defval, 1);
 +              ZVAL_TRUE(&opt->defval);
        }
  #endif
  }
@@@ -2218,23 -2024,14 +2053,13 @@@ static php_http_client_t *php_http_clie
  static void php_http_client_curl_dtor(php_http_client_t *h)
  {
        php_http_client_curl_t *curl = h->ctx;
 -      TSRMLS_FETCH_FROM_CTX(h->ts);
  
- #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 TSRMLS_CC);
 +      php_resource_factory_handle_dtor(h->rf, curl->handle);
  
        efree(curl);
        h->ctx = NULL;
@@@ -2447,36 -2220,22 +2247,21 @@@ static int php_http_client_curl_once(ph
  
  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;
 -      TSRMLS_FETCH_FROM_CTX(h->ts);
  
-       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 TSRMLS_CC, E_WARNING, "WinSock error: %d", WSAGetLastError());
++                      php_error_docref(NULL, E_WARNING, "WinSock error: %d", WSAGetLastError());
  #else
-                               php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
 -                      php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", strerror(errno));
++                      php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
  #endif
-                               return FAILURE;
-                       }
+                       return FAILURE;
                }
        }
  
  
  #if PHP_HTTP_HAVE_CURL
  
 +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 */
index 0000000,058c51f..e1ba505
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,332 +1,327 @@@
 -#include "php_http_client.h"
 -#include "php_http_client_curl.h"
+ /*
+     +--------------------------------------------------------------------+
+     | 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 <mike@php.net>            |
+     +--------------------------------------------------------------------+
+ */
+ #include "php_http_api.h"
 -      TSRMLS_FETCH_FROM_CTX(ctx->client->ts);
+ #if PHP_HTTP_HAVE_CURL
+ #if PHP_HTTP_HAVE_EVENT
+ #     if !PHP_HTTP_HAVE_EVENT2 && /* just be really sure */ !(LIBEVENT_VERSION_NUMBER >= 0x02000000)
+ #             include <event.h>
+ #             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 <event2/event.h>
+ #                     include <event2/event_struct.h>
+ #             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;
 -              php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s",  curl_multi_strerror(rc));
+ #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) {
 -      TSRMLS_FETCH_FROM_CTX(ctx->client->ts);
++              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;
 -                      php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unknown socket action %d", action);
+ #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:
 -      TSRMLS_FETCH_FROM_CTX(ctx->client->ts);
++                      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
+  */
index 0000000,d3b0928..4739f62
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,28 +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 <mike@php.net>            |
+     +--------------------------------------------------------------------+
+ */
++#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
+  */
index 0000000,6fdfa36..a30d666
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,346 +1,323 @@@
 -#include "php_http_client.h"
 -#include "php_http_client_curl.h"
 -#include "php_http_client_curl_user.h"
+ /*
+     +--------------------------------------------------------------------+
+     | 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 <mike@php.net>            |
+     +--------------------------------------------------------------------+
+ */
+ #include "php_http_api.h"
 -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;
 -
+ #include "php_network.h"
+ #include "zend_closures.h"
+ #if PHP_HTTP_HAVE_CURL
 -      if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O|rl", &zclient, php_http_client_class_entry, &zstream, &action)) {
+ 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;
 -      client = zend_object_store_get_object(zclient TSRMLS_CC);
++      if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS(), "O|rl", &zclient, php_http_client_get_class_entry(), &zstream, &action)) {
+               return;
+       }
 -              php_stream_from_zval(stream, &zstream);
++      client = PHP_HTTP_OBJ(NULL, zclient);
+       if (zstream) {
 -              zval **args[1], *ztimeout;
 -              TSRMLS_FETCH_FROM_CTX(context->client->ts);
++              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) {
 -              MAKE_STD_ZVAL(ztimeout);
++              zval args[1], *ztimeout = &args[0];
 -              args[0] = &ztimeout;
 -              php_http_object_method_call(&context->timer, context->user, NULL, 1, args TSRMLS_CC);
 -              zval_ptr_dtor(&ztimeout);
+               ZVAL_LONG(ztimeout, timeout_ms);
 -      zval **args[2], *zaction, *zsocket;
 -      TSRMLS_FETCH_FROM_CTX(ctx->client->ts);
++              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;
 -                      MAKE_STD_ZVAL(zsocket);
++      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:
 -                      args[0] = &zsocket;
 -                      MAKE_STD_ZVAL(zaction);
+                       php_stream_to_zval(ev->socket, zsocket);
 -                      args[1] = &zaction;
 -                      php_http_object_method_call(&ctx->socket, ctx->user, NULL, 2, args TSRMLS_CC);
 -                      zval_ptr_dtor(&zsocket);
 -                      zval_ptr_dtor(&zaction);
++                      Z_TRY_ADDREF_P(zsocket);
+                       ZVAL_LONG(zaction, action);
 -                      php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unknown socket action %d", action);
++                      php_http_object_method_call(&ctx->socket, &ctx->user, NULL, 2, args);
++                      zval_ptr_dtor(zsocket);
++                      zval_ptr_dtor(zaction);
+                       break;
+               default:
 -      TSRMLS_FETCH_FROM_CTX(ctx->client->ts);
++                      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;
 -      return php_http_object_method_call(&ctx->once, ctx->user, NULL, 0, NULL TSRMLS_CC);
+ #if DBG_EVENTS
+       fprintf(stderr, "O");
+ #endif
 -      zval **args[1], *ztimeout;
++      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;
 -      TSRMLS_FETCH_FROM_CTX(ctx->client->ts);
++      zval args[1], *ztimeout = &args[0];
+       ZEND_RESULT_CODE rv;
 -      MAKE_STD_ZVAL(ztimeout);
+ #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;
+       }
 -      args[0] = &ztimeout;
 -      rv = php_http_object_method_call(&ctx->wait, ctx->user, NULL, 1, args TSRMLS_CC);
 -      zval_ptr_dtor(&ztimeout);
+       ZVAL_LONG(ztimeout, custom_timeout->tv_sec * 1000 + custom_timeout->tv_usec / 1000);
 -      TSRMLS_FETCH_FROM_CTX(ctx->client->ts);
++      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 (SUCCESS != php_http_object_method_call(&ctx->send, ctx->user, NULL, 0, NULL TSRMLS_CC)) {
+ #if DBG_EVENTS
+       fprintf(stderr, "E");
+ #endif
+       /* kickstart */
+       php_http_client_curl_loop(ctx->client, CURL_SOCKET_TIMEOUT, 0);
+       do {
 -      zval *zclosure, **args[1];
 -      TSRMLS_FETCH_FROM_CTX(client->ts);
++              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;
 -      ctx->user = user_data;
 -      Z_ADDREF_P(ctx->user);
++      zval args[1], *zclosure = &args[0];
+ #if DBG_EVENTS
+       fprintf(stderr, "I");
+ #endif
+       ctx = ecalloc(1, sizeof(*ctx));
+       ctx->client = client;
 -      ctx->closure.common.function_name = "php_http_client_curl_user_handler";
++      ZVAL_COPY(&ctx->user, user_data);
+       memset(&ctx->closure, 0, sizeof(ctx->closure));
+       ctx->closure.common.type = ZEND_INTERNAL_FUNCTION;
 -      MAKE_STD_ZVAL(zclosure);
 -      zend_create_closure(zclosure, &ctx->closure, NULL, NULL TSRMLS_CC);
 -      args[0] = &zclosure;
++      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;
 -      php_http_object_method_init(&init, ctx->user, ZEND_STRL("init") TSRMLS_CC);
 -      php_http_object_method_call(&init, ctx->user, NULL, 1, args TSRMLS_CC);
++      zend_create_closure(zclosure, &ctx->closure, NULL, NULL, NULL);
 -      zval_ptr_dtor(&zclosure);
++      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);
 -      php_http_object_method_init(&ctx->timer, ctx->user, ZEND_STRL("timer") TSRMLS_CC);
 -      php_http_object_method_init(&ctx->socket, ctx->user, ZEND_STRL("socket") TSRMLS_CC);
 -      php_http_object_method_init(&ctx->once, ctx->user, ZEND_STRL("once") TSRMLS_CC);
 -      php_http_object_method_init(&ctx->wait, ctx->user, ZEND_STRL("wait") TSRMLS_CC);
 -      php_http_object_method_init(&ctx->send, ctx->user, ZEND_STRL("send") TSRMLS_CC);
++      zval_ptr_dtor(zclosure);
 -zend_class_entry *php_http_client_curl_user_class_entry;
++      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;
+ }
 -      ZEND_ARG_TYPE_INFO(0, socket, IS_RESOURCE, 0)
++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_INFO(0, socket)
+       ZEND_ARG_TYPE_INFO(0, action, IS_LONG, 0)
+ #else
 -      php_http_client_curl_user_class_entry = zend_register_internal_interface(&ce TSRMLS_CC);
+       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);
 -      zend_declare_class_constant_long(php_http_client_curl_user_class_entry, ZEND_STRL("POLL_NONE"), CURL_POLL_NONE TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_client_curl_user_class_entry, ZEND_STRL("POLL_IN"), CURL_POLL_IN TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_client_curl_user_class_entry, ZEND_STRL("POLL_OUT"), CURL_POLL_OUT TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_client_curl_user_class_entry, ZEND_STRL("POLL_INOUT"), CURL_POLL_INOUT TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_client_curl_user_class_entry, ZEND_STRL("POLL_REMOVE"), CURL_POLL_REMOVE TSRMLS_CC);
++      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
+  */
index 0000000,8a6778f..35f5d6f
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,89 +1,105 @@@
 -PHP_HTTP_API zend_class_entry *php_http_client_curl_user_class_entry;
+ /*
+     +--------------------------------------------------------------------+
+     | 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 <mike@php.net>            |
+     +--------------------------------------------------------------------+
+ */
++#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
+ <?php
+ interface http\Client\Curl\User
+ {
+       const POLL_NONE = 0;
+       const POLL_IN = 1;
+       const POLL_OUT = 2;
+       const POLL_INOUT = 3;
+       const POLL_REMOVE = 4;
+       /**
+        * Initialize the loop
+        *
+        * The callback should be run when:
+        *  - timeout occurs
+        *  - a watched socket needs action
+        *
+        * @param callable $run callback as function(http\Client $client, resource $socket = null, int $action = http\Client\Curl\User::POLL_NONE)
+        */
+       function init(callable $run);
+       /**
+        * Register a timeout watcher
+        * @param int $timeout_ms desired timeout with milliseconds resolution
+        */
+       function timer(int $timeout_ms);
+       /**
+        * (Un-)Register a socket watcher
+        * @param resource $socket the fd to watch
+        * @param int $poll http\Client\Curl\Loop::POLL_* constant
+        */
+       function socket($socket, int $poll);
+       /**
+        * Run the loop as long as it does not block
+        *
+        * Called by http\Client::once()
+        */
+       function once();
+       /**
+        * Wait/poll/select (block the loop) until events fire
+        *
+        * Called by http\Client::wait()
+        *
+        * @param int $timeout_ms block for maximal $timeout_ms milliseconds
+        */
+       function wait(int $timeout_ms = null);
+       /**
+        * Run the loop
+        *
+        * Called by http\Client::send() while there are unfinished requests and
+        * no exception has occurred
+        */
+       function send();
+ }
+ #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
+  */
@@@ -45,11 -45,15 +45,11 @@@ php_http_message_body_t *php_http_messa
        body->refcount = 1;
  
        if (stream) {
 -              php_stream_auto_cleanup(stream);
 -              body->stream_id = php_stream_get_resource_id(stream);
 -              zend_list_addref(body->stream_id);
 +              body->res = stream->res;
-               ++GC_REFCOUNT(body->res);
        } else {
 -              stream = php_stream_temp_create(TEMP_STREAM_DEFAULT, 0xffff);
 -              php_stream_auto_cleanup(stream);
 -              body->stream_id = php_stream_get_resource_id(stream);
 +              body->res = php_stream_temp_create(TEMP_STREAM_DEFAULT, 0xffff)->res;
        }
 -      TSRMLS_SET_CTX(body->ts);
++      ++GC_REFCOUNT(body->res);
  
        if (body_ptr) {
                *body_ptr = body;
  
  #include "php_http_api.h"
  
 -static int php_http_negotiate_sort(const void *a, const void *b TSRMLS_DC)
+ #ifndef PHP_HTTP_DEBUG_NEG
+ # define PHP_HTTP_DEBUG_NEG 0
+ #endif
 +static int php_http_negotiate_sort(const void *first, const void *second)
  {
 -      zval result, *first, *second;
 +      Bucket *b1 = (Bucket *) first, *b2 = (Bucket *) second;
 +      int result = numeric_compare_function(&b1->val, &b2->val);
  
 -      first = *((zval **) (*((Bucket **) a))->pData);
 -      second= *((zval **) (*((Bucket **) b))->pData);
 -
 -      if (numeric_compare_function(&result, first, second TSRMLS_CC) != SUCCESS) {
 -              return 0;
 -      }
 -      return (Z_LVAL(result) > 0 ? -1 : (Z_LVAL(result) < 0 ? 1 : 0));
 +      return (result > 0 ? -1 : (result < 0 ? 1 : 0));
  }
  
  #define M_PRI 5
@@@ -62,38 -75,41 +70,44 @@@ static inline unsigned php_http_negotia
  #endif
        return match;
  }
 -
 -static int php_http_negotiate_reduce(void *p TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key)
 +static int php_http_negotiate_reduce(zval *p, int num_args, va_list args, zend_hash_key *hash_key)
  {
        unsigned best_match = 0;
 -      HashPosition pos;
 -      php_http_array_hashkey_t key = php_http_array_hashkey_init(0);
 -      zval **val, *supported = php_http_ztyp(IS_STRING, *(zval **)p);
+       double q = 0;
-       zval *value, *q = NULL;
 +      php_http_arrkey_t key;
++      zval *value;
 +      zend_string *supported = zval_get_string(p);
        HashTable *params = va_arg(args, HashTable *);
        HashTable *result = va_arg(args, HashTable *);
        const char *sep_str = va_arg(args, const char *);
        size_t sep_len = va_arg(args, size_t);
  
 -      FOREACH_HASH_KEYVAL(pos, params, key, val) {
 -              if (key.type == HASH_KEY_IS_STRING) {
 -                      unsigned match = php_http_negotiate_match(key.str, key.len-1, Z_STRVAL_P(supported), Z_STRLEN_P(supported), sep_str, sep_len);
 +      ZEND_HASH_FOREACH_KEY_VAL(params, key.h, key.key, value)
 +      {
 +              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
 -                      if (match > best_match) {
 -                              best_match = match;
 -                              q = Z_DVAL_PP(val) - 0.1 / match;
 -                      }
 +              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 tmp;
 -              MAKE_STD_ZVAL(tmp);
 -              ZVAL_DOUBLE(tmp, q);
 -              zend_hash_update(result, Z_STRVAL_P(supported), Z_STRLEN_P(supported) + 1, (void *) &tmp, sizeof(zval *), NULL);
++              ZVAL_DOUBLE(&tmp, q);
++              zend_hash_update(result, supported, &tmp);
        }
  
 -      zval_ptr_dtor(&supported);
 +      zend_string_release(supported);
        return ZEND_HASH_APPLY_KEEP;
  }
  
@@@ -112,38 -129,38 +126,39 @@@ HashTable *php_http_negotiate(const cha
                php_http_params_opts_default_get(&opts);
                opts.input.str = estrndup(value_str, value_len);
                opts.input.len = value_len;
 -              php_http_params_parse(&params, &opts TSRMLS_CC);
+               opts.flags &= ~PHP_HTTP_PARAMS_RFC5987;
 +              php_http_params_parse(&params, &opts);
                efree(opts.input.str);
  
 -              INIT_PZVAL(&arr);
                array_init(&arr);
  
 -              FOREACH_HASH_KEYVAL(pos, &params, key, val) {
 +              ZEND_HASH_FOREACH_KEY_VAL(&params, key.h, key.key, val)
 +              {
                        double q;
  
 -                      if (SUCCESS == zend_hash_find(Z_ARRVAL_PP(val), ZEND_STRS("arguments"), (void *) &arg)
 -                      &&      IS_ARRAY == Z_TYPE_PP(arg)
 -                      &&      SUCCESS == zend_hash_find(Z_ARRVAL_PP(arg), ZEND_STRS("q"), (void *) &zq)) {
 -                              zval *tmp = php_http_ztyp(IS_DOUBLE, *zq);
 -
 -                              q = Z_DVAL_P(tmp);
 -                              zval_ptr_dtor(&tmp);
 +                      if ((arg = zend_hash_str_find(Z_ARRVAL_P(val), ZEND_STRL("arguments")))
 +                      &&      (IS_ARRAY == Z_TYPE_P(arg))
 +                      &&      (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 (key.type == HASH_KEY_IS_STRING) {
 -                              add_assoc_double_ex(&arr, key.str, key.len, q);
 +#if 0
 +                      fprintf(stderr, "Q: %s=%1.3f\n", key.key->val, q);
 +#endif
 +
 +                      if (key.key) {
 +                              add_assoc_double_ex(&arr, key.key->val, key.key->len, q);
                        } else {
 -                              add_index_double(&arr, key.num, q);
 +                              add_index_double(&arr, key.h, q);
                        }
  
 -                      PTR_FREE(key.str);
                }
 +              ZEND_HASH_FOREACH_END();
  
- #if 0
+ #if PHP_HTTP_DEBUG_NEG
 -              zend_print_zval_r(&arr, 1 TSRMLS_CC);
 +              zend_print_zval_r(&arr, 1);
  #endif
  
                ALLOC_HASHTABLE(result);
@@@ -553,7 -524,9 +555,9 @@@ static void push_param(HashTable *param
  {
        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 TSRMLS_CC);
 +                      sanitize_value(opts->flags, state->val.str, state->val.len, state->current.val, state->rfc5987);
+               } else {
 -                      ZVAL_EMPTY_STRING(*(state->current.val));
++                      ZVAL_EMPTY_STRING(state->current.val);
                }
                state->rfc5987 = 0;
        } else if (state->arg.str) {
@@@ -171,7 -161,28 +171,26 @@@ static int apply_querystring(zval *val
        return ZEND_HASH_APPLY_KEEP;
  }
  
 -static int apply_querystring_filter(void *pData TSRMLS_DC)
++static int apply_querystring_filter(zval *val)
+ {
 -      zval **val = pData;
 -
 -      switch (Z_TYPE_PP(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 TSRMLS_CC);
 -              if (!zend_hash_num_elements(HASH_OF(*val))) {
++              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 TSRMLS_DC)
 +ZEND_RESULT_CODE php_http_querystring_parse(HashTable *ht, const char *str, size_t len)
  {
        ZEND_RESULT_CODE rv = FAILURE;
        php_http_params_opts_t opts;
                zval_ptr_dtor(&arr);
        }
  
-       ZVAL_NULL(&opts.defval);
 -      MAKE_STD_ZVAL(opts.defval);
 -      ZVAL_TRUE(opts.defval);
++      ZVAL_TRUE(&opts.defval);
  
 -      if (php_http_params_parse(ht, &opts TSRMLS_CC)) {
 -              zend_hash_apply(ht, apply_querystring TSRMLS_CC);
 +      if (php_http_params_parse(ht, &opts)) {
 +              zend_hash_apply(ht, apply_querystring);
                rv = SUCCESS;
        }
  
@@@ -224,11 -238,16 +243,13 @@@ ZEND_RESULT_CODE php_http_querystring_u
        }
  
        /* modify qarray */
-       if (params) {
+       if (!params) {
 -              zend_hash_apply(Z_ARRVAL_P(qarray), apply_querystring_filter TSRMLS_CC);
++              zend_hash_apply(Z_ARRVAL_P(qarray), apply_querystring_filter);
+       } else {
 -              HashPosition pos;
 -              HashTable *ptr;
 -              php_http_array_hashkey_t key = php_http_array_hashkey_init(0);
 -              zval **params_entry,  **qarray_entry;
 -              zval zv, *zv_ptr = NULL;
 +              HashTable *ht;
 +              php_http_arrkey_t key;
 +              zval zv, *params_entry, *qarray_entry;
  
 -              INIT_PZVAL(&zv);
                ZVAL_NULL(&zv);
  
                /* squeeze the hash out of the zval */
                                        /*
                                         * update
                                         */
 -                                      zval equal, *entry = NULL;
 +                                      zval equal, tmp, *entry = &tmp;
  
 +                                      ZVAL_UNDEF(&tmp);
                                        /* recursive */
 -                                      if (Z_TYPE_PP(params_entry) == IS_ARRAY || Z_TYPE_PP(params_entry) == IS_OBJECT) {
 -                                              entry = php_http_zsep(1, IS_ARRAY, *qarray_entry);
 -                                              php_http_querystring_update(entry, *params_entry, NULL TSRMLS_CC);
 -                                      } else if ((FAILURE == is_identical_function(&equal, *qarray_entry, *params_entry TSRMLS_CC)) || !Z_BVAL(equal)) {
 -                                              Z_ADDREF_PP(params_entry);
 -                                              entry = *params_entry;
 +                                      if (Z_TYPE_P(params_entry) == IS_ARRAY || Z_TYPE_P(params_entry) == IS_OBJECT) {
 +                                              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;
                                        }
  
                                        if (entry) {
index 0000000,4678bcc..27ab2b6
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,141 +1,141 @@@
 -      function timer($timeout_ms) {
+ --TEST--
+ client curl user handler
+ --SKIPIF--
+ <?php 
+ include "skipif.inc";
+ skip_client_test();
+ ?> 
+ --FILE--
+ <?php 
+ echo "Test\n";
+ class UserHandler implements http\Client\Curl\User
+ {
+       private $client;
+       private $run;
+       private $fds = [
+                       "R" => [],
+                       "W" => []
+       ];
+       private $R = [];
+       private $W = [];
+       private $timeout = 1000;
+       
+       function __construct(http\Client $client) {
+               $this->client = $client;
+       }
+       
+       function init(callable $run) {
+               $this->run = $run;
+       }
+       
 -      function socket($socket, $action) {
++      function timer(int $timeout_ms) {
+               echo "T";
+               $this->timeout = $timeout_ms;
+       }
+       
 -      function wait($timeout_ms = null) {
++      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===
@@@ -122,7 -127,7 +127,7 @@@ CUSTO
  a.b: Array
  (
      [a.b] => 0.9
-     [a.x] => 0.1
-     [c.e] => 0.1
 -    [c.e] => 0.08
+     [a.x] => 0.08
++    [c.e] => 0.08
  )
  DONE