Merge branch 'master' into phpng
authorMichael Wallner <mike@php.net>
Wed, 18 Feb 2015 09:33:35 +0000 (10:33 +0100)
committerMichael Wallner <mike@php.net>
Wed, 18 Feb 2015 10:26:54 +0000 (11:26 +0100)
Conflicts:
package.xml
php_http_client_curl.c
php_http_env.c
php_http_env_response.c
php_http_header_parser.c
php_http_message.c
php_http_message_body.c
php_http_message_parser.c
php_http_misc.c
php_http_strlist.c
php_http_url.h
tests/info.phpt

20 files changed:
1  2 
config9.m4
package.xml
php_http.c
php_http_api.h
php_http_client.c
php_http_client.h
php_http_client_curl.c
php_http_env.c
php_http_env_response.c
php_http_header.c
php_http_header_parser.c
php_http_header_parser.h
php_http_message.c
php_http_message_body.c
php_http_message_parser.c
php_http_message_parser.h
php_http_misc.h
php_http_url.h
tests/messageparser002.phpt
tests/phpinfo.phpt

diff --cc config9.m4
Simple merge
diff --cc package.xml
Simple merge
diff --cc php_http.c
Simple merge
diff --cc php_http_api.h
Simple merge
@@@ -814,6 -843,22 +814,22 @@@ static PHP_METHOD(HttpClient, wait
        }
  }
  
 -      php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|H!", &settings), invalid_arg, return);
 -      obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+ ZEND_BEGIN_ARG_INFO_EX(ai_HttpClient_configure, 0, 0, 1)
+       ZEND_ARG_ARRAY_INFO(0, settings, 1)
+ ZEND_END_ARG_INFO();
+ static PHP_METHOD(HttpClient, configure)
+ {
+       HashTable *settings = NULL;
+       php_http_client_object_t *obj;
++      php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS(), "|H!", &settings), invalid_arg, return);
++      obj = PHP_HTTP_OBJ(NULL, getThis());
+       php_http_expect(SUCCESS == php_http_client_setopt(obj->client, PHP_HTTP_CLIENT_OPT_CONFIGURATION, settings), unexpected_val, return);
+       RETVAL_ZVAL(getThis(), 1, 0);
+ }
  ZEND_BEGIN_ARG_INFO_EX(ai_HttpClient_enablePipelining, 0, 0, 0)
        ZEND_ARG_INFO(0, enable)
  ZEND_END_ARG_INFO();
@@@ -1139,6 -1189,30 +1155,30 @@@ static PHP_METHOD(HttpClient, getAvaila
        }
  }
  
 -              php_http_client_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+ ZEND_BEGIN_ARG_INFO_EX(ai_HttpClient_getAvailableOptions, 0, 0, 0)
+ ZEND_END_ARG_INFO();
+ static PHP_METHOD(HttpClient, getAvailableOptions)
+ {
+       if (SUCCESS == zend_parse_parameters_none()) {
 -              php_http_client_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
++              php_http_client_object_t *obj = PHP_HTTP_OBJ(NULL, getThis());
+               array_init(return_value);
+               php_http_client_getopt(obj->client, PHP_HTTP_CLIENT_OPT_AVAILABLE_OPTIONS, NULL, &Z_ARRVAL_P(return_value));
+       }
+ }
+ ZEND_BEGIN_ARG_INFO_EX(ai_HttpClient_getAvailableConfiguration, 0, 0, 0)
+ ZEND_END_ARG_INFO();
+ static PHP_METHOD(HttpClient, getAvailableConfiguration)
+ {
+       if (SUCCESS == zend_parse_parameters_none()) {
++              php_http_client_object_t *obj = PHP_HTTP_OBJ(NULL, getThis());
+               array_init(return_value);
+               php_http_client_getopt(obj->client, PHP_HTTP_CLIENT_OPT_AVAILABLE_CONFIGURATION, NULL, &Z_ARRVAL_P(return_value));
+       }
+ }
  static zend_function_entry php_http_client_methods[] = {
        PHP_ME(HttpClient, __construct,          ai_HttpClient_construct,            ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
        PHP_ME(HttpClient, reset,                ai_HttpClient_reset,                ZEND_ACC_PUBLIC)
Simple merge
@@@ -962,7 -934,10 +962,10 @@@ static ZEND_RESULT_CODE php_http_curle_
        php_http_client_curl_handler_t *curl = userdata;
        CURL *ch = curl->handle;
  
 -      if (CURLE_OK != curl_easy_setopt(ch, CURLOPT_ACCEPT_ENCODING, Z_BVAL_P(val) ? "" : NULL)) {
+ #if !PHP_HTTP_CURL_VERSION(7,21,6)
+ #     define CURLOPT_ACCEPT_ENCODING CURLOPT_ENCODING
+ #endif
 +      if (CURLE_OK != curl_easy_setopt(ch, CURLOPT_ACCEPT_ENCODING, Z_TYPE_P(val) == IS_TRUE ? "" : NULL)) {
                return FAILURE;
        }
        return SUCCESS;
@@@ -1506,11 -1493,8 +1509,12 @@@ static zval *php_http_curle_get_option(
        zval *option;
  
        if ((option = php_http_option_get(opt, options, NULL))) {
 -              option = php_http_ztyp(opt->type, option);
 -              zend_hash_quick_update(&curl->options.cache, opt->name.s, opt->name.l, opt->name.h, &option, sizeof(zval *), NULL);
 +              zval zopt;
 +
 +              ZVAL_DUP(&zopt, option);
-               convert_to_explicit_type(option, opt->type);
++              convert_to_explicit_type(&zopt, opt->type);
 +              zend_hash_update(&curl->options.cache, opt->name, &zopt);
++              return zend_hash_find(&curl->options.cache, opt->name);
        }
        return option;
  }
@@@ -1599,6 -1580,182 +1603,187 @@@ static ZEND_RESULT_CODE php_http_curle_
        return rv;
  }
  
 -static STATUS php_http_curlm_option_set_pipelining_bl(php_http_option_t *opt, zval *value, void *userdata)
+ #if PHP_HTTP_CURL_VERSION(7,30,0)
 -              zval **entry;
 -              HashPosition pos;
++static ZEND_RESULT_CODE php_http_curlm_option_set_pipelining_bl(php_http_option_t *opt, zval *value, void *userdata)
+ {
+       php_http_client_t *client = userdata;
+       php_http_client_curl_t *curl = client->ctx;
+       CURLM *ch = curl->handle;
+       HashTable tmp_ht;
+       char **bl = NULL;
+       TSRMLS_FETCH_FROM_CTX(client->ts);
+       /* array of char *, ending with a NULL */
+       if (value && Z_TYPE_P(value) != IS_NULL) {
 -              FOREACH_HASH_VAL(pos, &tmp_ht, entry) {
 -                      *ptr++ = Z_STRVAL_PP(entry);
++              zval *entry;
+               HashTable *ht = HASH_OF(value);
+               int c = zend_hash_num_elements(ht);
+               char **ptr = ecalloc(c + 1, sizeof(char *));
+               bl = ptr;
+               zend_hash_init(&tmp_ht, c, NULL, ZVAL_PTR_DTOR, 0);
+               array_join(ht, &tmp_ht, 0, ARRAY_JOIN_STRINGIFY);
 -static inline STATUS php_http_curlm_use_eventloop(php_http_client_t *h, zend_bool enable)
++              ZEND_HASH_FOREACH_VAL(&tmp_ht, entry)
++              {
++                      *ptr++ = Z_STRVAL_P(entry);
+               }
++              ZEND_HASH_FOREACH_END();
+       }
+       if (CURLM_OK != curl_multi_setopt(ch, opt->option, bl)) {
+               if (bl) {
+                       efree(bl);
+                       zend_hash_destroy(&tmp_ht);
+               }
+               return FAILURE;
+       }
+       if (bl) {
+               efree(bl);
+               zend_hash_destroy(&tmp_ht);
+       }
+       return SUCCESS;
+ }
+ #endif
+ #if PHP_HTTP_HAVE_EVENT
 -static STATUS php_http_curlm_option_set_use_eventloop(php_http_option_t *opt, zval *value, void *userdata)
++static inline ZEND_RESULT_CODE php_http_curlm_use_eventloop(php_http_client_t *h, zend_bool enable)
+ {
+       php_http_client_curl_t *curl = h->ctx;
+       if ((curl->useevents = enable)) {
+               if (!curl->evbase) {
+                       curl->evbase = event_base_new();
+               }
+               if (!curl->timeout) {
+                       curl->timeout = ecalloc(1, sizeof(struct event));
+               }
+               curl_multi_setopt(curl->handle, CURLMOPT_SOCKETDATA, h);
+               curl_multi_setopt(curl->handle, CURLMOPT_SOCKETFUNCTION, php_http_curlm_socket_callback);
+               curl_multi_setopt(curl->handle, CURLMOPT_TIMERDATA, h);
+               curl_multi_setopt(curl->handle, CURLMOPT_TIMERFUNCTION, php_http_curlm_timer_callback);
+       } else {
+               curl_multi_setopt(curl->handle, CURLMOPT_SOCKETDATA, NULL);
+               curl_multi_setopt(curl->handle, CURLMOPT_SOCKETFUNCTION, NULL);
+               curl_multi_setopt(curl->handle, CURLMOPT_TIMERDATA, NULL);
+               curl_multi_setopt(curl->handle, CURLMOPT_TIMERFUNCTION, NULL);
+       }
+       return SUCCESS;
+ }
 -      return php_http_curlm_use_eventloop(client, value && Z_BVAL_P(value));
++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_option_register(registry, ZEND_STRL("pipelining"), CURLMOPT_PIPELINING, IS_BOOL);
++      return php_http_curlm_use_eventloop(client, value && Z_TYPE_P(value) == IS_TRUE);
+ }
+ #endif
+ static void php_http_curlm_options_init(php_http_options_t *registry TSRMLS_DC)
+ {
+       php_http_option_t *opt;
+       /* set size of connection cache */
+       if ((opt = php_http_option_register(registry, ZEND_STRL("maxconnects"), CURLMOPT_MAXCONNECTS, IS_LONG))) {
+               /* -1 == default, 0 == unlimited */
+               ZVAL_LONG(&opt->defval, -1);
+       }
+       /* set max number of connections to a single host */
+ #if PHP_HTTP_CURL_VERSION(7,30,0)
+       php_http_option_register(registry, ZEND_STRL("max_host_connections"), CURLMOPT_MAX_HOST_CONNECTIONS, IS_LONG);
+ #endif
+       /* maximum number of requests in a pipeline */
+ #if PHP_HTTP_CURL_VERSION(7,30,0)
+       if ((opt = php_http_option_register(registry, ZEND_STRL("max_pipeline_length"), CURLMOPT_MAX_PIPELINE_LENGTH, IS_LONG))) {
+               ZVAL_LONG(&opt->defval, 5);
+       }
+ #endif
+       /* max simultaneously open connections */
+ #if PHP_HTTP_CURL_VERSION(7,30,0)
+       php_http_option_register(registry, ZEND_STRL("max_total_connections"), CURLMOPT_MAX_TOTAL_CONNECTIONS, IS_LONG);
+ #endif
+       /* enable/disable HTTP pipelining */
 -      if ((opt = php_http_option_register(registry, ZEND_STRL("use_eventloop"), 0, IS_BOOL))) {
++      php_http_option_register(registry, ZEND_STRL("pipelining"), CURLMOPT_PIPELINING, _IS_BOOL);
+       /* chunk length threshold for pipelining */
+ #if PHP_HTTP_CURL_VERSION(7,30,0)
+       php_http_option_register(registry, ZEND_STRL("chunk_length_penalty_size"), CURLMOPT_CHUNK_LENGTH_PENALTY_SIZE, IS_LONG);
+ #endif
+       /* size threshold for pipelining penalty */
+ #if PHP_HTTP_CURL_VERSION(7,30,0)
+       php_http_option_register(registry, ZEND_STRL("content_length_penalty_size"), CURLMOPT_CONTENT_LENGTH_PENALTY_SIZE, IS_LONG);
+ #endif
+       /* pipelining server blacklist */
+ #if PHP_HTTP_CURL_VERSION(7,30,0)
+       if ((opt = php_http_option_register(registry, ZEND_STRL("pipelining_server_bl"), CURLMOPT_PIPELINING_SERVER_BL, IS_ARRAY))) {
+               opt->setter = php_http_curlm_option_set_pipelining_bl;
+       }
+ #endif
+       /* pipelining host blacklist */
+ #if PHP_HTTP_CURL_VERSION(7,30,0)
+       if ((opt = php_http_option_register(registry, ZEND_STRL("pipelining_site_bl"), CURLMOPT_PIPELINING_SITE_BL, IS_ARRAY))) {
+               opt->setter = php_http_curlm_option_set_pipelining_bl;
+       }
+ #endif
+       /* events */
+ #if PHP_HTTP_HAVE_EVENT
 -static STATUS php_http_curlm_set_option(php_http_option_t *opt, zval *val, void *userdata)
++      if ((opt = php_http_option_register(registry, ZEND_STRL("use_eventloop"), 0, _IS_BOOL))) {
+               opt->setter = php_http_curlm_option_set_use_eventloop;
+       }
+ #endif
+ }
 -      STATUS rv = SUCCESS;
 -      TSRMLS_FETCH_FROM_CTX(client->ts);
++static ZEND_RESULT_CODE php_http_curlm_set_option(php_http_option_t *opt, zval *val, void *userdata)
+ {
+       php_http_client_t *client = userdata;
+       php_http_client_curl_t *curl = client->ctx;
+       CURLM *ch = curl->handle;
+       zval *orig = val;
+       CURLMcode rc = CURLM_UNKNOWN_OPTION;
 -              val = php_http_ztyp(opt->type, val);
++      ZEND_RESULT_CODE rv = SUCCESS;
+       if (!val) {
+               val = &opt->defval;
+       } else if (opt->type && Z_TYPE_P(val) != opt->type && !(Z_TYPE_P(val) == IS_NULL && opt->type == IS_ARRAY)) {
 -              case IS_BOOL:
 -                      if (CURLM_OK != (rc = curl_multi_setopt(ch, opt->option, (long) Z_BVAL_P(val)))) {
++              zval zopt;
++
++              ZVAL_DUP(&zopt, val);
++              convert_to_explicit_type(&zopt, opt->type);
++
++              val = &zopt;
+       }
+       if (opt->setter) {
+               rv = opt->setter(opt, val, client);
+       } else {
+               switch (opt->type) {
 -              zval_ptr_dtor(&val);
++              case _IS_BOOL:
++                      if (CURLM_OK != (rc = curl_multi_setopt(ch, opt->option, (long) zend_is_true(val)))) {
+                               rv = FAILURE;
+                       }
+                       break;
+               case IS_LONG:
+                       if (CURLM_OK != (rc = curl_multi_setopt(ch, opt->option, Z_LVAL_P(val)))) {
+                               rv = FAILURE;
+                       }
+                       break;
+               default:
+                       rv = FAILURE;
+                       break;
+               }
+       }
+       if (val && val != orig && val != &opt->defval) {
 -              php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Could not set option %s (%s)", opt->name.s, curl_easy_strerror(rc));
++              zval_ptr_dtor(val);
+       }
+       if (rv != SUCCESS) {
++              php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Could not set option %s (%s)", opt->name->val, curl_easy_strerror(rc));
+       }
+       return rv;
+ }
  
  /* client ops */
  
@@@ -1912,24 -2064,37 +2097,40 @@@ static void queue_dtor(php_http_client_
        php_http_client_curl_handler_dtor(handler);
  }
  
- static php_resource_factory_t *create_rf(php_http_url_t *url)
 -static php_resource_factory_t *create_rf(php_http_client_t *h, php_http_client_enqueue_t *enqueue TSRMLS_DC)
++static php_resource_factory_t *create_rf(php_http_client_t *h, php_http_client_enqueue_t *enqueue)
  {
-       php_persistent_handle_factory_t *pf;
+       php_persistent_handle_factory_t *pf = NULL;
        php_resource_factory_t *rf = NULL;
-       zend_string *id;
-       char *id_str = NULL;
-       size_t id_len;
+       php_http_url_t *url = enqueue->request->http.info.request.url;
  
        if (!url || (!url->host && !url->path)) {
 -              php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot request empty URL");
 +              php_error_docref(NULL, E_WARNING, "Cannot request empty URL");
                return NULL;
        }
  
-       id_len = spprintf(&id_str, 0, "%s:%d", STR_PTR(url->host), url->port ? url->port : 80);
-       id = php_http_cs2zs(id_str, id_len);
+       /* only if the client itself is setup for persistence */
+       if (h->rf->dtor == (void (*)(void*)) php_persistent_handle_abandon) {
++              zend_string *id;
+               char *id_str = NULL;
+               size_t id_len;
+               int port = url->port ? url->port : 80;
 -              zval **zport;
++              zval *zport;
++
++              id_len = spprintf(&id_str, 0, "%s:%d", STR_PTR(url->host), url->port ? url->port : 80);
++              id = php_http_cs2zs(id_str, id_len);
 -              if (SUCCESS == zend_hash_find(enqueue->options, ZEND_STRS("port"), (void *) &zport)) {
 -                      zval *zcpy = php_http_ztyp(IS_LONG, *zport);
++              if ((zport = zend_hash_str_find(enqueue->options, ZEND_STRL("port")))) {
++                      zend_long lport = zval_get_long(zport);
 -                      if (Z_LVAL_P(zcpy)) {
 -                              port = Z_LVAL_P(zcpy);
++                      if (lport > 0) {
++                              port = lport;
+                       }
 -                      zval_ptr_dtor(&zcpy);
+               }
  
-       pf = php_persistent_handle_concede(NULL, PHP_HTTP_G->client.curl.driver.request_name, id, NULL, NULL);
-       zend_string_release(id);
+               id_len = spprintf(&id_str, 0, "%s:%d", STR_PTR(url->host), port);
 -              pf = php_persistent_handle_concede(NULL, ZEND_STRL("http\\Client\\Curl\\Request"), id_str, id_len, NULL, NULL TSRMLS_CC);
 -              efree(id_str);
++              pf = php_persistent_handle_concede(NULL, PHP_HTTP_G->client.curl.driver.request_name, id, NULL, NULL);
++              zend_string_release(id);
+       }
  
        if (pf) {
                rf = php_resource_factory_init(NULL, php_persistent_handle_get_resource_factory_ops(), pf, (void (*)(void*)) php_persistent_handle_abandon);
@@@ -1947,8 -2112,9 +2148,8 @@@ static ZEND_RESULT_CODE php_http_client
        php_http_client_curl_handler_t *handler;
        php_http_client_progress_state_t *progress;
        php_resource_factory_t *rf;
 -      TSRMLS_FETCH_FROM_CTX(h->ts);
  
-       rf = create_rf(enqueue->request->http.info.request.url);
 -      rf = create_rf(h, enqueue TSRMLS_CC);
++      rf = create_rf(h, enqueue);
        if (!rf) {
                return FAILURE;
        }
@@@ -2167,9 -2325,42 +2356,40 @@@ static ZEND_RESULT_CODE php_http_client
        return SUCCESS;
  }
  
 -static int apply_available_options(void *pDest TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key)
++static int apply_available_options(zval *pDest, int num_args, va_list args, zend_hash_key *hash_key)
+ {
 -      php_http_option_t *opt = pDest;
++      php_http_option_t *opt = Z_PTR_P(pDest);
+       HashTable *ht;
 -      zval *entry;
++      zval entry;
+       int c;
+       ht = va_arg(args, HashTable*);
 -      MAKE_STD_ZVAL(entry);
 -
+       if ((c = zend_hash_num_elements(&opt->suboptions.options))) {
 -              array_init_size(entry, c);
 -              zend_hash_apply_with_arguments(&opt->suboptions.options TSRMLS_CC, apply_available_options, 1, Z_ARRVAL_P(entry));
++              array_init_size(&entry, c);
++              zend_hash_apply_with_arguments(&opt->suboptions.options, apply_available_options, 1, Z_ARRVAL(entry));
+       } else {
+               /* catch deliberate NULL options */
+               if (Z_TYPE(opt->defval) == IS_STRING && !Z_STRVAL(opt->defval)) {
 -                      ZVAL_NULL(entry);
++                      ZVAL_NULL(&entry);
+               } else {
 -                      ZVAL_ZVAL(entry, &opt->defval, 1, 0);
++                      ZVAL_ZVAL(&entry, &opt->defval, 1, 0);
+               }
+       }
 -      if (hash_key->nKeyLength) {
 -              zend_hash_quick_update(ht, hash_key->arKey, hash_key->nKeyLength, hash_key->h, (void *) &entry, sizeof(zval *), NULL);
++      if (hash_key->key) {
++              zend_hash_update(ht, hash_key->key, &entry);
+       } else {
 -              zend_hash_index_update(ht, hash_key->h, (void *) &entry, sizeof(zval *), NULL);
++              zend_hash_index_update(ht, hash_key->h, &entry);
+       }
+       return ZEND_HASH_APPLY_KEEP;
+ }
 -static STATUS php_http_client_curl_getopt(php_http_client_t *h, php_http_client_getopt_opt_t opt, void *arg, void **res)
 +static ZEND_RESULT_CODE php_http_client_curl_getopt(php_http_client_t *h, php_http_client_getopt_opt_t opt, void *arg, void **res)
  {
        php_http_client_enqueue_t *enqueue;
+       TSRMLS_FETCH_FROM_CTX(h->ts);
  
        switch (opt) {
        case PHP_HTTP_CLIENT_OPT_PROGRESS_INFO:
@@@ -2241,8 -2439,14 +2469,14 @@@ PHP_MINIT_FUNCTION(http_client_curl
                options->getter = php_http_curle_get_option;
                options->setter = php_http_curle_set_option;
  
 -              php_http_curle_options_init(options TSRMLS_CC);
 +              php_http_curle_options_init(options);
        }
 -              php_http_curlm_options_init(options TSRMLS_CC);
+       if ((options = php_http_options_init(&php_http_curlm_options, 1))) {
+               options->getter = php_http_option_get;
+               options->setter = php_http_curlm_set_option;
++              php_http_curlm_options_init(options);
+       }
  
        /*
        * HTTP Protocol Version Constants
  
  PHP_MSHUTDOWN_FUNCTION(http_client_curl)
  {
 -      php_persistent_handle_cleanup(ZEND_STRL("http\\Client\\Curl"), NULL, 0 TSRMLS_CC);
 -      php_persistent_handle_cleanup(ZEND_STRL("http\\Client\\Curl\\Request"), NULL, 0 TSRMLS_CC);
 +      php_persistent_handle_cleanup(PHP_HTTP_G->client.curl.driver.client_name, NULL);
 +      php_persistent_handle_cleanup(PHP_HTTP_G->client.curl.driver.request_name, NULL);
 +      zend_string_release(PHP_HTTP_G->client.curl.driver.client_name);
 +      zend_string_release(PHP_HTTP_G->client.curl.driver.request_name);
 +      zend_string_release(PHP_HTTP_G->client.curl.driver.driver_name);
  
        php_http_options_dtor(&php_http_curle_options);
+       php_http_options_dtor(&php_http_curlm_options);
  
        return SUCCESS;
  }
diff --cc php_http_env.c
@@@ -709,12 -638,16 +624,16 @@@ ZEND_BEGIN_ARG_INFO_EX(ai_HttpEnv_getRe
  ZEND_END_ARG_INFO();
  static PHP_METHOD(HttpEnv, getResponseStatusForCode)
  {
 -      long code;
 +      zend_long code;
+       const char *status;
  
        if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &code)) {
                return;
        }
-       RETURN_STRING(php_http_env_get_response_status_for_code(code));
+       if ((status = php_http_env_get_response_status_for_code(code))) {
 -              RETURN_STRING(status, 1);
++              RETURN_STRING(status);
+       }
  }
  
  ZEND_BEGIN_ARG_INFO_EX(ai_HttpEnv_getResponseStatusForAllCodes, 0, 0, 0)
@@@ -730,12 -659,9 +645,9 @@@ static PHP_METHOD(HttpEnv, getResponseS
        }
  
        array_init(return_value);
-       for (   php_http_strlist_iterator_init(&i, php_http_env_response_status, 100);
-                       *(s = php_http_strlist_iterator_this(&i, &c));
-                       php_http_strlist_iterator_next(&i)
-       ) {
-               add_index_string(return_value, c, s);
-       }
 -#define PHP_HTTP_RESPONSE_CODE(code, status) add_index_string(return_value, code, status, 1);
++#define PHP_HTTP_RESPONSE_CODE(code, status) add_index_string(return_value, code, status);
+ #include "php_http_response_codes.h"
+ #undef PHP_HTTP_RESPONSE_CODE
  }
  
  ZEND_BEGIN_ARG_INFO_EX(ai_HttpEnv_getResponseHeader, 0, 0, 0)
@@@ -841,19 -891,25 +842,21 @@@ typedef struct php_http_env_response_st
        unsigned chunked:1;
  } php_http_env_response_stream_ctx_t;
  
 -static STATUS php_http_env_response_stream_init(php_http_env_response_t *r, void *init_arg)
 +static ZEND_RESULT_CODE php_http_env_response_stream_init(php_http_env_response_t *r, void *init_arg)
  {
        php_http_env_response_stream_ctx_t *ctx;
 -      TSRMLS_FETCH_FROM_CTX(r->ts);
+       size_t buffer_size = 0x1000;
  
        ctx = ecalloc(1, sizeof(*ctx));
  
        ctx->stream = init_arg;
 -      if (!ctx->stream || SUCCESS != zend_list_addref(ctx->stream->rsrc_id)) {
 -              efree(ctx);
 -              return FAILURE;
 -      }
 +      ++GC_REFCOUNT(ctx->stream->res);
 +      ZEND_INIT_SYMTABLE(&ctx->header);
 +      php_http_version_init(&ctx->version, 1, 1);
+       php_stream_set_option(ctx->stream, PHP_STREAM_OPTION_WRITE_BUFFER, PHP_STREAM_BUFFER_FULL, &buffer_size);
 -      zend_hash_init(&ctx->header, 0, NULL, ZVAL_PTR_DTOR, 0);
 -      php_http_version_init(&ctx->version, 1, 1 TSRMLS_CC);
        ctx->status_code = 200;
        ctx->chunked = 1;
 -      ctx->request = get_request(r->options TSRMLS_CC);
 +      ctx->request = get_request(&r->options);
  
        /* there are some limitations regarding TE:chunked, see https://tools.ietf.org/html/rfc7230#section-3.3.1 */
        if (ctx->request && ctx->request->http.version.major == 1 && ctx->request->http.version.minor == 0) {
  static void php_http_env_response_stream_dtor(php_http_env_response_t *r)
  {
        php_http_env_response_stream_ctx_t *ctx = r->ctx;
 -      TSRMLS_FETCH_FROM_CTX(r->ts);
  
 -              php_stream_filter_free(ctx->chunked_filter TSRMLS_CC);
+       if (ctx->chunked_filter) {
++              ctx->chunked_filter = php_stream_filter_remove(ctx->chunked_filter, 1);
+       }
        zend_hash_destroy(&ctx->header);
 -      zend_list_delete(ctx->stream->rsrc_id);
 +      zend_list_delete(ctx->stream->res);
        efree(ctx);
        r->ctx = NULL;
  }
- static void php_http_env_response_stream_header(php_http_env_response_stream_ctx_t *ctx, HashTable *header)
 -static void php_http_env_response_stream_header(php_http_env_response_stream_ctx_t *ctx, HashTable *header, php_http_buffer_t *buf TSRMLS_DC)
++static void php_http_env_response_stream_header(php_http_env_response_stream_ctx_t *ctx, HashTable *header, php_http_buffer_t *buf)
  {
 -      HashPosition pos;
 -      zval **val;
 +      zval *val;
  
 -      FOREACH_HASH_VAL(pos, header, val) {
 -              if (Z_TYPE_PP(val) == IS_ARRAY) {
 -                      php_http_env_response_stream_header(ctx, Z_ARRVAL_PP(val), buf TSRMLS_CC);
 +      ZEND_HASH_FOREACH_VAL(header, val)
 +      {
 +              if (Z_TYPE_P(val) == IS_ARRAY) {
-                       php_http_env_response_stream_header(ctx, Z_ARRVAL_P(val));
++                      php_http_env_response_stream_header(ctx, Z_ARRVAL_P(val), buf);
                } else {
 -                      zval *tmp = php_http_ztyp(IS_STRING, *val);
 +                      zend_string *zs = zval_get_string(val);
  
                        if (ctx->chunked) {
                                /* disable chunked transfer encoding if we've got an explicit content-length */
                                        ctx->chunked = 0;
                                }
                        }
-                       php_stream_write(ctx->stream, zs->val, zs->len);
-                       php_stream_write_string(ctx->stream, PHP_HTTP_CRLF);
 -                      php_http_buffer_append(buf, Z_STRVAL_P(tmp), Z_STRLEN_P(tmp));
++                      php_http_buffer_append(buf, zs->val, zs->len);
+                       php_http_buffer_appends(buf, PHP_HTTP_CRLF);
 -                      zval_ptr_dtor(&tmp);
 +                      zend_string_release(zs);
                }
        }
 +      ZEND_HASH_FOREACH_END();
  }
 -static STATUS php_http_env_response_stream_start(php_http_env_response_stream_ctx_t *ctx TSRMLS_DC)
 +static ZEND_RESULT_CODE php_http_env_response_stream_start(php_http_env_response_stream_ctx_t *ctx)
  {
+       php_http_buffer_t header_buf;
        if (ctx->started || ctx->finished) {
                return FAILURE;
        }
                ctx->chunked = 0;
        }
  
-       php_http_env_response_stream_header(ctx, &ctx->header);
 -      php_http_env_response_stream_header(ctx, &ctx->header, &header_buf TSRMLS_CC);
++      php_http_env_response_stream_header(ctx, &ctx->header, &header_buf);
  
        /* enable chunked transfer encoding */
        if (ctx->chunked) {
-               php_stream_write_string(ctx->stream, "Transfer-Encoding: chunked" PHP_HTTP_CRLF);
+               php_http_buffer_appends(&header_buf, "Transfer-Encoding: chunked" PHP_HTTP_CRLF);
        }
-       php_stream_write_string(ctx->stream, PHP_HTTP_CRLF);
 +
+       php_http_buffer_appends(&header_buf, PHP_HTTP_CRLF);
  
-       ctx->started = 1;
+       if (header_buf.used == php_stream_write(ctx->stream, header_buf.data, header_buf.used)) {
+               ctx->started = 1;
+       }
+       php_http_buffer_dtor(&header_buf);
+       php_stream_flush(ctx->stream);
  
-       return SUCCESS;
+       if (ctx->chunked) {
 -              ctx->chunked_filter = php_stream_filter_create("http.chunked_encode", NULL, 0 TSRMLS_CC);
++              ctx->chunked_filter = php_stream_filter_create("http.chunked_encode", NULL, 0);
+               php_stream_filter_append(&ctx->stream->writefilters, ctx->chunked_filter);
+       }
+       return ctx->started ? SUCCESS : FAILURE;
  }
  static long php_http_env_response_stream_get_status(php_http_env_response_t *r)
  {
@@@ -1047,15 -1115,12 +1061,11 @@@ static ZEND_RESULT_CODE php_http_env_re
                return FAILURE;
        }
  
-       if (stream_ctx->chunked && 2 != php_stream_write_string(stream_ctx->stream, PHP_HTTP_CRLF)) {
-               return FAILURE;
-       }
        return SUCCESS;
  }
 -static STATUS php_http_env_response_stream_flush(php_http_env_response_t *r)
 +static ZEND_RESULT_CODE php_http_env_response_stream_flush(php_http_env_response_t *r)
  {
        php_http_env_response_stream_ctx_t *stream_ctx = r->ctx;
 -      TSRMLS_FETCH_FROM_CTX(r->ts);
  
        if (stream_ctx->finished) {
                return FAILURE;
  
        return php_stream_flush(stream_ctx->stream);
  }
 -static STATUS php_http_env_response_stream_finish(php_http_env_response_t *r)
 +static ZEND_RESULT_CODE php_http_env_response_stream_finish(php_http_env_response_t *r)
  {
-       php_http_env_response_stream_ctx_t *stream_ctx = r->ctx;
+       php_http_env_response_stream_ctx_t *ctx = r->ctx;
 -      TSRMLS_FETCH_FROM_CTX(r->ts);
  
-       if (stream_ctx->finished) {
+       if (ctx->finished) {
                return FAILURE;
        }
-       if (!stream_ctx->started) {
-               if (SUCCESS != php_http_env_response_stream_start(stream_ctx)) {
+       if (!ctx->started) {
 -              if (SUCCESS != php_http_env_response_stream_start(ctx TSRMLS_CC)) {
++              if (SUCCESS != php_http_env_response_stream_start(ctx)) {
                        return FAILURE;
                }
        }
Simple merge
@@@ -90,15 -96,33 +94,30 @@@ void php_http_header_parser_free(php_ht
        }
  }
  
 -static void php_http_header_parser_error(size_t valid_len, char *str, size_t len, const char *eol_str TSRMLS_DC)
+ /* NOTE: 'str' has to be null terminated */
 -      int escaped_len;
 -      char *escaped_str;
++static void php_http_header_parser_error(size_t valid_len, char *str, size_t len, const char *eol_str )
+ {
 -      escaped_str = php_addcslashes(str, len, &escaped_len, 0, ZEND_STRL("\x0..\x1F\x7F..\xFF") TSRMLS_CC);
++      zend_string *escaped_str = zend_string_init(str, len, 0);
 -              php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to parse headers: unexpected character '\\%03o' at pos %zu of '%.*s'", str[valid_len], valid_len, escaped_len, escaped_str);
++      escaped_str = php_addcslashes(escaped_str, 1, ZEND_STRL("\x0..\x1F\x7F..\xFF"));
+       if (valid_len != len && (!eol_str || (str+valid_len) != eol_str)) {
 -              php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to parse headers: unexpected end of line at pos %zu of '%.*s'", eol_str - str, escaped_len, escaped_str);
++              php_error_docref(NULL, E_WARNING, "Failed to parse headers: unexpected character '\\%03o' at pos %zu of '%s'", str[valid_len], valid_len, escaped_str->val);
+       } else if (eol_str) {
 -              php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to parse headers: unexpected end of input at pos %zu of '%.*s'", len, escaped_len, escaped_str);
++              php_error_docref(NULL, E_WARNING, "Failed to parse headers: unexpected end of line at pos %zu of '%s'", eol_str - str, escaped_str->val);
+       } else {
 -STATUS php_http_header_parser_parse(php_http_header_parser_t *parser, php_http_buffer_t *buffer, unsigned flags, HashTable *headers, php_http_info_callback_t callback_func, void *callback_arg)
++              php_error_docref(NULL, E_WARNING, "Failed to parse headers: unexpected end of input at pos %zu of '%s'", len, escaped_str->val);
+       }
+       efree(escaped_str);
+ }
 +php_http_header_parser_state_t php_http_header_parser_parse(php_http_header_parser_t *parser, php_http_buffer_t *buffer, unsigned flags, HashTable *headers, php_http_info_callback_t callback_func, void *callback_arg)
  {
 -      TSRMLS_FETCH_FROM_CTX(parser->ts);
 -
        while (buffer->used || !php_http_header_parser_states[php_http_header_parser_state_is(parser)].need_data) {
- #if 0
+ #if DBG_PARSER
                const char *state[] = {"START", "KEY", "VALUE", "VALUE_EX", "HEADER_DONE", "DONE"};
-               int num_headers = headers ? zend_hash_num_elements(headers) : 0;
-               fprintf(stderr, "#HP: (%d) %s (avail:%zu, num:%d)\n", php_http_header_parser_state_is(parser),
-                               php_http_header_parser_state_is(parser) < 0 ? "FAILURE" : state[php_http_header_parser_state_is(parser)],
-                                               buffer->used, num_headers);
+               fprintf(stderr, "#HP: %s (avail:%zu, num:%d cleanup:%u)\n", php_http_header_parser_state_is(parser) < 0 ? "FAILURE" : state[php_http_header_parser_state_is(parser)], buffer->used, headers?zend_hash_num_elements(headers):0, flags);
                _dpf(0, buffer->data, buffer->used);
  #endif
                switch (php_http_header_parser_state_pop(parser)) {
  
                        case PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE:
                                if (parser->_key.str && parser->_val.str) {
 -                                      zval array, **exist;
 +                                      zval tmp, *exist;
+                                       size_t valid_len = strlen(parser->_val.str);
+                                       /* check for truncation */
+                                       if (valid_len != parser->_val.len) {
+                                               php_http_header_parser_error(valid_len, parser->_val.str, parser->_val.len, NULL TSRMLS_CC);
+                                               PTR_SET(parser->_key.str, NULL);
+                                               PTR_SET(parser->_val.str, NULL);
+                                               return php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_FAILURE);
+                                       }
  
                                        if (!headers && callback_func) {
 -                                              callback_func(callback_arg, &headers, NULL TSRMLS_CC);
 +                                              callback_func(callback_arg, &headers, NULL);
                                        }
  
 -                                      INIT_PZVAL_ARRAY(&array, headers);
                                        php_http_pretty_key(parser->_key.str, parser->_key.len, 1, 1);
 -                                      if (SUCCESS == zend_symtable_find(headers, parser->_key.str, parser->_key.len + 1, (void *) &exist)) {
 -                                              convert_to_array(*exist);
 -                                              add_next_index_stringl(*exist, parser->_val.str, parser->_val.len, 0);
 +                                      if ((exist = zend_symtable_str_find(headers, parser->_key.str, parser->_key.len))) {
 +                                              convert_to_array(exist);
 +                                              add_next_index_str(exist, php_http_cs2zs(parser->_val.str, parser->_val.len));
                                        } else {
 -                                              add_assoc_stringl_ex(&array, parser->_key.str, parser->_key.len + 1, parser->_val.str, parser->_val.len, 0);
 +                                              ZVAL_STR(&tmp, php_http_cs2zs(parser->_val.str, parser->_val.len));
 +                                              zend_symtable_str_update(headers, parser->_key.str, parser->_key.len, &tmp);
                                        }
                                        parser->_val.str = NULL;
                                }
        return php_http_header_parser_state_is(parser);
  }
  
 -zend_object_value php_http_header_parser_object_new(zend_class_entry *ce TSRMLS_DC)
+ php_http_header_parser_state_t php_http_header_parser_parse_stream(php_http_header_parser_t *parser, php_http_buffer_t *buf, php_stream *s, unsigned flags, HashTable *headers, php_http_info_callback_t callback_func, void *callback_arg)
+ {
+       php_http_message_parser_state_t state = PHP_HTTP_MESSAGE_PARSER_STATE_START;
+       TSRMLS_FETCH_FROM_CTX(parser->ts);
+       if (!buf->data) {
+               php_http_buffer_resize_ex(buf, 0x1000, 1, 0);
+       }
+       while (1) {
+               size_t justread = 0;
+ #if DBG_PARSER
+               const char *states[] = {"START", "KEY", "VALUE", "VALUE_EX", "HEADER_DONE", "DONE"};
+               fprintf(stderr, "#SHP: %s (f:%u)\n", states[state], flags);
+ #endif
+               /* resize if needed */
+               if (buf->free < 0x1000) {
+                       php_http_buffer_resize_ex(buf, 0x1000, 1, 0);
+               }
+               switch (state) {
+               case PHP_HTTP_HEADER_PARSER_STATE_FAILURE:
+               case PHP_HTTP_HEADER_PARSER_STATE_DONE:
+                       return state;
+               default:
+                       /* read line */
+                       php_stream_get_line(s, buf->data + buf->used, buf->free, &justread);
+                       /* if we fail reading a whole line, try a single char */
+                       if (!justread) {
+                               int c = php_stream_getc(s);
+                               if (c != EOF) {
+                                       char s[1] = {c};
+                                       justread = php_http_buffer_append(buf, s, 1);
+                               }
+                       }
+                       php_http_buffer_account(buf, justread);
+               }
+               if (justread) {
+                       state = php_http_header_parser_parse(parser, buf, flags, headers, callback_func, callback_arg);
+               } else if (php_stream_eof(s)) {
+                       return php_http_header_parser_parse(parser, buf, flags | PHP_HTTP_HEADER_PARSER_CLEANUP, headers, callback_func, callback_arg);
+               } else  {
+                       return state;
+               }
+       }
+       return PHP_HTTP_HEADER_PARSER_STATE_DONE;
+ }
+ zend_class_entry *php_http_header_parser_class_entry;
+ static zend_object_handlers php_http_header_parser_object_handlers;
 -      return php_http_header_parser_object_new_ex(ce, NULL, NULL TSRMLS_CC);
++zend_object *php_http_header_parser_object_new(zend_class_entry *ce)
+ {
 -zend_object_value php_http_header_parser_object_new_ex(zend_class_entry *ce, php_http_header_parser_t *parser, php_http_header_parser_object_t **ptr TSRMLS_DC)
++      return &php_http_header_parser_object_new_ex(ce, NULL)->zo;
+ }
 -      o = ecalloc(1, sizeof(php_http_header_parser_object_t));
 -      zend_object_std_init((zend_object *) o, ce TSRMLS_CC);
 -      object_properties_init((zend_object *) o, ce);
 -
 -      if (ptr) {
 -              *ptr = o;
 -      }
++php_http_header_parser_object_t *php_http_header_parser_object_new_ex(zend_class_entry *ce, php_http_header_parser_t *parser)
+ {
+       php_http_header_parser_object_t *o;
 -              o->parser = php_http_header_parser_init(NULL TSRMLS_CC);
++      o = ecalloc(1, sizeof(php_http_header_parser_object_t) + zend_object_properties_size(ce));
++      zend_object_std_init(&o->zo, ce);
++      object_properties_init(&o->zo, ce);
+       if (parser) {
+               o->parser = parser;
+       } else {
 -      o->zv.handle = zend_objects_store_put((zend_object *) o, NULL, php_http_header_parser_object_free, NULL TSRMLS_CC);
 -      o->zv.handlers = &php_http_header_parser_object_handlers;
++              o->parser = php_http_header_parser_init(NULL);
+       }
+       o->buffer = php_http_buffer_new();
 -      return o->zv;
++      o->zo.handlers = &php_http_header_parser_object_handlers;
 -void php_http_header_parser_object_free(void *object TSRMLS_DC)
++      return o;
+ }
 -      php_http_header_parser_object_t *o = (php_http_header_parser_object_t *) object;
++void php_http_header_parser_object_free(zend_object *object)
+ {
 -      zend_object_std_dtor((zend_object *) o TSRMLS_CC);
 -      efree(o);
++      php_http_header_parser_object_t *o = PHP_HTTP_OBJ(object, NULL);
+       if (o->parser) {
+               php_http_header_parser_free(&o->parser);
+       }
+       if (o->buffer) {
+               php_http_buffer_free(&o->buffer);
+       }
 -      php_http_header_parser_object_t *parser_obj = zend_object_store_get_object(getThis() TSRMLS_CC);
++      zend_object_std_dtor(object);
+ }
+ ZEND_BEGIN_ARG_INFO_EX(ai_HttpHeaderParser_getState, 0, 0, 0)
+ ZEND_END_ARG_INFO();
+ static PHP_METHOD(HttpHeaderParser, getState)
+ {
 -      int data_len;
 -      long flags;
++      php_http_header_parser_object_t *parser_obj = PHP_HTTP_OBJ(NULL, getThis());
+       zend_parse_parameters_none();
+       /* always return the real state */
+       RETVAL_LONG(php_http_header_parser_state_is(parser_obj->parser));
+ }
+ ZEND_BEGIN_ARG_INFO_EX(ai_HttpHeaderParser_parse, 0, 0, 3)
+       ZEND_ARG_INFO(0, data)
+       ZEND_ARG_INFO(0, flags)
+       ZEND_ARG_ARRAY_INFO(1, headers, 1)
+ ZEND_END_ARG_INFO();
+ static PHP_METHOD(HttpHeaderParser, parse)
+ {
+       php_http_header_parser_object_t *parser_obj;
+       zval *zmsg;
+       char *data_str;
 -      php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "slz", &data_str, &data_len, &flags, &zmsg), invalid_arg, return);
++      size_t data_len;
++      zend_long flags;
 -      parser_obj = zend_object_store_get_object(getThis() TSRMLS_CC);
++      php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS(), "slz", &data_str, &data_len, &flags, &zmsg), invalid_arg, return);
++      ZVAL_DEREF(zmsg);
+       if (Z_TYPE_P(zmsg) != IS_ARRAY) {
+               zval_dtor(zmsg);
+               array_init(zmsg);
+       }
 -      long flags;
++      parser_obj = PHP_HTTP_OBJ(NULL, getThis());
+       php_http_buffer_append(parser_obj->buffer, data_str, data_len);
+       RETVAL_LONG(php_http_header_parser_parse(parser_obj->parser, parser_obj->buffer, flags, Z_ARRVAL_P(zmsg), NULL, NULL));
+ }
+ ZEND_BEGIN_ARG_INFO_EX(ai_HttpHeaderParser_stream, 0, 0, 3)
+       ZEND_ARG_INFO(0, stream)
+       ZEND_ARG_INFO(0, flags)
+       ZEND_ARG_ARRAY_INFO(1, headers, 1)
+ ZEND_END_ARG_INFO();
+ static PHP_METHOD(HttpHeaderParser, stream)
+ {
+       php_http_header_parser_object_t *parser_obj;
+       zend_error_handling zeh;
+       zval *zmsg, *zstream;
+       php_stream *s;
 -      php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rlz", &zstream, &flags, &zmsg), invalid_arg, return);
++      zend_long flags;
 -      zend_replace_error_handling(EH_THROW, php_http_exception_unexpected_val_class_entry, &zeh TSRMLS_CC);
 -      php_stream_from_zval(s, &zstream);
 -      zend_restore_error_handling(&zeh TSRMLS_CC);
++      php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS(), "rlz", &zstream, &flags, &zmsg), invalid_arg, return);
 -      parser_obj = zend_object_store_get_object(getThis() TSRMLS_CC);
++      zend_replace_error_handling(EH_THROW, php_http_exception_unexpected_val_class_entry, &zeh);
++      php_stream_from_zval(s, zstream);
++      zend_restore_error_handling(&zeh);
++      ZVAL_DEREF(zmsg);
+       if (Z_TYPE_P(zmsg) != IS_ARRAY) {
+               zval_dtor(zmsg);
+               array_init(zmsg);
+       }
 -      php_http_header_parser_class_entry = zend_register_internal_class(&ce TSRMLS_CC);
++      parser_obj = PHP_HTTP_OBJ(NULL, getThis());
+       RETVAL_LONG(php_http_header_parser_parse_stream(parser_obj->parser, parser_obj->buffer, s, flags, Z_ARRVAL_P(zmsg), NULL, NULL));
+ }
+ static zend_function_entry php_http_header_parser_methods[] = {
+               PHP_ME(HttpHeaderParser, getState, ai_HttpHeaderParser_getState, ZEND_ACC_PUBLIC)
+               PHP_ME(HttpHeaderParser, parse, ai_HttpHeaderParser_parse, ZEND_ACC_PUBLIC)
+               PHP_ME(HttpHeaderParser, stream, ai_HttpHeaderParser_stream, ZEND_ACC_PUBLIC)
+               {NULL, NULL, NULL}
+ };
+ PHP_MINIT_FUNCTION(http_header_parser)
+ {
+       zend_class_entry ce;
+       INIT_NS_CLASS_ENTRY(ce, "http\\Header", "Parser", php_http_header_parser_methods);
 -      zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("CLEANUP"), PHP_HTTP_HEADER_PARSER_CLEANUP TSRMLS_CC);
++      php_http_header_parser_class_entry = zend_register_internal_class(&ce);
+       memcpy(&php_http_header_parser_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
+       php_http_header_parser_class_entry->create_object = php_http_header_parser_object_new;
++      php_http_header_parser_object_handlers.offset = XtOffsetOf(php_http_header_parser_object_t, zo);
+       php_http_header_parser_object_handlers.clone_obj = NULL;
++      php_http_header_parser_object_handlers.free_obj = php_http_header_parser_object_free;
 -      zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_FAILURE"), PHP_HTTP_HEADER_PARSER_STATE_FAILURE TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_START"), PHP_HTTP_HEADER_PARSER_STATE_START TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_KEY"), PHP_HTTP_HEADER_PARSER_STATE_KEY TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_VALUE"), PHP_HTTP_HEADER_PARSER_STATE_VALUE TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_VALUE_EX"), PHP_HTTP_HEADER_PARSER_STATE_VALUE_EX TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_HEADER_DONE"), PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_DONE"), PHP_HTTP_HEADER_PARSER_STATE_DONE TSRMLS_CC);
++      zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("CLEANUP"), PHP_HTTP_HEADER_PARSER_CLEANUP);
++      zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_FAILURE"), PHP_HTTP_HEADER_PARSER_STATE_FAILURE);
++      zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_START"), PHP_HTTP_HEADER_PARSER_STATE_START);
++      zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_KEY"), PHP_HTTP_HEADER_PARSER_STATE_KEY);
++      zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_VALUE"), PHP_HTTP_HEADER_PARSER_STATE_VALUE);
++      zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_VALUE_EX"), PHP_HTTP_HEADER_PARSER_STATE_VALUE_EX);
++      zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_HEADER_DONE"), PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE);
++      zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_DONE"), PHP_HTTP_HEADER_PARSER_STATE_DONE);
+       return SUCCESS;
+ }
  /*
   * Local variables:
   * tab-width: 4
@@@ -47,6 -50,22 +47,21 @@@ PHP_HTTP_API php_http_header_parser_sta
  PHP_HTTP_API void php_http_header_parser_dtor(php_http_header_parser_t *parser);
  PHP_HTTP_API void php_http_header_parser_free(php_http_header_parser_t **parser);
  PHP_HTTP_API php_http_header_parser_state_t php_http_header_parser_parse(php_http_header_parser_t *parser, php_http_buffer_t *buffer, unsigned flags, HashTable *headers, php_http_info_callback_t callback_func, void *callback_arg);
 -      zend_object zo;
 -      zend_object_value zv;
+ PHP_HTTP_API php_http_header_parser_state_t php_http_headerparser_parse_stream(php_http_header_parser_t *parser, php_http_buffer_t *buffer, php_stream *s, unsigned flags, HashTable *headers, php_http_info_callback_t callback_func, void *callback_arg);
+ typedef struct php_http_header_parser_object {
 -zend_object_value php_http_header_parser_object_new(zend_class_entry *ce TSRMLS_DC);
 -zend_object_value php_http_header_parser_object_new_ex(zend_class_entry *ce, php_http_header_parser_t *parser, php_http_header_parser_object_t **ptr TSRMLS_DC);
 -void php_http_header_parser_object_free(void *object TSRMLS_DC);
+       php_http_buffer_t *buffer;
+       php_http_header_parser_t *parser;
++      zend_object zo;
+ } php_http_header_parser_object_t;
+ PHP_HTTP_API zend_class_entry *php_http_header_parser_class_entry;
+ PHP_MINIT_FUNCTION(http_header_parser);
++zend_object *php_http_header_parser_object_new(zend_class_entry *ce);
++php_http_header_parser_object_t *php_http_header_parser_object_new_ex(zend_class_entry *ce, php_http_header_parser_t *parser);
++void php_http_header_parser_object_free(zend_object *object);
  
  #endif /* PHP_HTTP_HEADER_PARSER_H */
  
@@@ -275,33 -297,40 +275,36 @@@ void php_http_message_update_headers(ph
  
        if (php_http_message_body_stream(msg->body)->readfilters.head) {
                /* if a read stream filter is attached to the body the caller must also care for the headers */
 -      } else if ((h = php_http_message_header(msg, ZEND_STRL("Content-Range"), 0))) {
++      } else if (php_http_message_header(msg, ZEND_STRL("Content-Range"))) {
+               /* don't mess around with a Content-Range message */
 -              zval_ptr_dtor(&h);
        } else if ((size = php_http_message_body_size(msg->body))) {
 -              MAKE_STD_ZVAL(h);
 -              ZVAL_LONG(h, size);
 -              zend_hash_update(&msg->hdrs, "Content-Length", sizeof("Content-Length"), &h, sizeof(zval *), NULL);
 +              ZVAL_LONG(&h, size);
 +              zend_hash_str_update(&msg->hdrs, "Content-Length", lenof("Content-Length"), &h);
  
                if (msg->body->boundary) {
                        char *str;
                        size_t len;
 +                      zend_string *ct;
  
 -                      if (!(h = php_http_message_header(msg, ZEND_STRL("Content-Type"), 1))) {
 +                      if (!(ct = php_http_message_header_string(msg, ZEND_STRL("Content-Type")))) {
                                len = spprintf(&str, 0, "multipart/form-data; boundary=\"%s\"", msg->body->boundary);
 -                              MAKE_STD_ZVAL(h);
 -                              ZVAL_STRINGL(h, str, len, 0);
 -                              zend_hash_update(&msg->hdrs, "Content-Type", sizeof("Content-Type"), &h, sizeof(zval *), NULL);
 -                      } else if (!php_http_match(Z_STRVAL_P(h), "boundary=", PHP_HTTP_MATCH_WORD)) {
 -                              zval_dtor(h);
 -                              Z_STRLEN_P(h) = spprintf(&Z_STRVAL_P(h), 0, "%s; boundary=\"%s\"", Z_STRVAL_P(h), msg->body->boundary);
 -                              zend_hash_update(&msg->hdrs, "Content-Type", sizeof("Content-Type"), &h, sizeof(zval *), NULL);
 +                              ZVAL_STR(&h, php_http_cs2zs(str, len));
 +                              zend_hash_str_update(&msg->hdrs, "Content-Type", lenof("Content-Type"), &h);
 +                      } else if (!php_http_match(ct->val, "boundary=", PHP_HTTP_MATCH_WORD)) {
 +                              len = spprintf(&str, 0, "%s; boundary=\"%s\"", ct->val, msg->body->boundary);
 +                              ZVAL_STR(&h, php_http_cs2zs(str, len));
 +                              zend_hash_str_update(&msg->hdrs, "Content-Type", lenof("Content-Type"), &h);
 +                              zend_string_release(ct);
                        } else {
 -                              zval_ptr_dtor(&h);
 +                              zend_string_release(ct);
                        }
                }
 -      } else if ((h = php_http_message_header(msg, ZEND_STRL("Content-Length"), 1))) {
 -              zval *h_cpy = php_http_ztyp(IS_LONG, h);
 -
 -              zval_ptr_dtor(&h);
 -              if (Z_LVAL_P(h_cpy)) {
 +      } else if ((cl = php_http_message_header_string(msg, ZEND_STRL("Content-Length")))) {
 +              if (!zend_string_equals_literal(cl, "0")) {
+                       /* body->size == 0, so get rid of old Content-Length */
 -                      zend_hash_del(&msg->hdrs, "Content-Length", sizeof("Content-Length"));
 +                      zend_hash_str_del(&msg->hdrs, ZEND_STRL("Content-Length"));
                }
 -              zval_ptr_dtor(&h_cpy);
 +              zend_string_release(cl);
        }
  }
  
@@@ -112,8 -118,9 +112,8 @@@ const char *php_http_message_body_bound
  {
        if (!body->boundary) {
                union { double dbl; int num[2]; } data;
 -              TSRMLS_FETCH_FROM_CTX(body->ts);
  
--              data.dbl = php_combined_lcg(TSRMLS_C);
++              data.dbl = php_combined_lcg();
                spprintf(&body->boundary, 0, "%x.%x", data.num[0], data.num[1]);
        }
        return body->boundary;
  
  char *php_http_message_body_etag(php_http_message_body_t *body)
  {
-       const php_stream_statbuf *ssb = php_http_message_body_stat(body);
+       php_http_etag_t *etag;
+       php_stream *s = php_http_message_body_stream(body);
 -      TSRMLS_FETCH_FROM_CTX(body->ts);
  
        /* real file or temp buffer ? */
-       if (ssb->sb.st_mtime) {
-               char *etag;
+       if (s->ops != &php_stream_temp_ops && s->ops != &php_stream_memory_ops) {
+               php_stream_stat(php_http_message_body_stream(body), &body->ssb);
  
-               spprintf(&etag, 0, "%lx-%lx-%lx", ssb->sb.st_ino, ssb->sb.st_mtime, ssb->sb.st_size);
-               return etag;
-       } else {
-               php_http_etag_t *etag = php_http_etag_init(PHP_HTTP_G->env.etag_mode);
+               if (body->ssb.sb.st_mtime) {
+                       char *etag;
  
-               if (etag) {
-                       php_http_message_body_to_callback(body, (php_http_pass_callback_t) php_http_etag_update, etag, 0, 0);
-                       return php_http_etag_finish(etag);
-               } else {
-                       return NULL;
+                       spprintf(&etag, 0, "%lx-%lx-%lx", body->ssb.sb.st_ino, body->ssb.sb.st_mtime, body->ssb.sb.st_size);
+                       return etag;
                }
        }
 -      if ((etag = php_http_etag_init(PHP_HTTP_G->env.etag_mode TSRMLS_CC))) {
+       /* content based */
++      if ((etag = php_http_etag_init(PHP_HTTP_G->env.etag_mode))) {
+               php_http_message_body_to_callback(body, (php_http_pass_callback_t) php_http_etag_update, etag, 0, 0);
+               return php_http_etag_finish(etag);
+       }
+       return NULL;
  }
  
 -void php_http_message_body_to_string(php_http_message_body_t *body, char **buf, size_t *len, off_t offset, size_t forlen)
 +zend_string *php_http_message_body_to_string(php_http_message_body_t *body, off_t offset, size_t forlen)
  {
        php_stream *s = php_http_message_body_stream(body);
 -      TSRMLS_FETCH_FROM_CTX(body->ts);
  
        php_stream_seek(s, offset, SEEK_SET);
        if (!forlen) {
@@@ -212,7 -228,7 +216,7 @@@ size_t php_http_message_body_append(php
        written = php_stream_write(s, buf, len);
  
        if (written != len) {
--              php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to append %zu bytes to body; wrote %zu", len, written);
++              php_error_docref(NULL, E_WARNING, "Failed to append %zu bytes to body; wrote %zu", len, written);
        }
  
        return len;
@@@ -562,29 -589,32 +566,29 @@@ php_http_message_body_object_t *php_htt
                o->body = body;
        }
  
 -      o->zv.handle = zend_objects_store_put((zend_object *) o, NULL, php_http_message_body_object_free, NULL TSRMLS_CC);
 -      o->zv.handlers = &php_http_message_body_object_handlers;
 +      o->zo.handlers = &php_http_message_body_object_handlers;
  
 -      return o->zv;
 +      return o;
  }
  
 -zend_object_value php_http_message_body_object_clone(zval *object TSRMLS_DC)
 +zend_object *php_http_message_body_object_clone(zval *object)
  {
 -      zend_object_value new_ov;
        php_http_message_body_object_t *new_obj = NULL;
 -      php_http_message_body_object_t *old_obj = zend_object_store_get_object(object TSRMLS_CC);
 +      php_http_message_body_object_t *old_obj = PHP_HTTP_OBJ(NULL, object);
        php_http_message_body_t *body = php_http_message_body_copy(old_obj->body, NULL);
  
 -      new_ov = php_http_message_body_object_new_ex(old_obj->zo.ce, body, &new_obj TSRMLS_CC);
 -      zend_objects_clone_members(&new_obj->zo, new_ov, &old_obj->zo, Z_OBJ_HANDLE_P(object) TSRMLS_CC);
 +      new_obj = php_http_message_body_object_new_ex(old_obj->zo.ce, body);
 +      zend_objects_clone_members(&new_obj->zo, &old_obj->zo);
  
 -      return new_ov;
 +      return &new_obj->zo;
  }
  
 -void php_http_message_body_object_free(void *object TSRMLS_DC)
 +void php_http_message_body_object_free(zend_object *object)
  {
 -      php_http_message_body_object_t *obj = object;
 +      php_http_message_body_object_t *obj = PHP_HTTP_OBJ(object, NULL);
  
        php_http_message_body_free(&obj->body);
-       zend_object_std_dtor(object TSRMLS_CC);
 -      zend_object_std_dtor((zend_object *) obj TSRMLS_CC);
 -      efree(obj);
++      zend_object_std_dtor(object);
  }
  
  #define PHP_HTTP_MESSAGE_BODY_OBJECT_INIT(obj) \
@@@ -811,10 -848,10 +815,10 @@@ ZEND_END_ARG_INFO()
  PHP_METHOD(HttpMessageBody, stat)
  {
        char *field_str = NULL;
 -      int field_len = 0;
 +      size_t field_len = 0;
  
--      if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s", &field_str, &field_len)) {
 -              php_http_message_body_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
++      if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS(), "|s", &field_str, &field_len)) {
 +              php_http_message_body_object_t *obj = PHP_HTTP_OBJ(NULL, getThis());
                const php_stream_statbuf *sb;
  
                PHP_HTTP_MESSAGE_BODY_OBJECT_INIT(obj);
@@@ -247,93 -268,92 +264,94 @@@ php_http_message_parser_state_t php_htt
  
                        case PHP_HTTP_MESSAGE_PARSER_STATE_HEADER_DONE:
                        {
 -                              zval *h, *h_loc = NULL, *h_con = NULL, **h_cl = NULL, **h_cr = NULL, **h_te = NULL;
 +                              zval h, *h_ptr, *h_loc = NULL, *h_con = NULL, *h_ce;
 +                              zend_bool chunked = 0;
 +                              zend_long content_length = -1;
 +                              zend_string *content_range = NULL;
  
 -                              if ((h = php_http_message_header(*message, ZEND_STRL("Transfer-Encoding"), 1))) {
 -                                      zend_hash_update(&(*message)->hdrs, "X-Original-Transfer-Encoding", sizeof("X-Original-Transfer-Encoding"), (void *) &h, sizeof(zval *), (void *) &h_te);
 -                                      zend_hash_del(&(*message)->hdrs, "Transfer-Encoding", sizeof("Transfer-Encoding"));
+                               /* Content-Range has higher precedence than Content-Length,
+                                * and content-length denotes the original length of the entity,
+                                * so let's *NOT* remove CR/CL, because that would fundamentally
+                                * change the meaning of the whole message
+                                */
 +                              if ((h_ptr = php_http_message_header(*message, ZEND_STRL("Transfer-Encoding")))) {
 +                                      zend_string *zs = zval_get_string(h_ptr);
 +
 +                                      chunked = zend_string_equals_literal(zs, "chunked");
 +                                      zend_string_release(zs);
 +
 +                                      Z_TRY_ADDREF_P(h_ptr);
 +                                      zend_hash_str_update(&(*message)->hdrs, "X-Original-Transfer-Encoding", lenof("X-Original-Transfer-Encoding"), h_ptr);
 +                                      zend_hash_str_del(&(*message)->hdrs, "Transfer-Encoding", lenof("Transfer-Encoding"));
  
-                               }
-                               if ((h_ptr = php_http_message_header(*message, ZEND_STRL("Content-Length")))) {
+                                       /* reset */
 -                                      MAKE_STD_ZVAL(h);
 -                                      ZVAL_LONG(h, 0);
 -                                      zend_hash_update(&(*message)->hdrs, "Content-Length", sizeof("Content-Length"), (void *) &h, sizeof(zval *), NULL);
 -                              } else if ((h = php_http_message_header(*message, ZEND_STRL("Content-Length"), 1))) {
 -                                      zend_hash_update(&(*message)->hdrs, "X-Original-Content-Length", sizeof("X-Original-Content-Length"), (void *) &h, sizeof(zval *), (void *) &h_cl);
++                                      ZVAL_LONG(&h, 0);
++                                      zend_hash_str_update(&(*message)->hdrs, "Content-Length", lenof("Content-Length"), &h);
++                              } else if ((h_ptr = php_http_message_header(*message, ZEND_STRL("Content-Length")))) {
 +                                      content_length = zval_get_long(h_ptr);
 +                                      Z_TRY_ADDREF_P(h_ptr);
 +                                      zend_hash_str_update(&(*message)->hdrs, "X-Original-Content-Length", lenof("X-Original-Content-Length"), h_ptr);
                                }
-                               if ((h_ptr = php_http_message_header(*message, ZEND_STRL("Content-Range")))) {
-                                       content_range = zval_get_string(h_ptr);
  
-                                       Z_TRY_ADDREF_P(h_ptr);
-                                       zend_hash_str_update(&(*message)->hdrs, "X-Original-Content-Range", lenof("X-Original-Content-Range"), h_ptr);
-                                       zend_hash_str_del(&(*message)->hdrs, "Content-Range", lenof("Content-Range"));
 -                              if ((h = php_http_message_header(*message, ZEND_STRL("Content-Range"), 1))) {
 -                                      zend_hash_find(&(*message)->hdrs, ZEND_STRS("Content-Range"), (void *) &h_cr);
 -                                      if (h != *h_cr) {
 -                                              zend_hash_update(&(*message)->hdrs, "Content-Range", sizeof("Content-Range"), &h, sizeof(zval *), (void *) &h_cr);
 -                                      } else {
 -                                              zval_ptr_dtor(&h);
 -                                      }
++                              if ((content_range = php_http_message_header_string(*message, ZEND_STRL("Content-Range")))) {
++                                      ZVAL_STR_COPY(&h, content_range);
++                                      zend_hash_str_update(&(*message)->hdrs, "Content-Range", lenof("Content-Range"), &h);
                                }
  
-                               /* default */
-                               ZVAL_LONG(&h, 0);
-                               zend_hash_str_update(&(*message)->hdrs, "Content-Length", lenof("Content-Length"), &h);
                                /* so, if curl sees a 3xx code, a Location header and a Connection:close header
                                 * it decides not to read the response body.
                                 */
                                if ((flags & PHP_HTTP_MESSAGE_PARSER_EMPTY_REDIRECTS)
                                &&      (*message)->type == PHP_HTTP_RESPONSE
                                &&      (*message)->http.info.response.code/100 == 3
 -                              &&      (h_loc = php_http_message_header(*message, ZEND_STRL("Location"), 1))
 -                              &&      (h_con = php_http_message_header(*message, ZEND_STRL("Connection"), 1))
 +                              &&      (h_loc = php_http_message_header(*message, ZEND_STRL("Location")))
 +                              &&      (h_con = php_http_message_header(*message, ZEND_STRL("Connection")))
                                ) {
--                                      if (php_http_match(Z_STRVAL_P(h_con), "close", PHP_HTTP_MATCH_WORD)) {
++                                      zend_string *con = zval_get_string(h_con);
++
++                                      if (php_http_match(con->val, "close", PHP_HTTP_MATCH_WORD)) {
++                                              zend_string_release(con);
                                                php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_DONE);
 -                                              zval_ptr_dtor(&h_loc);
 -                                              zval_ptr_dtor(&h_con);
                                                break;
                                        }
++                                      zend_string_release(con);
                                }
 -                              if (h_loc) {
 -                                      zval_ptr_dtor(&h_loc);
 -                              }
 -                              if (h_con) {
 -                                      zval_ptr_dtor(&h_con);
 -                              }
  
 -                              if ((h = php_http_message_header(*message, ZEND_STRL("Content-Encoding"), 1))) {
 -                                      if (php_http_match(Z_STRVAL_P(h), "gzip", PHP_HTTP_MATCH_WORD)
 -                                      ||      php_http_match(Z_STRVAL_P(h), "x-gzip", PHP_HTTP_MATCH_WORD)
 -                                      ||      php_http_match(Z_STRVAL_P(h), "deflate", PHP_HTTP_MATCH_WORD)
 +                              if ((h_ce = php_http_message_header(*message, ZEND_STRL("Content-Encoding")))) {
-                                       if (php_http_match(Z_STRVAL_P(h_ce), "gzip", PHP_HTTP_MATCH_WORD)
-                                       ||      php_http_match(Z_STRVAL_P(h_ce), "x-gzip", PHP_HTTP_MATCH_WORD)
-                                       ||      php_http_match(Z_STRVAL_P(h_ce), "deflate", PHP_HTTP_MATCH_WORD)
++                                      zend_string *ce = zval_get_string(h_ce);
++
++                                      if (php_http_match(ce->val, "gzip", PHP_HTTP_MATCH_WORD)
++                                      ||      php_http_match(ce->val, "x-gzip", PHP_HTTP_MATCH_WORD)
++                                      ||      php_http_match(ce->val, "deflate", PHP_HTTP_MATCH_WORD)
                                        ) {
                                                if (parser->inflate) {
                                                        php_http_encoding_stream_reset(&parser->inflate);
                                                } else {
 -                                                      parser->inflate = php_http_encoding_stream_init(NULL, php_http_encoding_stream_get_inflate_ops(), 0 TSRMLS_CC);
 +                                                      parser->inflate = php_http_encoding_stream_init(NULL, php_http_encoding_stream_get_inflate_ops(), 0);
                                                }
 -                                              zend_hash_update(&(*message)->hdrs, "X-Original-Content-Encoding", sizeof("X-Original-Content-Encoding"), &h, sizeof(zval *), NULL);
 -                                              zend_hash_del(&(*message)->hdrs, "Content-Encoding", sizeof("Content-Encoding"));
 -                                      } else {
 -                                              zval_ptr_dtor(&h);
 +                                              Z_TRY_ADDREF_P(h_ce);
 +                                              zend_hash_str_update(&(*message)->hdrs, "X-Original-Content-Encoding", lenof("X-Original-Content-Encoding"), h_ce);
 +                                              zend_hash_str_del(&(*message)->hdrs, "Content-Encoding", lenof("Content-Encoding"));
                                        }
++                                      zend_string_release(ce);
                                }
  
                                if ((flags & PHP_HTTP_MESSAGE_PARSER_DUMB_BODIES)) {
                                        php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DUMB);
                                } else {
 -                                      if (h_te) {
 -                                              if (strstr(Z_STRVAL_PP(h_te), "chunked")) {
 -                                                      parser->dechunk = php_http_encoding_stream_init(parser->dechunk, php_http_encoding_stream_get_dechunk_ops(), 0 TSRMLS_CC);
 -                                                      php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_BODY_CHUNKED);
 -                                                      break;
 -                                              }
 +                                      if (chunked) {
 +                                              parser->dechunk = php_http_encoding_stream_init(parser->dechunk, php_http_encoding_stream_get_dechunk_ops(), 0);
 +                                              php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_BODY_CHUNKED);
 +                                              break;
                                        }
  
-                                       if (content_length >= 0) {
-                                               parser->body_length = content_length;
-                                               php_http_message_parser_state_push(parser, 1, !parser->body_length?PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE:PHP_HTTP_MESSAGE_PARSER_STATE_BODY_LENGTH);
-                                               break;
-                                       }
 -                                      if (h_cr) {
 +                                      if (content_range) {
                                                ulong total = 0, start = 0, end = 0;
  
 -                                              if (!strncasecmp(Z_STRVAL_PP(h_cr), "bytes", lenof("bytes"))
 -                                              && (    Z_STRVAL_PP(h_cr)[lenof("bytes")] == ':'
 -                                                      ||      Z_STRVAL_PP(h_cr)[lenof("bytes")] == ' '
 -                                                      ||      Z_STRVAL_PP(h_cr)[lenof("bytes")] == '='
 +                                              if (!strncasecmp(content_range->val, "bytes", lenof("bytes"))
 +                                              && (    content_range->val[lenof("bytes")] == ':'
 +                                                      ||      content_range->val[lenof("bytes")] == ' '
 +                                                      ||      content_range->val[lenof("bytes")] == '='
                                                        )
                                                ) {
                                                        char *total_at = NULL, *end_at = NULL;
                                                                        total = strtoul(total_at + 1, NULL, 10);
                                                                }
  
-                                                               if (end >= start && (!total || end < total)) {
+                                                               if (end >= start && (!total || end <= total)) {
                                                                        parser->body_length = end + 1 - start;
                                                                        php_http_message_parser_state_push(parser, 1, !parser->body_length?PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE:PHP_HTTP_MESSAGE_PARSER_STATE_BODY_LENGTH);
 +                                                                      zend_string_release(content_range);
                                                                        break;
                                                                }
                                                        }
                                                }
 -                                      }
  
 -                                      if (h_cl) {
 -                                              char *stop;
 -
 -                                              if (Z_TYPE_PP(h_cl) == IS_STRING) {
 -                                                      parser->body_length = strtoul(Z_STRVAL_PP(h_cl), &stop, 10);
 +                                              zend_string_release(content_range);
 +                                      }
  
 -                                                      if (stop != Z_STRVAL_PP(h_cl)) {
 -                                                              php_http_message_parser_state_push(parser, 1, !parser->body_length?PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE:PHP_HTTP_MESSAGE_PARSER_STATE_BODY_LENGTH);
 -                                                              break;
 -                                                      }
 -                                              } else if (Z_TYPE_PP(h_cl) == IS_LONG) {
 -                                                      parser->body_length = Z_LVAL_PP(h_cl);
 -                                                      php_http_message_parser_state_push(parser, 1, !parser->body_length?PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE:PHP_HTTP_MESSAGE_PARSER_STATE_BODY_LENGTH);
 -                                                      break;
 -                                              }
++                                      if (content_length >= 0) {
++                                              parser->body_length = content_length;
++                                              php_http_message_parser_state_push(parser, 1, !parser->body_length?PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE:PHP_HTTP_MESSAGE_PARSER_STATE_BODY_LENGTH);
++                                              break;
+                                       }
  
                                        if ((*message)->type == PHP_HTTP_REQUEST) {
                                                php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_DONE);
                        case PHP_HTTP_MESSAGE_PARSER_STATE_BODY:
                        {
                                if (len) {
-                                       zval zcl;
 -                                      /* FIXME: what if we re-use the parser? */
                                        if (parser->inflate) {
                                                char *dec_str = NULL;
                                                size_t dec_len;
                                        }
  
                                        php_stream_write(php_http_message_body_stream((*message)->body), str, len);
--
-                                       /* keep track */
-                                       ZVAL_LONG(&zcl, php_http_message_body_size((*message)->body));
-                                       zend_hash_str_update(&(*message)->hdrs, "Content-Length", lenof("Content-Length"), &zcl);
                                }
  
                                if (cut) {
                                break;
                        }
  
-                       case PHP_HTTP_MESSAGE_PARSER_STATE_DONE: {
+                       case PHP_HTTP_MESSAGE_PARSER_STATE_UPDATE_CL:
+                       {
 -                              zval *zcl;
 -                              MAKE_STD_ZVAL(zcl);
 -                              ZVAL_LONG(zcl, php_http_message_body_size((*message)->body));
 -                              zend_hash_update(&(*message)->hdrs, "Content-Length", sizeof("Content-Length"), &zcl, sizeof(zval *), NULL);
++                              zval zcl;
++
++                              ZVAL_LONG(&zcl, php_http_message_body_size((*message)->body));
++                              zend_hash_str_update(&(*message)->hdrs, "Content-Length", lenof("Content-Length"), &zcl);
+                               break;
+                       }
+                       case PHP_HTTP_MESSAGE_PARSER_STATE_DONE:
+                       {
                                char *ptr = buffer->data;
  
                                while (ptr - buffer->data < buffer->used && PHP_HTTP_IS_CTYPE(space, *ptr)) {
@@@ -631,24 -671,23 +658,25 @@@ PHP_MINIT_FUNCTION(http_message_parser
        memcpy(&php_http_message_parser_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
        php_http_message_parser_class_entry->create_object = php_http_message_parser_object_new;
        php_http_message_parser_object_handlers.clone_obj = NULL;
 -
 -      zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("CLEANUP"), PHP_HTTP_MESSAGE_PARSER_CLEANUP TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("DUMB_BODIES"), PHP_HTTP_MESSAGE_PARSER_DUMB_BODIES TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("EMPTY_REDIRECTS"), PHP_HTTP_MESSAGE_PARSER_EMPTY_REDIRECTS TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("GREEDY"), PHP_HTTP_MESSAGE_PARSER_GREEDY TSRMLS_CC);
 -
 -      zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("STATE_FAILURE"), PHP_HTTP_MESSAGE_PARSER_STATE_FAILURE TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("STATE_START"), PHP_HTTP_MESSAGE_PARSER_STATE_START TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("STATE_HEADER"), PHP_HTTP_MESSAGE_PARSER_STATE_HEADER TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("STATE_HEADER_DONE"), PHP_HTTP_MESSAGE_PARSER_STATE_HEADER_DONE TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("STATE_BODY"), PHP_HTTP_MESSAGE_PARSER_STATE_BODY TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("STATE_BODY_DUMB"), PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DUMB TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("STATE_BODY_LENGTH"), PHP_HTTP_MESSAGE_PARSER_STATE_BODY_LENGTH TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("STATE_BODY_CHUNKED"), PHP_HTTP_MESSAGE_PARSER_STATE_BODY_CHUNKED TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("STATE_BODY_DONE"), PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("STATE_UPDATE_CL"), PHP_HTTP_MESSAGE_PARSER_STATE_UPDATE_CL TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("STATE_DONE"), PHP_HTTP_MESSAGE_PARSER_STATE_DONE TSRMLS_CC);
 +      php_http_message_parser_object_handlers.free_obj = php_http_message_parser_object_free;
 +      php_http_message_parser_object_handlers.offset = XtOffsetOf(php_http_message_parser_object_t, zo);
 +
 +      zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("CLEANUP"), PHP_HTTP_MESSAGE_PARSER_CLEANUP);
 +      zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("DUMB_BODIES"), PHP_HTTP_MESSAGE_PARSER_DUMB_BODIES);
 +      zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("EMPTY_REDIRECTS"), PHP_HTTP_MESSAGE_PARSER_EMPTY_REDIRECTS);
 +      zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("GREEDY"), PHP_HTTP_MESSAGE_PARSER_GREEDY);
 +
 +      zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("STATE_FAILURE"), PHP_HTTP_MESSAGE_PARSER_STATE_FAILURE);
 +      zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("STATE_START"), PHP_HTTP_MESSAGE_PARSER_STATE_START);
 +      zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("STATE_HEADER"), PHP_HTTP_MESSAGE_PARSER_STATE_HEADER);
 +      zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("STATE_HEADER_DONE"), PHP_HTTP_MESSAGE_PARSER_STATE_HEADER_DONE);
 +      zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("STATE_BODY"), PHP_HTTP_MESSAGE_PARSER_STATE_BODY);
 +      zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("STATE_BODY_DUMB"), PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DUMB);
 +      zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("STATE_BODY_LENGTH"), PHP_HTTP_MESSAGE_PARSER_STATE_BODY_LENGTH);
 +      zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("STATE_BODY_CHUNKED"), PHP_HTTP_MESSAGE_PARSER_STATE_BODY_CHUNKED);
 +      zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("STATE_BODY_DONE"), PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE);
++      zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("STATE_UPDATE_CL"), PHP_HTTP_MESSAGE_PARSER_STATE_UPDATE_CL);
 +      zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("STATE_DONE"), PHP_HTTP_MESSAGE_PARSER_STATE_DONE);
  
        return SUCCESS;
  }
Simple merge
diff --cc php_http_misc.h
Simple merge
diff --cc php_http_url.h
@@@ -68,32 -68,14 +68,17 @@@ PHP_HTTP_API char *php_http_url_to_stri
  PHP_HTTP_API char *php_http_url_authority_to_string(const php_http_url_t *url, char **url_str, size_t *url_len);
  PHP_HTTP_API void php_http_url_free(php_http_url_t **url);
  
 -PHP_HTTP_API STATUS php_http_url_encode_hash(HashTable *hash, const char *pre_encoded_str, size_t pre_encoded_len, char **encoded_str, size_t *encoded_len TSRMLS_DC);
 -PHP_HTTP_API STATUS php_http_url_encode_hash_ex(HashTable *hash, php_http_buffer_t *qstr, const char *arg_sep_str, size_t arg_sep_len, const char *val_sep_str, size_t val_sep_len, const char *pre_encoded_str, size_t pre_encoded_len TSRMLS_DC);
 +PHP_HTTP_API ZEND_RESULT_CODE php_http_url_encode_hash(HashTable *hash, const char *pre_encoded_str, size_t pre_encoded_len, char **encoded_str, size_t *encoded_len);
 +PHP_HTTP_API ZEND_RESULT_CODE php_http_url_encode_hash_ex(HashTable *hash, php_http_buffer_t *qstr, const char *arg_sep_str, size_t arg_sep_len, const char *val_sep_str, size_t val_sep_len, const char *pre_encoded_str, size_t pre_encoded_len);
  
 -static inline void php_http_url_argsep(const char **str, size_t *len TSRMLS_DC)
 +static inline void php_http_url_argsep(const char **str, size_t *len)
  {
 -      php_http_ini_entry(ZEND_STRL("arg_separator.output"), str, len, 0 TSRMLS_CC);
 +      if (SUCCESS != php_http_ini_entry(ZEND_STRL("arg_separator.output"), str, len, 0) || !*len) {
 +              *str = PHP_HTTP_URL_ARGSEP;
 +              *len = lenof(PHP_HTTP_URL_ARGSEP);
 +      }
  }
  
- static inline php_url *php_http_url_to_php_url(php_http_url_t *url)
- {
-       php_url *purl = ecalloc(1, sizeof(*purl));
-       if (url->scheme)   purl->scheme   = estrdup(url->scheme);
-       if (url->pass)     purl->pass     = estrdup(url->pass);
-       if (url->user)     purl->user     = estrdup(url->user);
-       if (url->host)     purl->host     = estrdup(url->host);
-       if (url->path)     purl->path     = estrdup(url->path);
-       if (url->query)    purl->query    = estrdup(url->query);
-       if (url->fragment) purl->fragment = estrdup(url->fragment);
-       return purl;
- }
  static inline zend_bool php_http_url_is_empty(const php_http_url_t *url) {
        return !(url->scheme || url->pass || url->user || url->host || url->port ||     url->path || url->query || url->fragment);
  }
@@@ -63,4 -63,4 +63,4 @@@ object(http\Message)#%d (9) 
  }
  string(3) "OK
  "
--DONE
++DONE
index 0000000,373cb45..c1f58d9
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,19 +1,19 @@@
 -Extension Version => 2.%s
+ --TEST--
+ phpinfo
+ --SKIPIF--
+ <?php
+ include "skipif.inc";
+ ?>
+ --FILE--
+ <?php
+ echo "Test\n";
+ phpinfo(INFO_MODULES);
+ ?>
+ Done
+ --EXPECTF--
+ Test
+ %a
+ HTTP Support => enabled
++Extension Version => 3.%s
+ %a
+ Done