Merge branch 'v2.6.x'
authorMichael Wallner <mike@php.net>
Wed, 27 Jul 2016 13:24:35 +0000 (15:24 +0200)
committerMichael Wallner <mike@php.net>
Wed, 27 Jul 2016 13:24:35 +0000 (15:24 +0200)
1  2 
src/php_http_info.c
src/php_http_params.c
src/php_http_version.c
tests/gh-issue47.phpt

diff --combined src/php_http_info.c
index 6eef822707c1bfa6082672be7a91762e31ee094e,800821521154cb0e4966275a0a5e76da98ed56ae..9045cd40997d4b23fa5ba97aadd1d6ab5947be4e
@@@ -12,7 -12,7 +12,7 @@@
  
  #include "php_http_api.h"
  
 -php_http_info_t *php_http_info_init(php_http_info_t *i TSRMLS_DC)
 +php_http_info_t *php_http_info_init(php_http_info_t *i)
  {
        if (!i) {
                i = emalloc(sizeof(*i));
@@@ -30,11 -30,11 +30,11 @@@ void php_http_info_dtor(php_http_info_
                        PTR_SET(PHP_HTTP_INFO(i).request.method, NULL);
                        PTR_SET(PHP_HTTP_INFO(i).request.url, NULL);
                        break;
--              
++
                case PHP_HTTP_RESPONSE:
                        PTR_SET(PHP_HTTP_INFO(i).response.status, NULL);
                        break;
--              
++
                default:
                        break;
        }
@@@ -49,31 -49,43 +49,43 @@@ void php_http_info_free(php_http_info_
        }
  }
  
 -php_http_info_t *php_http_info_parse(php_http_info_t *info, const char *pre_header TSRMLS_DC)
 +php_http_info_t *php_http_info_parse(php_http_info_t *info, const char *pre_header)
  {
-       const char *end, *http;
+       const char *end, *http, *off;
        zend_bool free_info = !info;
--      
++
        /* sane parameter */
        if ((!pre_header) || (!*pre_header)) {
                return NULL;
        }
--      
++
        /* where's the end of the line */
        if (!(end = php_http_locate_eol(pre_header, NULL))) {
                end = pre_header + strlen(pre_header);
        }
--      
++
        /* there must be HTTP/1.x in the line */
        if (!(http = php_http_locate_str(pre_header, end - pre_header, "HTTP/", lenof("HTTP/")))) {
                return NULL;
        }
--      
 -      info = php_http_info_init(info TSRMLS_CC);
 -      if (!php_http_version_parse(&info->http.version, http TSRMLS_CC)) {
 +      info = php_http_info_init(info);
 +
-       /* and nothing than SPACE or NUL after HTTP/X.x */
-       if (!php_http_version_parse(&info->http.version, http)
-       ||      (http[lenof("HTTP/X.x")] && (!PHP_HTTP_IS_CTYPE(space, http[lenof("HTTP/X.x")])))) {
++      if (!php_http_version_parse(&info->http.version, http)) {
+               if (free_info) {
+                       php_http_info_free(&info);
+               }
+               return NULL;
+       }
+       /* clumsy fix for changed libcurl behaviour in 7.49.1, see https://github.com/curl/curl/issues/888 */
+       off = &http[lenof("HTTP/X")];
+       if (info->http.version.major < 2) {
+               off += 2;
+       }
+       /* and nothing than SPACE or NUL after HTTP/X(.x) */
+       if (*off && (!PHP_HTTP_IS_CTYPE(space, *off))) {
                if (free_info) {
                        php_http_info_free(&info);
                }
  
        /* is response */
        if (pre_header == http) {
-               const char *status = NULL, *code = http + sizeof("HTTP/X.x");
-               
+               const char *status = NULL, *code = off;
 -              
++
                info->type = PHP_HTTP_RESPONSE;
 -              while (' ' == *code) ++code;
 +              while (code < end && ' ' == *code) ++code;
-               if (code && end > code) {
+               if (end > code) {
                        /* rfc7230#3.1.2 The status-code element is a 3-digit integer code */
                        PHP_HTTP_INFO(info).response.code = 100*(*code++ - '0');
                        PHP_HTTP_INFO(info).response.code += 10*(*code++ - '0');
                } else {
                        PHP_HTTP_INFO(info).response.status = NULL;
                }
--              
++
                return info;
        }
--      
++
        /* is request */
-       else if (*(http - 1) == ' ' && (!http[lenof("HTTP/X.x")] || http[lenof("HTTP/X.x")] == '\r' || http[lenof("HTTP/X.x")] == '\n')) {
+       else if (*(http - 1) == ' ' && (!*off || *off == '\r' || *off == '\n')) {
                const char *url = strchr(pre_header, ' ');
--              
++
                info->type = PHP_HTTP_REQUEST;
                if (url && http > url) {
                        size_t url_len = url - pre_header;
                        if (http > url) {
                                /* CONNECT presents an authority only */
                                if (strcasecmp(PHP_HTTP_INFO(info).request.method, "CONNECT")) {
 -                                      PHP_HTTP_INFO(info).request.url = php_http_url_parse(url, http - url, ~0 TSRMLS_CC);
 +                                      PHP_HTTP_INFO(info).request.url = php_http_url_parse(url, http - url, ~0);
                                } else {
 -                                      PHP_HTTP_INFO(info).request.url = php_http_url_parse_authority(url, http - url, ~0 TSRMLS_CC);
 +                                      PHP_HTTP_INFO(info).request.url = php_http_url_parse_authority(url, http - url, ~0);
                                }
                                if (!PHP_HTTP_INFO(info).request.url) {
                                        PTR_SET(PHP_HTTP_INFO(info).request.method, NULL);
                        PHP_HTTP_INFO(info).request.method = NULL;
                        PHP_HTTP_INFO(info).request.url = NULL;
                }
--              
++
                return info;
        }
  
-       /* some darn header containing HTTP/X.x */
+       /* some darn header containing HTTP/X(.x) */
        else {
                if (free_info) {
                        php_http_info_free(&info);
diff --combined src/php_http_params.c
index c9feccb6c56705e63088ded27a3a0f716acb7969,fc75f0e96140151f52e918ec8136cbf9140c8304..8988f43c65c0cd33dcd9ff7220304c0c4065d7bd
@@@ -20,7 -20,7 +20,7 @@@ static php_http_params_opts_t def_opts 
        def_param_sep_ptr,
        def_arg_sep_ptr,
        def_val_sep_ptr,
 -      NULL,
 +      {{0}},
        PHP_HTTP_PARAMS_DEFAULT
  };
  
@@@ -41,101 -41,82 +41,101 @@@ typedef struct php_http_params_state 
        php_http_params_token_t arg;
        php_http_params_token_t val;
        struct {
 -              zval **param;
 -              zval **args;
 -              zval **val;
 +              zval *param;
 +              zval *args;
 +              zval *val;
        } current;
        unsigned quotes:1;
        unsigned escape:1;
        unsigned rfc5987:1;
  } php_http_params_state_t;
  
 -static inline void sanitize_escaped(zval *zv TSRMLS_DC)
 +static inline void sanitize_escaped(zval *zv)
  {
        if (Z_STRVAL_P(zv)[0] == '"' && Z_STRVAL_P(zv)[Z_STRLEN_P(zv) - 1] == '"') {
                size_t deq_len = Z_STRLEN_P(zv) - 2;
                char *deq = estrndup(Z_STRVAL_P(zv) + 1, deq_len);
  
                zval_dtor(zv);
 -              ZVAL_STRINGL(zv, deq, deq_len, 0);
 +              ZVAL_STR(zv, php_http_cs2zs(deq, deq_len));
        }
  
 -      php_stripcslashes(Z_STRVAL_P(zv), &Z_STRLEN_P(zv));
 +      php_stripcslashes(Z_STR_P(zv));
  }
  
 -static inline void quote_string(zval *zv, zend_bool force TSRMLS_DC)
 +static inline void quote_string(zend_string **zs, zend_bool force)
  {
 -      int len = Z_STRLEN_P(zv);
 +      int len = (*zs)->len;
  
 -      Z_STRVAL_P(zv) = php_addcslashes(Z_STRVAL_P(zv), Z_STRLEN_P(zv), &Z_STRLEN_P(zv), 1,
 -                      ZEND_STRL("\0..\37\173\\\"") TSRMLS_CC);
 +      *zs = php_addcslashes(*zs, 1, ZEND_STRL("\0..\37\173\\\""));
  
 -      if (force || len != Z_STRLEN_P(zv) || strpbrk(Z_STRVAL_P(zv), "()<>@,;:\"[]?={} ")) {
 -              zval tmp = *zv;
 -              int len = Z_STRLEN_P(zv) + 2;
 -              char *str = emalloc(len + 1);
 +      if (force || len != (*zs)->len || strpbrk((*zs)->val, "()<>@,;:\"[]?={} ")) {
 +              int len = (*zs)->len + 2;
  
 -              str[0] = '"';
 -              memcpy(&str[1], Z_STRVAL_P(zv), Z_STRLEN_P(zv));
 -              str[len-1] = '"';
 -              str[len] = '\0';
 +              *zs = zend_string_extend(*zs, len, 0);
  
 -              zval_dtor(&tmp);
 -              ZVAL_STRINGL(zv, str, len, 0);
 +              memmove(&(*zs)->val[1], (*zs)->val, (*zs)->len);
 +              (*zs)->val[0] = '"';
 +              (*zs)->val[len-1] = '"';
 +              (*zs)->val[len] = '\0';
 +
 +              zend_string_forget_hash_val(*zs);
        }
  }
  
 -static inline void prepare_escaped(zval *zv TSRMLS_DC)
 +/*    if (Z_TYPE_P(zv) == IS_STRING) {
 +              size_t len = Z_STRLEN_P(zv);
 +              zend_string *stripped = php_addcslashes(Z_STR_P(zv), 0,
 +                              ZEND_STRL("\0..\37\173\\\""));
 +
 +              if (len != stripped->len || strpbrk(stripped->val, "()<>@,;:\"[]?={} ")) {
 +                      size_t len = stripped->len + 2;
 +                      char *str = emalloc(len + 1);
 +
 +                      str[0] = '"';
 +                      memcpy(&str[1], stripped->val, stripped->len);
 +                      str[len-1] = '"';
 +                      str[len] = '\0';
 +
 +                      zval_dtor(zv);
 +                      zend_string_release(stripped);
 +                      ZVAL_STR(zv, php_http_cs2zs(str, len));
 +              } else {
 +                      zval_dtor(zv);
 +                      ZVAL_STR(zv, stripped);
 +              }
 +*/
 +
 +static inline void prepare_escaped(zval *zv)
  {
        if (Z_TYPE_P(zv) == IS_STRING) {
 -              quote_string(zv, 0 TSRMLS_CC);
 +              quote_string(&Z_STR_P(zv), 0);
        } else {
                zval_dtor(zv);
                ZVAL_EMPTY_STRING(zv);
        }
  }
  
 -static inline void sanitize_urlencoded(zval *zv TSRMLS_DC)
 +static inline void sanitize_urlencoded(zval *zv)
  {
        Z_STRLEN_P(zv) = php_raw_url_decode(Z_STRVAL_P(zv), Z_STRLEN_P(zv));
  }
  
 -static inline void prepare_urlencoded(zval *zv TSRMLS_DC)
 +static inline void prepare_urlencoded(zval *zv)
  {
 -      int len;
 -      char *str =     php_raw_url_encode(Z_STRVAL_P(zv), Z_STRLEN_P(zv), &len);
 +      zend_string *str = php_raw_url_encode(Z_STRVAL_P(zv), Z_STRLEN_P(zv));
  
        zval_dtor(zv);
 -      ZVAL_STRINGL(zv, str, len, 0);
 +      ZVAL_STR(zv, str);
  }
  
 -static void sanitize_dimension(zval *zv TSRMLS_DC)
 +static void sanitize_dimension(zval *zv)
  {
 -      zval *arr = NULL, *tmp = NULL, **cur = NULL;
 +      zval arr, tmp, *cur = NULL;
        char *var = NULL, *ptr = Z_STRVAL_P(zv), *end = Z_STRVAL_P(zv) + Z_STRLEN_P(zv);
        long level = 0;
  
 -      MAKE_STD_ZVAL(arr);
 -      array_init(arr);
 +      array_init(&arr);
        cur = &arr;
  
        while (ptr < end) {
                        case '[':
                                if (++level > PG(max_input_nesting_level)) {
                                        zval_ptr_dtor(&arr);
 -                                      php_error_docref(NULL TSRMLS_CC, E_WARNING, "Max input nesting level of %ld exceeded", (long) PG(max_input_nesting_level));
 +                                      php_error_docref(NULL, E_WARNING, "Max input nesting level of %ld exceeded", (long) PG(max_input_nesting_level));
                                        return;
                                }
                                if (ptr - var == 0) {
  
                        case ']':
  
 -                              MAKE_STD_ZVAL(tmp);
 -                              ZVAL_NULL(tmp);
 -                              convert_to_array(*cur);
 +                              ZVAL_NULL(&tmp);
 +                              convert_to_array(cur);
  
                                if (ptr - var) {
                                        char chr = *ptr;
                                        *ptr = '\0';
 -                                      zend_symtable_update(Z_ARRVAL_PP(cur), var, ptr - var + 1, (void *) &tmp, sizeof(zval *), (void *) &cur);
 +                                      cur = zend_symtable_str_update(Z_ARRVAL_P(cur), var, ptr - var, &tmp);
                                        *ptr = chr;
                                } else {
 -                                      zend_hash_next_index_insert(Z_ARRVAL_PP(cur), (void *) &tmp, sizeof(zval *), (void *) &cur);
 +                                      cur = zend_hash_next_index_insert(Z_ARRVAL_P(cur), &tmp);
                                }
  
                                var = NULL;
                ++ptr;
        }
  
 -      if (zend_hash_num_elements(Z_ARRVAL_P(arr))) {
 +      if (zend_hash_num_elements(Z_ARRVAL(arr))) {
                zval_dtor(zv);
 -#if PHP_VERSION_ID >= 50400
 -              ZVAL_COPY_VALUE(zv, arr);
 -#else
 -              zv->value = arr->value;
 -              Z_TYPE_P(zv) = Z_TYPE_P(arr);
 -#endif
 -              FREE_ZVAL(arr);
 +              ZVAL_COPY_VALUE(zv, &arr);
        } else {
                zval_ptr_dtor(&arr);
        }
  }
  
 -static inline void shift_key(php_http_buffer_t *buf, char *key_str, size_t key_len, const char *ass, size_t asl, unsigned flags TSRMLS_DC);
 -static inline void shift_val(php_http_buffer_t *buf, zval *zvalue, const char *vss, size_t vsl, unsigned flags TSRMLS_DC);
 +static inline void shift_key(php_http_buffer_t *buf, char *key_str, size_t key_len, const char *ass, size_t asl, unsigned flags);
 +static inline void shift_val(php_http_buffer_t *buf, zval *zvalue, const char *vss, size_t vsl, unsigned flags);
  
 -static void prepare_dimension(php_http_buffer_t *buf, php_http_buffer_t *keybuf, zval *zvalue, const char *pss, size_t psl, const char *vss, size_t vsl, unsigned flags TSRMLS_DC)
 +static void prepare_dimension(php_http_buffer_t *buf, php_http_buffer_t *keybuf, zval *zvalue, const char *pss, size_t psl, const char *vss, size_t vsl, unsigned flags)
  {
        HashTable *ht = HASH_OF(zvalue);
 -      HashPosition pos;
 -      php_http_array_hashkey_t key = php_http_array_hashkey_init(0);
 -      zval **val;
 +      php_http_arrkey_t key;
 +      zval *val;
        php_http_buffer_t prefix;
  
 -      if (!ht->nApplyCount++) {
 +      if (!ZEND_HASH_GET_APPLY_COUNT(ht)) {
 +              ZEND_HASH_INC_APPLY_COUNT(ht);
                php_http_buffer_init(&prefix);
                php_http_buffer_append(&prefix, keybuf->data, keybuf->used);
  
 -              FOREACH_HASH_KEYVAL(pos, ht, key, val) {
 -                      if (key.type == HASH_KEY_IS_STRING && !*key.str) {
 +              ZEND_HASH_FOREACH_KEY_VAL_IND(ht, key.h, key.key, val)
 +              {
 +                      if (key.key && !*key.key->val) {
                                /* only public properties */
                                continue;
                        }
  
                        php_http_buffer_appends(&prefix, "[");
 -                      if (key.type == HASH_KEY_IS_STRING) {
 -                              php_http_buffer_append(&prefix, key.str, key.len - 1);
 +                      if (key.key) {
 +                              php_http_buffer_append(&prefix, key.key->val, key.key->len);
                        } else {
 -                              php_http_buffer_appendf(&prefix, "%lu", key.num);
 +                              php_http_buffer_appendf(&prefix, "%lu", key.h);
                        }
                        php_http_buffer_appends(&prefix, "]");
  
 -                      if (Z_TYPE_PP(val) == IS_ARRAY || Z_TYPE_PP(val) == IS_OBJECT) {
 -                              prepare_dimension(buf, &prefix, *val, pss, psl, vss, vsl, flags TSRMLS_CC);
 +                      if (Z_TYPE_P(val) == IS_ARRAY || Z_TYPE_P(val) == IS_OBJECT) {
 +                              prepare_dimension(buf, &prefix, val, pss, psl, vss, vsl, flags);
                        } else {
 -                              zval *cpy = php_http_ztyp(IS_STRING, *val);
 +                              zend_string *cpy = zval_get_string(val);
 +                              zval tmp;
  
 -                              shift_key(buf, prefix.data, prefix.used, pss, psl, flags TSRMLS_CC);
 -                              shift_val(buf, cpy, vss, vsl, flags TSRMLS_CC);
 -                              zval_ptr_dtor(&cpy);
 +                              ZVAL_STR(&tmp, cpy);
 +                              shift_key(buf, prefix.data, prefix.used, pss, psl, flags);
 +                              shift_val(buf, &tmp, vss, vsl, flags);
 +                              zend_string_release(cpy);
                        }
  
                        php_http_buffer_cut(&prefix, keybuf->used, prefix.used - keybuf->used);
                }
 +              ZEND_HASH_FOREACH_END();
 +              ZEND_HASH_DEC_APPLY_COUNT(ht);
 +
                php_http_buffer_dtor(&prefix);
        }
 -      --ht->nApplyCount;
  }
  
 -static inline void sanitize_key(unsigned flags, char *str, size_t len, zval *zv, zend_bool *rfc5987 TSRMLS_DC)
 +static inline void sanitize_key(unsigned flags, const char *str, size_t len, zval *zv, zend_bool *rfc5987)
  {
        char *eos;
 +      zend_string *zs = zend_string_init(str, len, 0);
  
        zval_dtor(zv);
 -      php_trim(str, len, NULL, 0, zv, 3 TSRMLS_CC);
 +      ZVAL_STR(zv, php_trim(zs, NULL, 0, 3));
 +      zend_string_release(zs);
  
        if (flags & PHP_HTTP_PARAMS_ESCAPED) {
 -              sanitize_escaped(zv TSRMLS_CC);
 +              sanitize_escaped(zv);
        }
--      
++
        if (!Z_STRLEN_P(zv)) {
                return;
        }
        }
  
        if (flags & PHP_HTTP_PARAMS_URLENCODED) {
 -              sanitize_urlencoded(zv TSRMLS_CC);
 +              sanitize_urlencoded(zv);
        }
  
        if (flags & PHP_HTTP_PARAMS_DIMENSION) {
 -              sanitize_dimension(zv TSRMLS_CC);
 +              sanitize_dimension(zv);
        }
  }
  
 -static inline void sanitize_rfc5987(zval *zv, char **language, zend_bool *latin1 TSRMLS_DC)
 +static inline void sanitize_rfc5987(zval *zv, char **language, zend_bool *latin1)
  {
        char *ptr;
  
                /* remainder */
                ptr = estrdup(++ptr);
                zval_dtor(zv);
 -              ZVAL_STRING(zv, ptr, 0);
 +              ZVAL_STR(zv, php_http_cs2zs(ptr, strlen(ptr)));
        }
  }
  
 -static inline void sanitize_rfc5988(char *str, size_t len, zval *zv TSRMLS_DC)
 +static inline void sanitize_rfc5988(char *str, size_t len, zval *zv)
  {
 +      zend_string *zs = zend_string_init(str, len, 0);
 +
        zval_dtor(zv);
 -      php_trim(str, len, " ><", 3, zv, 3 TSRMLS_CC);
 +      ZVAL_STR(zv, php_trim(zs, " ><", 3, 3));
 +      zend_string_release(zs);
  }
  
 -static inline void prepare_rfc5988(zval *zv TSRMLS_DC)
 +static inline void prepare_rfc5988(zval *zv)
  {
        if (Z_TYPE_P(zv) != IS_STRING) {
                zval_dtor(zv);
@@@ -356,129 -334,130 +356,129 @@@ static void utf8encode(zval *zv
                }
        }
        zval_dtor(zv);
 -      ZVAL_STRINGL(zv, (char *) ptr, pos-1, 0);
 +      ZVAL_STR(zv, php_http_cs2zs((char *) ptr, pos-1));
  }
  
 -static inline void sanitize_value(unsigned flags, char *str, size_t len, zval *zv, zend_bool rfc5987 TSRMLS_DC)
 +static inline void sanitize_value(unsigned flags, const char *str, size_t len, zval *zv, zend_bool rfc5987)
  {
        char *language = NULL;
        zend_bool latin1 = 0;
 +      zend_string *zs = zend_string_init(str, len, 0);
  
        zval_dtor(zv);
 -      php_trim(str, len, NULL, 0, zv, 3 TSRMLS_CC);
 +      ZVAL_STR(zv, php_trim(zs, NULL, 0, 3));
 +      zend_string_release(zs);
  
        if (rfc5987) {
 -              sanitize_rfc5987(zv, &language, &latin1 TSRMLS_CC);
 +              sanitize_rfc5987(zv, &language, &latin1);
        }
  
        if (flags & PHP_HTTP_PARAMS_ESCAPED) {
 -              sanitize_escaped(zv TSRMLS_CC);
 +              sanitize_escaped(zv);
        }
  
        if ((flags & PHP_HTTP_PARAMS_URLENCODED) || (rfc5987 && language)) {
 -              sanitize_urlencoded(zv TSRMLS_CC);
 +              sanitize_urlencoded(zv);
        }
  
        if (rfc5987 && language) {
 -              zval *tmp;
 +              zval tmp;
  
                if (latin1) {
                        utf8encode(zv);
                }
  
 -              MAKE_STD_ZVAL(tmp);
 -              ZVAL_COPY_VALUE(tmp, zv);
 +              ZVAL_COPY_VALUE(&tmp, zv);
                array_init(zv);
 -              add_assoc_zval(zv, language, tmp);
 -              PTR_FREE(language);
 +              add_assoc_zval(zv, language, &tmp);
 +              efree(language);
        }
  }
  
 -static inline void prepare_key(unsigned flags, char *old_key, size_t old_len, char **new_key, size_t *new_len TSRMLS_DC)
 +static inline void prepare_key(unsigned flags, char *old_key, size_t old_len, char **new_key, size_t *new_len)
  {
        zval zv;
  
 -      INIT_PZVAL(&zv);
 -      ZVAL_STRINGL(&zv, old_key, old_len, 1);
 +      ZVAL_STRINGL(&zv, old_key, old_len);
  
        if (flags & PHP_HTTP_PARAMS_URLENCODED) {
 -              prepare_urlencoded(&zv TSRMLS_CC);
 +              prepare_urlencoded(&zv);
        }
  
        if (flags & PHP_HTTP_PARAMS_ESCAPED) {
                if (flags & PHP_HTTP_PARAMS_RFC5988) {
 -                      prepare_rfc5988(&zv TSRMLS_CC);
 +                      prepare_rfc5988(&zv);
                } else {
 -                      prepare_escaped(&zv TSRMLS_CC);
 +                      prepare_escaped(&zv);
                }
        }
  
 -      *new_key = Z_STRVAL(zv);
 +      *new_key = estrndup(Z_STRVAL(zv), Z_STRLEN(zv));
        *new_len = Z_STRLEN(zv);
 +      zval_ptr_dtor(&zv);
  }
  
 -static inline void prepare_value(unsigned flags, zval *zv TSRMLS_DC)
 +static inline void prepare_value(unsigned flags, zval *zv)
  {
        if (flags & PHP_HTTP_PARAMS_URLENCODED) {
 -              prepare_urlencoded(zv TSRMLS_CC);
 +              prepare_urlencoded(zv);
        }
  
        if (flags & PHP_HTTP_PARAMS_ESCAPED) {
 -              prepare_escaped(zv TSRMLS_CC);
 +              prepare_escaped(zv);
        }
  }
  
 -static void merge_param(HashTable *params, zval *zdata, zval ***current_param, zval ***current_args TSRMLS_DC)
 +static void merge_param(HashTable *params, zval *zdata, zval **current_param, zval **current_args)
  {
 -      zval **ptr, **zdata_ptr;
 -      php_http_array_hashkey_t hkey = php_http_array_hashkey_init(0);
 +      zval *ptr, *zdata_ptr;
 +      php_http_arrkey_t hkey = {0};
  
  #if 0
        {
                zval tmp;
                INIT_PZVAL_ARRAY(&tmp, params);
                fprintf(stderr, "params = ");
 -              zend_print_zval_r(&tmp, 1 TSRMLS_CC);
 +              zend_print_zval_r(&tmp, 1);
                fprintf(stderr, "\n");
        }
  #endif
  
 -      hkey.type = zend_hash_get_current_key_ex(Z_ARRVAL_P(zdata), &hkey.str, &hkey.len, &hkey.num, hkey.dup, NULL);
 +      zend_hash_get_current_key(Z_ARRVAL_P(zdata), &hkey.key, &hkey.h);
  
 -      if ((hkey.type == HASH_KEY_IS_STRING && !zend_hash_exists(params, hkey.str, hkey.len))
 -      ||      (hkey.type == HASH_KEY_IS_LONG && !zend_hash_index_exists(params, hkey.num))
 +      if ((hkey.key && !zend_hash_exists(params, hkey.key))
 +      ||      (!hkey.key && !zend_hash_index_exists(params, hkey.h))
        ) {
 -              zval *tmp, *arg, **args;
 +              zval tmp, arg, *args;
  
                /* create the entry if it doesn't exist */
 -              zend_hash_get_current_data(Z_ARRVAL_P(zdata), (void *) &ptr);
 -              Z_ADDREF_PP(ptr);
 -              MAKE_STD_ZVAL(tmp);
 -              array_init(tmp);
 -              add_assoc_zval_ex(tmp, ZEND_STRS("value"), *ptr);
 -
 -              MAKE_STD_ZVAL(arg);
 -              array_init(arg);
 -              zend_hash_update(Z_ARRVAL_P(tmp), "arguments", sizeof("arguments"), (void *) &arg, sizeof(zval *), (void *) &args);
 +              ptr = zend_hash_get_current_data(Z_ARRVAL_P(zdata));
 +              Z_TRY_ADDREF_P(ptr);
 +              array_init(&tmp);
 +              add_assoc_zval_ex(&tmp, ZEND_STRL("value"), ptr);
 +
 +              array_init(&arg);
 +              args = zend_hash_str_update(Z_ARRVAL(tmp), "arguments", lenof("arguments"), &arg);
                *current_args = args;
  
 -              if (hkey.type == HASH_KEY_IS_STRING) {
 -                      zend_hash_update(params, hkey.str, hkey.len, (void *) &tmp, sizeof(zval *), (void *) &ptr);
 +              if (hkey.key) {
 +                      ptr = zend_hash_update(params, hkey.key, &tmp);
                } else {
 -                      zend_hash_index_update(params, hkey.num, (void *) &tmp, sizeof(zval *), (void *) &ptr);
 +                      ptr = zend_hash_index_update(params, hkey.h, &tmp);
                }
        } else {
                /* merge */
 -              if (hkey.type == HASH_KEY_IS_STRING) {
 -                      zend_hash_find(params, hkey.str, hkey.len, (void *) &ptr);
 +              if (hkey.key) {
 +                      ptr = zend_hash_find(params, hkey.key);
                } else {
 -                      zend_hash_index_find(params, hkey.num, (void *) &ptr);
 +                      ptr = zend_hash_index_find(params, hkey.h);
                }
  
 -              zdata_ptr = &zdata;
 +              zdata_ptr = zdata;
  
 -              if (Z_TYPE_PP(ptr) == IS_ARRAY
 -              &&      SUCCESS == zend_hash_find(Z_ARRVAL_PP(ptr), "value", sizeof("value"), (void *) &ptr)
 -              &&      SUCCESS == zend_hash_get_current_data(Z_ARRVAL_PP(zdata_ptr), (void *) &zdata_ptr)
 +              if (Z_TYPE_P(ptr) == IS_ARRAY
 +              &&      (ptr = zend_hash_str_find(Z_ARRVAL_P(ptr), "value", lenof("value")))
 +              &&      (zdata_ptr = zend_hash_get_current_data(Z_ARRVAL_P(zdata_ptr)))
                ) {
                        /*
                         * params = [arr => [value => [0 => 1]]]
                         * zdata  = [arr => [0 => NULL]]
                         *                  ^- zdata_ptr
                         */
 -                      zval **test_ptr;
 +                      zval *test_ptr;
  
 -                      while (Z_TYPE_PP(zdata_ptr) == IS_ARRAY
 -                      &&      SUCCESS == zend_hash_get_current_data(Z_ARRVAL_PP(zdata_ptr), (void *) &test_ptr)
 -                      ) {
 -                              if (Z_TYPE_PP(test_ptr) == IS_ARRAY) {
 +                      while (Z_TYPE_P(zdata_ptr) == IS_ARRAY && (test_ptr = zend_hash_get_current_data(Z_ARRVAL_P(zdata_ptr)))) {
 +                              if (Z_TYPE_P(test_ptr) == IS_ARRAY) {
 +                                      zval *tmp_ptr = ptr;
  
                                        /* now find key in ptr */
 -                                      if (HASH_KEY_IS_STRING == zend_hash_get_current_key_ex(Z_ARRVAL_PP(zdata_ptr), &hkey.str, &hkey.len, &hkey.num, hkey.dup, NULL)) {
 -                                              if (SUCCESS == zend_hash_find(Z_ARRVAL_PP(ptr), hkey.str, hkey.len, (void *) &ptr)) {
 +                                      if (HASH_KEY_IS_STRING == zend_hash_get_current_key(Z_ARRVAL_P(zdata_ptr), &hkey.key, &hkey.h)) {
 +                                              if ((ptr = zend_hash_find(Z_ARRVAL_P(ptr), hkey.key))) {
                                                        zdata_ptr = test_ptr;
                                                } else {
 -                                                      Z_ADDREF_PP(test_ptr);
 -                                                      zend_hash_update(Z_ARRVAL_PP(ptr), hkey.str, hkey.len, (void *) test_ptr, sizeof(zval *), (void *) &ptr);
 +                                                      ptr = tmp_ptr;
 +                                                      Z_TRY_ADDREF_P(test_ptr);
 +                                                      ptr = zend_hash_update(Z_ARRVAL_P(ptr), hkey.key, test_ptr);
                                                        break;
                                                }
                                        } else {
 -                                              if (SUCCESS == zend_hash_index_find(Z_ARRVAL_PP(ptr), hkey.num, (void *) &ptr)) {
 +                                              if ((ptr = zend_hash_index_find(Z_ARRVAL_P(ptr), hkey.h))) {
                                                        zdata_ptr = test_ptr;
 -                                              } else if (hkey.num) {
 -                                                      Z_ADDREF_PP(test_ptr);
 -                                                      zend_hash_index_update(Z_ARRVAL_PP(ptr), hkey.num, (void *) test_ptr, sizeof(zval *), (void *) &ptr);
 +                                              } else if (hkey.h) {
 +                                                      ptr = tmp_ptr;
 +                                                      Z_TRY_ADDREF_P(test_ptr);
 +                                                      ptr = zend_hash_index_update(Z_ARRVAL_P(ptr), hkey.h, test_ptr);
                                                        break;
                                                } else {
 -                                                      Z_ADDREF_PP(test_ptr);
 -                                                      zend_hash_next_index_insert(Z_ARRVAL_PP(ptr), (void *) test_ptr, sizeof(zval *), (void *) &ptr);
 +                                                      ptr = tmp_ptr;
 +                                                      Z_TRY_ADDREF_P(test_ptr);
 +                                                      ptr = zend_hash_next_index_insert(Z_ARRVAL_P(ptr), test_ptr);
                                                        break;
                                                }
                                        }
                                } else {
                                        /* this is the leaf */
 -                                      Z_ADDREF_PP(test_ptr);
 -                                      if (Z_TYPE_PP(ptr) != IS_ARRAY) {
 -                                              zval_dtor(*ptr);
 -                                              array_init(*ptr);
 +                                      Z_TRY_ADDREF_P(test_ptr);
 +                                      if (Z_TYPE_P(ptr) != IS_ARRAY) {
 +                                              zval_dtor(ptr);
 +                                              array_init(ptr);
                                        }
 -                                      if (HASH_KEY_IS_STRING == zend_hash_get_current_key_ex(Z_ARRVAL_PP(zdata_ptr), &hkey.str, &hkey.len, &hkey.num, hkey.dup, NULL)) {
 -                                              zend_hash_update(Z_ARRVAL_PP(ptr), hkey.str, hkey.len, (void *) test_ptr, sizeof(zval *), (void *) &ptr);
 -                                      } else if (hkey.num) {
 -                                              zend_hash_index_update(Z_ARRVAL_PP(ptr), hkey.num, (void *) test_ptr, sizeof(zval *), (void *) &ptr);
 +                                      if (HASH_KEY_IS_STRING == zend_hash_get_current_key(Z_ARRVAL_P(zdata_ptr), &hkey.key, &hkey.h)) {
 +                                              ptr = zend_hash_update(Z_ARRVAL_P(ptr), hkey.key, test_ptr);
 +                                      } else if (hkey.h) {
 +                                              ptr = zend_hash_index_update(Z_ARRVAL_P(ptr), hkey.h, test_ptr);
                                        } else {
 -                                              zend_hash_next_index_insert(Z_ARRVAL_PP(ptr), (void *) test_ptr, sizeof(zval *), (void *) &ptr);
 +                                              ptr = zend_hash_next_index_insert(Z_ARRVAL_P(ptr), test_ptr);
                                        }
                                        break;
                                }
        }
  
        /* bubble up */
 -      while (Z_TYPE_PP(ptr) == IS_ARRAY && SUCCESS == zend_hash_get_current_data(Z_ARRVAL_PP(ptr), (void *) &ptr));
 +      while (Z_TYPE_P(ptr) == IS_ARRAY) {
 +              zval *tmp = zend_hash_get_current_data(Z_ARRVAL_P(ptr));
 +
 +              if (tmp) {
 +                      ptr = tmp;
 +              } else {
 +                      break;
 +              }
 +      }
        *current_param = ptr;
  }
  
 -static void push_param(HashTable *params, php_http_params_state_t *state, const php_http_params_opts_t *opts TSRMLS_DC)
 +static void push_param(HashTable *params, php_http_params_state_t *state, const php_http_params_opts_t *opts)
  {
        if (state->val.str) {
-               if (0 < (state->val.len = state->input.str - state->val.str)) {
+               if (!state->current.val) {
+                       return;
+               } else 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) {
                if (0 < (state->arg.len = state->input.str - state->arg.str)) {
 -                      zval *val, key;
 +                      zval val, key;
                        zend_bool rfc5987 = 0;
  
 -                      INIT_PZVAL(&key);
                        ZVAL_NULL(&key);
 -                      sanitize_key(opts->flags, state->arg.str, state->arg.len, &key, &rfc5987 TSRMLS_CC);
 +                      sanitize_key(opts->flags, state->arg.str, state->arg.len, &key, &rfc5987);
                        state->rfc5987 = rfc5987;
                        if (Z_TYPE(key) == IS_STRING && Z_STRLEN(key)) {
 -                              MAKE_STD_ZVAL(val);
 -                              ZVAL_TRUE(val);
 +                              ZVAL_TRUE(&val);
  
                                if (rfc5987) {
 -                                      zval **rfc;
 +                                      zval *rfc;
  
 -                                      if (SUCCESS == zend_hash_find(Z_ARRVAL_PP(state->current.args), ZEND_STRS("*rfc5987*"), (void *) &rfc)) {
 -                                              zend_symtable_update(Z_ARRVAL_PP(rfc), Z_STRVAL(key), Z_STRLEN(key) + 1, (void *) &val, sizeof(zval *), (void *) &state->current.val);
 +                                      if ((rfc = zend_hash_str_find(Z_ARRVAL_P(state->current.args), ZEND_STRL("*rfc5987*")))) {
 +                                              state->current.val = zend_symtable_str_update(Z_ARRVAL_P(rfc), Z_STRVAL(key), Z_STRLEN(key), &val);
                                        } else {
 -                                              zval *tmp;
 +                                              zval tmp;
  
 -                                              MAKE_STD_ZVAL(tmp);
 -                                              array_init_size(tmp, 1);
 -                                              zend_symtable_update(Z_ARRVAL_P(tmp), Z_STRVAL(key), Z_STRLEN(key) + 1, (void *) &val, sizeof(zval *), (void *) &state->current.val);
 -                                              zend_symtable_update(Z_ARRVAL_PP(state->current.args), ZEND_STRS("*rfc5987*"), (void *) &tmp, sizeof(zval *), NULL);
 +                                              array_init_size(&tmp, 1);
 +                                              state->current.val = zend_symtable_str_update(Z_ARRVAL(tmp), Z_STRVAL(key), Z_STRLEN(key), &val);
 +                                              zend_symtable_str_update(Z_ARRVAL_P(state->current.args), ZEND_STRL("*rfc5987*"), &tmp);
                                        }
                                } else {
 -                                      zend_symtable_update(Z_ARRVAL_PP(state->current.args), Z_STRVAL(key), Z_STRLEN(key) + 1, (void *) &val, sizeof(zval *), (void *) &state->current.val);
 +                                      state->current.val = zend_symtable_str_update(Z_ARRVAL_P(state->current.args), Z_STRVAL(key), Z_STRLEN(key), &val);
                                }
                        }
                        zval_dtor(&key);
                }
        } else if (state->param.str) {
                if (0 < (state->param.len = state->input.str - state->param.str)) {
 -                      zval *prm, *arg, *val, *key;
 +                      zval prm, arg, val, key;
                        zend_bool rfc5987 = 0;
  
 -                      MAKE_STD_ZVAL(key);
 -                      ZVAL_NULL(key);
 +                      ZVAL_NULL(&key);
                        if (opts->flags & PHP_HTTP_PARAMS_RFC5988) {
 -                              sanitize_rfc5988(state->param.str, state->param.len, key TSRMLS_CC);
 +                              sanitize_rfc5988(state->param.str, state->param.len, &key);
                        } else {
 -                              sanitize_key(opts->flags, state->param.str, state->param.len, key, &rfc5987 TSRMLS_CC);
 +                              sanitize_key(opts->flags, state->param.str, state->param.len, &key, &rfc5987);
                                state->rfc5987 = rfc5987;
                        }
 -                      if (Z_TYPE_P(key) != IS_STRING) {
 -                              merge_param(params, key, &state->current.val, &state->current.args TSRMLS_CC);
 -                      } else if (Z_STRLEN_P(key)) {
 -                              MAKE_STD_ZVAL(prm);
 -                              array_init_size(prm, 2);
 -
 -                              MAKE_STD_ZVAL(val);
 -                              if (opts->defval) {
 -                                      ZVAL_COPY_VALUE(val, opts->defval);
 -                                      zval_copy_ctor(val);
 +                      if (Z_TYPE(key) == IS_ARRAY) {
 +                              merge_param(params, &key, &state->current.val, &state->current.args);
 +                      } else if (Z_TYPE(key) == IS_STRING && Z_STRLEN(key)) {
 +                              // FIXME: array_init_size(&prm, 2);
 +                              array_init(&prm);
 +
 +                              if (!Z_ISUNDEF(opts->defval)) {
 +                                      ZVAL_COPY_VALUE(&val, &opts->defval);
 +                                      zval_copy_ctor(&val);
                                } else {
 -                                      ZVAL_TRUE(val);
 +                                      ZVAL_TRUE(&val);
                                }
                                if (rfc5987 && (opts->flags & PHP_HTTP_PARAMS_RFC5987)) {
 -                                      zend_hash_update(Z_ARRVAL_P(prm), "*rfc5987*", sizeof("*rfc5987*"), (void *) &val, sizeof(zval *), (void *) &state->current.val);
 +                                      state->current.val = zend_hash_str_update(Z_ARRVAL(prm), "*rfc5987*", lenof("*rfc5987*"), &val);
                                } else {
 -                                      zend_hash_update(Z_ARRVAL_P(prm), "value", sizeof("value"), (void *) &val, sizeof(zval *), (void *) &state->current.val);
 +                                      state->current.val = zend_hash_str_update(Z_ARRVAL(prm), "value", lenof("value"), &val);
                                }
 -
 -                              MAKE_STD_ZVAL(arg);
 -                              array_init_size(arg, 3);
 -                              zend_hash_update(Z_ARRVAL_P(prm), "arguments", sizeof("arguments"), (void *) &arg, sizeof(zval *), (void *) &state->current.args);
 -
 -                              zend_symtable_update(params, Z_STRVAL_P(key), Z_STRLEN_P(key) + 1, (void *) &prm, sizeof(zval *), (void *) &state->current.param);
 +                              // FIXME: array_init_size(&arg, 3);
 +                              array_init(&arg);
 +                              state->current.args = zend_hash_str_update(Z_ARRVAL(prm), "arguments", lenof("arguments"), &arg);
 +                              state->current.param = zend_symtable_str_update(params, Z_STRVAL(key), Z_STRLEN(key), &prm);
                        }
                        zval_ptr_dtor(&key);
                }
@@@ -639,7 -617,7 +641,7 @@@ static size_t check_sep(php_http_params
        if (state->quotes || state->escape) {
                return 0;
        }
--      
++
        if (sep) while (*sep) {
                if (check_str(state->input.str, state->input.len, (*sep)->str, (*sep)->len)) {
                        return (*sep)->len;
        return 0;
  }
  
 -static void skip_sep(size_t skip, php_http_params_state_t *state, php_http_params_token_t **param, php_http_params_token_t **arg, php_http_params_token_t **val TSRMLS_DC)
 +static void skip_sep(size_t skip, php_http_params_state_t *state, php_http_params_token_t **param, php_http_params_token_t **arg, php_http_params_token_t **val)
  {
        size_t sep_len;
  
        }
  }
  
 -HashTable *php_http_params_parse(HashTable *params, const php_http_params_opts_t *opts TSRMLS_DC)
 +HashTable *php_http_params_parse(HashTable *params, const php_http_params_opts_t *opts)
  {
        php_http_params_state_t state = {{NULL,0}, {NULL,0}, {NULL,0}, {NULL,0}, {NULL,NULL,NULL}, 0, 0};
  
                } else {
                        state.escape = (*state.input.str == '\\');
                }
--              
++
                if (!state.param.str) {
                        /* initialize */
 -                      skip_sep(0, &state, opts->param, opts->arg, opts->val TSRMLS_CC);
 +                      skip_sep(0, &state, opts->param, opts->arg, opts->val);
                        state.param.str = state.input.str;
                } else {
                        size_t sep_len;
                        /* are we at a param separator? */
                        if (0 < (sep_len = check_sep(&state, opts->param))) {
 -                              push_param(params, &state, opts TSRMLS_CC);
 +                              push_param(params, &state, opts);
  
 -                              skip_sep(sep_len, &state, opts->param, opts->arg, opts->val TSRMLS_CC);
 +                              skip_sep(sep_len, &state, opts->param, opts->arg, opts->val);
  
                                /* start off with a new param */
                                state.param.str = state.input.str;
                        } else
                        /* are we at an arg separator? */
                        if (0 < (sep_len = check_sep(&state, opts->arg))) {
 -                              push_param(params, &state, opts TSRMLS_CC);
 +                              push_param(params, &state, opts);
  
 -                              skip_sep(sep_len, &state, NULL, opts->arg, opts->val TSRMLS_CC);
 +                              skip_sep(sep_len, &state, NULL, opts->arg, opts->val);
  
                                /* continue with a new arg */
                                state.arg.str = state.input.str;
                        if (0 < (sep_len = check_sep(&state, opts->val))) {
                                /* only handle separator if we're not already reading in a val */
                                if (!state.val.str) {
 -                                      push_param(params, &state, opts TSRMLS_CC);
 +                                      push_param(params, &state, opts);
  
 -                                      skip_sep(sep_len, &state, NULL, NULL, opts->val TSRMLS_CC);
 +                                      skip_sep(sep_len, &state, NULL, NULL, opts->val);
  
                                        state.val.str = state.input.str;
                                        state.val.len = 0;
                                }
                        }
                }
--              
++
                if (state.input.len) {
                        ++state.input.str;
                        --state.input.len;
                }
        }
        /* finalize */
 -      push_param(params, &state, opts TSRMLS_CC);
 +      push_param(params, &state, opts);
  
        return params;
  }
  
 -static inline void shift_key(php_http_buffer_t *buf, char *key_str, size_t key_len, const char *ass, size_t asl, unsigned flags TSRMLS_DC)
 +static inline void shift_key(php_http_buffer_t *buf, char *key_str, size_t key_len, const char *ass, size_t asl, unsigned flags)
  {
        char *str;
        size_t len;
                php_http_buffer_append(buf, ass, asl);
        }
  
 -      prepare_key(flags, key_str, key_len, &str, &len TSRMLS_CC);
 +      prepare_key(flags, key_str, key_len, &str, &len);
        php_http_buffer_append(buf, str, len);
        efree(str);
  }
  
 -static inline void shift_rfc5987(php_http_buffer_t *buf, zval *zvalue, const char *vss, size_t vsl, unsigned flags TSRMLS_DC)
 +static inline void shift_rfc5987(php_http_buffer_t *buf, zval *zvalue, const char *vss, size_t vsl, unsigned flags)
  {
        HashTable *ht = HASH_OF(zvalue);
 -      zval **zdata, *tmp;
 -      php_http_array_hashkey_t key = php_http_array_hashkey_init(0);
 +      zval *zdata, tmp;
 +      zend_string *zs;
 +      php_http_arrkey_t key = {0};
  
 -      if (SUCCESS == zend_hash_get_current_data(ht, (void *) &zdata)
 -      &&      HASH_KEY_NON_EXISTENT != (key.type = zend_hash_get_current_key_ex(ht, &key.str, &key.len, &key.num, key.dup, NULL))
 +      if ((zdata = zend_hash_get_current_data(ht))
 +      &&      HASH_KEY_NON_EXISTENT != zend_hash_get_current_key(ht, &key.key, &key.h)
        ) {
 -              php_http_array_hashkey_stringify(&key);
 +              php_http_arrkey_stringify(&key, NULL);
                php_http_buffer_appendf(buf, "*%.*sutf-8'%.*s'",
                                (int) (vsl > INT_MAX ? INT_MAX : vsl), vss,
 -                              (int) (key.len > INT_MAX ? INT_MAX : key.len), key.str);
 -              php_http_array_hashkey_stringfree(&key);
 +                              (int) (key.key->len > INT_MAX ? INT_MAX : key.key->len), key.key->val);
 +              php_http_arrkey_dtor(&key);
  
 -              tmp = php_http_zsep(1, IS_STRING, *zdata);
 -              prepare_value(flags | PHP_HTTP_PARAMS_URLENCODED, tmp TSRMLS_CC);
 -              php_http_buffer_append(buf, Z_STRVAL_P(tmp), Z_STRLEN_P(tmp));
 +              if (Z_TYPE_P(zdata) == IS_INDIRECT) {
 +                      zdata = Z_INDIRECT_P(zdata);
 +              }
 +              zs = zval_get_string(zdata);
 +              ZVAL_STR(&tmp, zs);
 +              prepare_value(flags | PHP_HTTP_PARAMS_URLENCODED, &tmp);
 +              php_http_buffer_append(buf, Z_STRVAL(tmp), Z_STRLEN(tmp));
                zval_ptr_dtor(&tmp);
        }
  }
  
 -static inline void shift_rfc5988(php_http_buffer_t *buf, char *key_str, size_t key_len, const char *ass, size_t asl, unsigned flags TSRMLS_DC)
 +static inline void shift_rfc5988(php_http_buffer_t *buf, char *key_str, size_t key_len, const char *ass, size_t asl, unsigned flags)
  {
        char *str;
        size_t len;
                php_http_buffer_append(buf, ass, asl);
        }
  
 -      prepare_key(flags, key_str, key_len, &str, &len TSRMLS_CC);
 +      prepare_key(flags, key_str, key_len, &str, &len);
        php_http_buffer_appends(buf, "<");
        php_http_buffer_append(buf, str, len);
        php_http_buffer_appends(buf, ">");
        efree(str);
  }
  
 -static inline void shift_rfc5988_val(php_http_buffer_t *buf, zval *zv, const char *vss, size_t vsl, unsigned flags TSRMLS_DC)
 +static inline void shift_rfc5988_val(php_http_buffer_t *buf, zval *zv, const char *vss, size_t vsl, unsigned flags)
  {
 -      zval *tmp = php_http_zsep(1, IS_STRING, zv);
 +      zend_string *zs = zval_get_string(zv);
  
 -      quote_string(tmp, 1 TSRMLS_CC);
 +      quote_string(&zs, 1);
        php_http_buffer_append(buf, vss, vsl);
 -      php_http_buffer_append(buf, Z_STRVAL_P(tmp), Z_STRLEN_P(tmp));
 +      php_http_buffer_append(buf, zs->val, zs->len);
  
 -      zval_ptr_dtor(&tmp);
 +      zend_string_release(zs);
  }
  
 -static inline void shift_val(php_http_buffer_t *buf, zval *zvalue, const char *vss, size_t vsl, unsigned flags TSRMLS_DC)
 +static inline void shift_val(php_http_buffer_t *buf, zval *zvalue, const char *vss, size_t vsl, unsigned flags)
  {
 -      if (Z_TYPE_P(zvalue) != IS_BOOL) {
 -              zval *tmp = php_http_zsep(1, IS_STRING, zvalue);
 +      zval tmp;
 +      zend_string *zs;
  
 -              prepare_value(flags, tmp TSRMLS_CC);
 -              php_http_buffer_append(buf, vss, vsl);
 -              php_http_buffer_append(buf, Z_STRVAL_P(tmp), Z_STRLEN_P(tmp));
 +      switch (Z_TYPE_P(zvalue)) {
 +      case IS_TRUE:
 +              break;
  
 -              zval_ptr_dtor(&tmp);
 -      } else if (!Z_BVAL_P(zvalue)) {
 +      case IS_FALSE:
                php_http_buffer_append(buf, vss, vsl);
                php_http_buffer_appends(buf, "0");
 +              break;
 +
 +      default:
 +              zs = zval_get_string(zvalue);
 +
 +              ZVAL_STR(&tmp, zs);
 +              prepare_value(flags, &tmp);
 +              php_http_buffer_append(buf, vss, vsl);
 +              php_http_buffer_append(buf, Z_STRVAL(tmp), Z_STRLEN(tmp));
 +
 +              zval_ptr_dtor(&tmp);
 +              break;
        }
  }
  
 -static void shift_arg(php_http_buffer_t *buf, char *key_str, size_t key_len, zval *zvalue, const char *ass, size_t asl, const char *vss, size_t vsl, unsigned flags TSRMLS_DC)
 +static void shift_arg(php_http_buffer_t *buf, char *key_str, size_t key_len, zval *zvalue, const char *ass, size_t asl, const char *vss, size_t vsl, unsigned flags)
  {
        if (Z_TYPE_P(zvalue) == IS_ARRAY || Z_TYPE_P(zvalue) == IS_OBJECT) {
 -              HashPosition pos;
 -              php_http_array_hashkey_t key = php_http_array_hashkey_init(0);
 -              zval **val;
 +              php_http_arrkey_t key;
 +              HashTable *ht = HASH_OF(zvalue);
 +              zval *val;
                zend_bool rfc5987 = !strcmp(key_str, "*rfc5987*");
  
                if (!rfc5987) {
 -                      shift_key(buf, key_str, key_len, ass, asl, flags TSRMLS_CC);
 +                      shift_key(buf, key_str, key_len, ass, asl, flags);
                }
 -              FOREACH_KEYVAL(pos, zvalue, key, val) {
 +              ZEND_HASH_FOREACH_KEY_VAL_IND(ht, key.h, key.key, val)
 +              {
                        /* did you mean recursion? */
 -                      php_http_array_hashkey_stringify(&key);
 -                      if (rfc5987 && (Z_TYPE_PP(val) == IS_ARRAY || Z_TYPE_PP(val) == IS_OBJECT)) {
 -                              shift_key(buf, key.str, key.len-1, ass, asl, flags TSRMLS_CC);
 -                              shift_rfc5987(buf, *val, vss, vsl, flags TSRMLS_CC);
 +                      php_http_arrkey_stringify(&key, NULL);
 +                      if (rfc5987 && (Z_TYPE_P(val) == IS_ARRAY || Z_TYPE_P(val) == IS_OBJECT)) {
 +                              shift_key(buf, key.key->val, key.key->len, ass, asl, flags);
 +                              shift_rfc5987(buf, val, vss, vsl, flags);
                        } else {
 -                              shift_arg(buf, key.str, key.len-1, *val, ass, asl, vss, vsl, flags TSRMLS_CC);
 +                              shift_arg(buf, key.key->val, key.key->len, val, ass, asl, vss, vsl, flags);
                        }
 -                      php_http_array_hashkey_stringfree(&key);
 +                      php_http_arrkey_dtor(&key);
                }
 +              ZEND_HASH_FOREACH_END();
        } else {
 -              shift_key(buf, key_str, key_len, ass, asl, flags TSRMLS_CC);
 +              shift_key(buf, key_str, key_len, ass, asl, flags);
  
                if (flags & PHP_HTTP_PARAMS_RFC5988) {
                        switch (key_len) {
                        case lenof("anchor"):
                                /* some args must be quoted */
                                if (0 <= php_http_select_str(key_str, 3, "rel", "title", "anchor")) {
 -                                      shift_rfc5988_val(buf, zvalue, vss, vsl, flags TSRMLS_CC);
 +                                      shift_rfc5988_val(buf, zvalue, vss, vsl, flags);
                                        return;
                                }
                                break;
                        }
                }
  
 -              shift_val(buf, zvalue, vss, vsl, flags TSRMLS_CC);
 +              shift_val(buf, zvalue, vss, vsl, flags);
        }
  }
  
 -static void shift_param(php_http_buffer_t *buf, char *key_str, size_t key_len, zval *zvalue, const char *pss, size_t psl, const char *ass, size_t asl, const char *vss, size_t vsl, unsigned flags, zend_bool rfc5987 TSRMLS_DC)
 +static void shift_param(php_http_buffer_t *buf, char *key_str, size_t key_len, zval *zvalue, const char *pss, size_t psl, const char *ass, size_t asl, const char *vss, size_t vsl, unsigned flags, zend_bool rfc5987)
  {
        if (Z_TYPE_P(zvalue) == IS_ARRAY || Z_TYPE_P(zvalue) == IS_OBJECT) {
                /* treat as arguments, unless we care for dimensions or rfc5987 */
                if (flags & PHP_HTTP_PARAMS_DIMENSION) {
                        php_http_buffer_t *keybuf = php_http_buffer_from_string(key_str, key_len);
 -                      prepare_dimension(buf, keybuf, zvalue, pss, psl, vss, vsl, flags TSRMLS_CC);
 +                      prepare_dimension(buf, keybuf, zvalue, pss, psl, vss, vsl, flags);
                        php_http_buffer_free(&keybuf);
                } else if (rfc5987) {
 -                      shift_key(buf, key_str, key_len, pss, psl, flags TSRMLS_CC);
 -                      shift_rfc5987(buf, zvalue, vss, vsl, flags TSRMLS_CC);
 +                      shift_key(buf, key_str, key_len, pss, psl, flags);
 +                      shift_rfc5987(buf, zvalue, vss, vsl, flags);
                } else {
 -                      shift_arg(buf, key_str, key_len, zvalue, ass, asl, vss, vsl, flags TSRMLS_CC);
 +                      shift_arg(buf, key_str, key_len, zvalue, ass, asl, vss, vsl, flags);
                }
        } else {
                if (flags & PHP_HTTP_PARAMS_RFC5988) {
 -                      shift_rfc5988(buf, key_str, key_len, pss, psl, flags TSRMLS_CC);
 +                      shift_rfc5988(buf, key_str, key_len, pss, psl, flags);
                } else {
 -                      shift_key(buf, key_str, key_len, pss, psl, flags TSRMLS_CC);
 +                      shift_key(buf, key_str, key_len, pss, psl, flags);
                }
 -              shift_val(buf, zvalue, vss, vsl, flags TSRMLS_CC);
 +              shift_val(buf, zvalue, vss, vsl, flags);
        }
  }
  
 -php_http_buffer_t *php_http_params_to_string(php_http_buffer_t *buf, HashTable *params, const char *pss, size_t psl, const char *ass, size_t asl, const char *vss, size_t vsl, unsigned flags TSRMLS_DC)
 +php_http_buffer_t *php_http_params_to_string(php_http_buffer_t *buf, HashTable *params, const char *pss, size_t psl, const char *ass, size_t asl, const char *vss, size_t vsl, unsigned flags)
  {
 -      zval **zparam;
 -      HashPosition pos, pos1;
 -      php_http_array_hashkey_t key = php_http_array_hashkey_init(0), key1 = php_http_array_hashkey_init(0);
 +      zval *zparam;
 +      php_http_arrkey_t key;
        zend_bool rfc5987 = 0;
  
        if (!buf) {
                buf = php_http_buffer_init(NULL);
        }
  
 -      FOREACH_HASH_KEYVAL(pos, params, key, zparam) {
 -              zval **zvalue, **zargs;
 +      ZEND_HASH_FOREACH_KEY_VAL(params, key.h, key.key, zparam)
 +      {
 +              zval *zvalue, *zargs;
  
 -              if (Z_TYPE_PP(zparam) != IS_ARRAY) {
 +              if (Z_TYPE_P(zparam) != IS_ARRAY) {
                        zvalue = zparam;
                } else {
 -                      if (SUCCESS != zend_hash_find(Z_ARRVAL_PP(zparam), ZEND_STRS("value"), (void *) &zvalue)) {
 -                              if (SUCCESS != zend_hash_find(Z_ARRVAL_PP(zparam), ZEND_STRS("*rfc5987*"), (void *) &zvalue)) {
 +                      if (!(zvalue = zend_hash_str_find(Z_ARRVAL_P(zparam), ZEND_STRL("value")))) {
 +                              if (!(zvalue = zend_hash_str_find(Z_ARRVAL_P(zparam), ZEND_STRL("*rfc5987*")))) {
                                        zvalue = zparam;
                                } else {
                                        rfc5987 = 1;
                        }
                }
  
 -              php_http_array_hashkey_stringify(&key);
 -              shift_param(buf, key.str, key.len - 1, *zvalue, pss, psl, ass, asl, vss, vsl, flags, rfc5987 TSRMLS_CC);
 -              php_http_array_hashkey_stringfree(&key);
 +              php_http_arrkey_stringify(&key, NULL);
 +              shift_param(buf, key.key->val, key.key->len, zvalue, pss, psl, ass, asl, vss, vsl, flags, rfc5987);
 +              php_http_arrkey_dtor(&key);
 +
 +              if (Z_TYPE_P(zparam) == IS_ARRAY) {
 +                      zval *tmp = zend_hash_str_find(Z_ARRVAL_P(zparam), ZEND_STRL("arguments"));
  
 -              if (Z_TYPE_PP(zparam) == IS_ARRAY && SUCCESS != zend_hash_find(Z_ARRVAL_PP(zparam), ZEND_STRS("arguments"), (void *) &zvalue)) {
 -                      if (zvalue == zparam) {
 +                      if (tmp) {
 +                              zvalue = tmp;
 +                      } else if (zvalue == zparam) {
                                continue;
 +                      } else {
 +                              zvalue = zparam;
                        }
 -                      zvalue = zparam;
                }
  
 -              if (Z_TYPE_PP(zvalue) == IS_ARRAY) {
 -                      FOREACH_KEYVAL(pos1, *zvalue, key1, zargs) {
 -                              if (zvalue == zparam && key1.type == HASH_KEY_IS_STRING && !strcmp(key1.str, "value")) {
 +              if (Z_TYPE_P(zvalue) == IS_ARRAY) {
 +                      ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(zvalue), key.h, key.key, zargs)
 +                      {
 +                              if (zvalue == zparam && key.key && zend_string_equals_literal(key.key, "value")) {
                                        continue;
                                }
  
 -                              php_http_array_hashkey_stringify(&key1);
 -                              shift_arg(buf, key1.str, key1.len - 1, *zargs, ass, asl, vss, vsl, flags TSRMLS_CC);
 -                              php_http_array_hashkey_stringfree(&key1);
 +                              php_http_arrkey_stringify(&key, NULL);
 +                              shift_arg(buf, key.key->val, key.key->len, zargs, ass, asl, vss, vsl, flags);
 +                              php_http_arrkey_dtor(&key);
                        }
 +                      ZEND_HASH_FOREACH_END();
                }
        }
 +      ZEND_HASH_FOREACH_END();
  
        php_http_buffer_shrink(buf);
        php_http_buffer_fix(buf);
        return buf;
  }
  
 -php_http_params_token_t **php_http_params_separator_init(zval *zv TSRMLS_DC)
 +php_http_params_token_t **php_http_params_separator_init(zval *zv)
  {
 -      zval **sep;
 -      HashPosition pos;
 +      zval *sep, ztmp;
        php_http_params_token_t **ret, **tmp;
  
        if (!zv) {
                return NULL;
        }
  
 -      zv = php_http_ztyp(IS_ARRAY, zv);
 +      ZVAL_DUP(&ztmp, zv);
 +      zv = &ztmp;
 +      convert_to_array(zv);
 +
        ret = ecalloc(zend_hash_num_elements(Z_ARRVAL_P(zv)) + 1, sizeof(*ret));
  
        tmp = ret;
 -      FOREACH_VAL(pos, zv, sep) {
 -              zval *zt = php_http_ztyp(IS_STRING, *sep);
 +      ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(zv), sep)
 +      {
 +              zend_string *zs = zval_get_string(sep);
  
 -              if (Z_STRLEN_P(zt)) {
 +              if (zs->len) {
                        *tmp = emalloc(sizeof(**tmp));
 -                      (*tmp)->str = estrndup(Z_STRVAL_P(zt), (*tmp)->len = Z_STRLEN_P(zt));
 +                      (*tmp)->str = estrndup(zs->val, (*tmp)->len = zs->len);
                        ++tmp;
                }
 -              zval_ptr_dtor(&zt);
 +              zend_string_release(zs);
        }
 -      zval_ptr_dtor(&zv);
 +      ZEND_HASH_FOREACH_END();
 +
 +      zval_ptr_dtor(&ztmp);
  
        *tmp = NULL;
        return ret;
@@@ -1031,12 -978,6 +1033,12 @@@ void php_http_params_separator_free(php
        }
  }
  
 +static zend_class_entry *php_http_params_class_entry;
 +zend_class_entry *php_http_params_get_class_entry(void)
 +{
 +      return php_http_params_class_entry;
 +}
 +
  ZEND_BEGIN_ARG_INFO_EX(ai_HttpParams___construct, 0, 0, 0)
        ZEND_ARG_INFO(0, params)
        ZEND_ARG_INFO(0, param_sep)
  ZEND_END_ARG_INFO();
  PHP_METHOD(HttpParams, __construct)
  {
 -      zval *zcopy, *zparams = NULL, *param_sep = NULL, *arg_sep = NULL, *val_sep = NULL;
 -      long flags = PHP_HTTP_PARAMS_DEFAULT;
 +      zval *zparams = NULL, *param_sep = NULL, *arg_sep = NULL, *val_sep = NULL;
 +      zend_long flags = PHP_HTTP_PARAMS_DEFAULT;
        zend_error_handling zeh;
 +      zend_string *zs;
  
 -      php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|z!/z/z/z/l", &zparams, &param_sep, &arg_sep, &val_sep, &flags), invalid_arg, return);
 +      php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS(), "|z!/z/z/z/l", &zparams, &param_sep, &arg_sep, &val_sep, &flags), invalid_arg, return);
  
 -      zend_replace_error_handling(EH_THROW, php_http_exception_runtime_class_entry, &zeh TSRMLS_CC);
 +      zend_replace_error_handling(EH_THROW, php_http_get_exception_runtime_class_entry(), &zeh);
        {
                switch (ZEND_NUM_ARGS()) {
                        case 5:
 -                              zend_update_property_long(php_http_params_class_entry, getThis(), ZEND_STRL("flags"), flags TSRMLS_CC);
 +                              zend_update_property_long(php_http_params_class_entry, getThis(), ZEND_STRL("flags"), flags);
                                /* no break */
                        case 4:
 -                              zend_update_property(php_http_params_class_entry, getThis(), ZEND_STRL("val_sep"), val_sep TSRMLS_CC);
 +                              zend_update_property(php_http_params_class_entry, getThis(), ZEND_STRL("val_sep"), val_sep);
                                /* no break */
                        case 3:
 -                              zend_update_property(php_http_params_class_entry, getThis(), ZEND_STRL("arg_sep"), arg_sep TSRMLS_CC);
 +                              zend_update_property(php_http_params_class_entry, getThis(), ZEND_STRL("arg_sep"), arg_sep);
                                /* no break */
                        case 2:
 -                              zend_update_property(php_http_params_class_entry, getThis(), ZEND_STRL("param_sep"), param_sep TSRMLS_CC);
 +                              zend_update_property(php_http_params_class_entry, getThis(), ZEND_STRL("param_sep"), param_sep);
                                /* no break */
                }
  
                        switch (Z_TYPE_P(zparams)) {
                                case IS_OBJECT:
                                case IS_ARRAY:
 -                                      zcopy = php_http_zsep(1, IS_ARRAY, zparams);
 -                                      zend_update_property(php_http_params_class_entry, getThis(), ZEND_STRL("params"), zcopy TSRMLS_CC);
 -                                      zval_ptr_dtor(&zcopy);
 +                                      convert_to_array(zparams);
 +                                      zend_update_property(php_http_params_class_entry, getThis(), ZEND_STRL("params"), zparams);
                                        break;
                                default:
 -                                      zcopy = php_http_ztyp(IS_STRING, zparams);
 -                                      if (Z_STRLEN_P(zcopy)) {
 +                                      zs = zval_get_string(zparams);
 +                                      if (zs->len) {
 +                                              zval tmp;
 +
                                                php_http_params_opts_t opts = {
 -                                                      {Z_STRVAL_P(zcopy), Z_STRLEN_P(zcopy)},
 -                                                      php_http_params_separator_init(zend_read_property(php_http_params_class_entry, getThis(), ZEND_STRL("param_sep"), 0 TSRMLS_CC) TSRMLS_CC),
 -                                                      php_http_params_separator_init(zend_read_property(php_http_params_class_entry, getThis(), ZEND_STRL("arg_sep"), 0 TSRMLS_CC) TSRMLS_CC),
 -                                                      php_http_params_separator_init(zend_read_property(php_http_params_class_entry, getThis(), ZEND_STRL("val_sep"), 0 TSRMLS_CC) TSRMLS_CC),
 -                                                      NULL, flags
 +                                                      {zs->val, zs->len},
 +                                                      php_http_params_separator_init(zend_read_property(php_http_params_class_entry, getThis(), ZEND_STRL("param_sep"), 0, &tmp)),
 +                                                      php_http_params_separator_init(zend_read_property(php_http_params_class_entry, getThis(), ZEND_STRL("arg_sep"), 0, &tmp)),
 +                                                      php_http_params_separator_init(zend_read_property(php_http_params_class_entry, getThis(), ZEND_STRL("val_sep"), 0, &tmp)),
 +                                                      {{0}}, flags
                                                };
  
 -                                              MAKE_STD_ZVAL(zparams);
 -                                              array_init(zparams);
 -                                              php_http_params_parse(Z_ARRVAL_P(zparams), &opts TSRMLS_CC);
 -                                              zend_update_property(php_http_params_class_entry, getThis(), ZEND_STRL("params"), zparams TSRMLS_CC);
 -                                              zval_ptr_dtor(&zparams);
 +                                              array_init(&tmp);
 +                                              php_http_params_parse(Z_ARRVAL(tmp), &opts);
 +                                              zend_update_property(php_http_params_class_entry, getThis(), ZEND_STRL("params"), &tmp);
 +                                              zval_ptr_dtor(&tmp);
  
                                                php_http_params_separator_free(opts.param);
                                                php_http_params_separator_free(opts.arg);
                                                php_http_params_separator_free(opts.val);
                                        }
 -                                      zval_ptr_dtor(&zcopy);
 +                                      zend_string_release(zs);
                                        break;
                        }
                } else {
 -                      MAKE_STD_ZVAL(zparams);
 -                      array_init(zparams);
 -                      zend_update_property(php_http_params_class_entry, getThis(), ZEND_STRL("params"), zparams TSRMLS_CC);
 -                      zval_ptr_dtor(&zparams);
 +                      zval tmp;
 +
 +                      array_init(&tmp);
 +                      zend_update_property(php_http_params_class_entry, getThis(), ZEND_STRL("params"), &tmp);
 +                      zval_ptr_dtor(&tmp);
                }
        }
 -      zend_restore_error_handling(&zeh TSRMLS_CC);
 +      zend_restore_error_handling(&zeh);
  }
  
  ZEND_BEGIN_ARG_INFO_EX(ai_HttpParams_toArray, 0, 0, 0)
  ZEND_END_ARG_INFO();
  PHP_METHOD(HttpParams, toArray)
  {
 -      zval *zparams;
 +      zval zparams_tmp, *zparams;
  
        if (SUCCESS != zend_parse_parameters_none()) {
                return;
        }
 -      zparams = zend_read_property(php_http_params_class_entry, getThis(), ZEND_STRL("params"), 0 TSRMLS_CC);
 +      zparams = zend_read_property(php_http_params_class_entry, getThis(), ZEND_STRL("params"), 0, &zparams_tmp);
        RETURN_ZVAL(zparams, 1, 0);
  }
  
@@@ -1130,43 -1069,41 +1132,43 @@@ ZEND_BEGIN_ARG_INFO_EX(ai_HttpParams_to
  ZEND_END_ARG_INFO();
  PHP_METHOD(HttpParams, toString)
  {
 -      zval **tmp, *zparams, *zpsep, *zasep, *zvsep, *zflags;
 +      zval *tmp, *zparams, *zpsep, *zasep, *zvsep;
 +      zval zparams_tmp, flags_tmp, psep_tmp, asep_tmp, vsep_tmp;
 +      zend_string *psep, *asep, *vsep;
 +      long flags;
        php_http_buffer_t buf;
  
 -      zparams = php_http_zsep(1, IS_ARRAY, zend_read_property(php_http_params_class_entry, getThis(), ZEND_STRL("params"), 0 TSRMLS_CC));
 -      zflags = php_http_ztyp(IS_LONG, zend_read_property(php_http_params_class_entry, getThis(), ZEND_STRL("flags"), 0 TSRMLS_CC));
 +      zparams = zend_read_property(php_http_params_class_entry, getThis(), ZEND_STRL("params"), 0, &zparams_tmp);
 +      convert_to_array_ex(zparams);
 +      flags = zval_get_long(zend_read_property(php_http_params_class_entry, getThis(), ZEND_STRL("flags"), 0, &flags_tmp));
  
 -      zpsep = zend_read_property(php_http_params_class_entry, getThis(), ZEND_STRL("param_sep"), 0 TSRMLS_CC);
 -      if (Z_TYPE_P(zpsep) == IS_ARRAY && SUCCESS == zend_hash_get_current_data(Z_ARRVAL_P(zpsep), (void *) &tmp)) {
 -              zpsep = php_http_ztyp(IS_STRING, *tmp);
 +      zpsep = zend_read_property(php_http_params_class_entry, getThis(), ZEND_STRL("param_sep"), 0, &psep_tmp);
 +      if (Z_TYPE_P(zpsep) == IS_ARRAY && (tmp = zend_hash_get_current_data(Z_ARRVAL_P(zpsep)))) {
 +              psep = zval_get_string(tmp);
        } else {
 -              zpsep = php_http_ztyp(IS_STRING, zpsep);
 +              psep = zval_get_string(zpsep);
        }
 -      zasep = zend_read_property(php_http_params_class_entry, getThis(), ZEND_STRL("arg_sep"), 0 TSRMLS_CC);
 -      if (Z_TYPE_P(zasep) == IS_ARRAY && SUCCESS == zend_hash_get_current_data(Z_ARRVAL_P(zasep), (void *) &tmp)) {
 -              zasep = php_http_ztyp(IS_STRING, *tmp);
 +      zasep = zend_read_property(php_http_params_class_entry, getThis(), ZEND_STRL("arg_sep"), 0, &asep_tmp);
 +      if (Z_TYPE_P(zasep) == IS_ARRAY && (tmp = zend_hash_get_current_data(Z_ARRVAL_P(zasep)))) {
 +              asep = zval_get_string(tmp);
        } else {
 -              zasep = php_http_ztyp(IS_STRING, zasep);
 +              asep = zval_get_string(zasep);
        }
 -      zvsep = zend_read_property(php_http_params_class_entry, getThis(), ZEND_STRL("val_sep"), 0 TSRMLS_CC);
 -      if (Z_TYPE_P(zvsep) == IS_ARRAY && SUCCESS == zend_hash_get_current_data(Z_ARRVAL_P(zvsep), (void *) &tmp)) {
 -              zvsep = php_http_ztyp(IS_STRING, *tmp);
 +      zvsep = zend_read_property(php_http_params_class_entry, getThis(), ZEND_STRL("val_sep"), 0, &vsep_tmp);
 +      if (Z_TYPE_P(zvsep) == IS_ARRAY && (tmp = zend_hash_get_current_data(Z_ARRVAL_P(zvsep)))) {
 +              vsep = zval_get_string(tmp);
        } else {
 -              zvsep = php_http_ztyp(IS_STRING, zvsep);
 +              vsep = zval_get_string(zvsep);
        }
  
        php_http_buffer_init(&buf);
 -      php_http_params_to_string(&buf, Z_ARRVAL_P(zparams), Z_STRVAL_P(zpsep), Z_STRLEN_P(zpsep), Z_STRVAL_P(zasep), Z_STRLEN_P(zasep), Z_STRVAL_P(zvsep), Z_STRLEN_P(zvsep), Z_LVAL_P(zflags) TSRMLS_CC);
 +      php_http_params_to_string(&buf, Z_ARRVAL_P(zparams), psep->val, psep->len, asep->val, asep->len, vsep->val, vsep->len, flags);
  
 -      zval_ptr_dtor(&zparams);
 -      zval_ptr_dtor(&zpsep);
 -      zval_ptr_dtor(&zasep);
 -      zval_ptr_dtor(&zvsep);
 -      zval_ptr_dtor(&zflags);
 +      zend_string_release(psep);
 +      zend_string_release(asep);
 +      zend_string_release(vsep);
  
 -      RETVAL_PHP_HTTP_BUFFER_VAL(&buf);
 +      RETVAL_STR(php_http_cs2zs(buf.data, buf.used));
  }
  
  ZEND_BEGIN_ARG_INFO_EX(ai_HttpParams_offsetExists, 0, 0, 1)
  ZEND_END_ARG_INFO();
  PHP_METHOD(HttpParams, offsetExists)
  {
 -      char *name_str;
 -      int name_len;
 -      zval **zparam, *zparams;
 +      zend_string *name;
 +      zval zparams_tmp, *zparam, *zparams;
  
 -      if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &name_str, &name_len)) {
 +      if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS(), "S", &name)) {
                return;
        }
  
 -      zparams = php_http_ztyp(IS_ARRAY, zend_read_property(php_http_params_class_entry, getThis(), ZEND_STRL("params"), 0 TSRMLS_CC));
 +      zparams = zend_read_property(php_http_params_class_entry, getThis(), ZEND_STRL("params"), 0, &zparams_tmp);
  
 -      if (SUCCESS == zend_symtable_find(Z_ARRVAL_P(zparams), name_str, name_len + 1, (void *) &zparam)) {
 -              RETVAL_BOOL(Z_TYPE_PP(zparam) != IS_NULL);
 +      if (Z_TYPE_P(zparams) == IS_ARRAY && (zparam = zend_symtable_find(Z_ARRVAL_P(zparams), name))) {
 +              RETVAL_BOOL(Z_TYPE_P(zparam) != IS_NULL);
        } else {
                RETVAL_FALSE;
        }
 -      zval_ptr_dtor(&zparams);
  }
  
  ZEND_BEGIN_ARG_INFO_EX(ai_HttpParams_offsetGet, 0, 0, 1)
  ZEND_END_ARG_INFO();
  PHP_METHOD(HttpParams, offsetGet)
  {
 -      char *name_str;
 -      int name_len;
 -      zval **zparam, *zparams;
 +      zend_string *name;
 +      zval zparams_tmp, *zparam, *zparams;
  
 -      if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &name_str, &name_len)) {
 +      if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS(), "S", &name)) {
                return;
        }
  
 -      zparams = php_http_ztyp(IS_ARRAY, zend_read_property(php_http_params_class_entry, getThis(), ZEND_STRL("params"), 0 TSRMLS_CC));
 +      zparams = zend_read_property(php_http_params_class_entry, getThis(), ZEND_STRL("params"), 0, &zparams_tmp);
  
 -      if (SUCCESS == zend_symtable_find(Z_ARRVAL_P(zparams), name_str, name_len + 1, (void *) &zparam)) {
 -              RETVAL_ZVAL(*zparam, 1, 0);
 +      if (Z_TYPE_P(zparams) == IS_ARRAY && (zparam = zend_symtable_find(Z_ARRVAL_P(zparams), name))) {
 +              RETVAL_ZVAL(zparam, 1, 0);
        }
 -
 -      zval_ptr_dtor(&zparams);
  }
  
  ZEND_BEGIN_ARG_INFO_EX(ai_HttpParams_offsetUnset, 0, 0, 1)
  ZEND_END_ARG_INFO();
  PHP_METHOD(HttpParams, offsetUnset)
  {
 -      char *name_str;
 -      int name_len;
 -      zval *zparams;
 +      zend_string *name;
 +      zval zparams_tmp, *zparams;
  
 -      if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &name_str, &name_len)) {
 +      if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS(), "S", &name)) {
                return;
        }
  
 -      zparams = php_http_zsep(1, IS_ARRAY, zend_read_property(php_http_params_class_entry, getThis(), ZEND_STRL("params"), 0 TSRMLS_CC));
 +      zparams = zend_read_property(php_http_params_class_entry, getThis(), ZEND_STRL("params"), 0, &zparams_tmp);
  
 -      zend_symtable_del(Z_ARRVAL_P(zparams), name_str, name_len + 1);
 -      zend_update_property(php_http_params_class_entry, getThis(), ZEND_STRL("params"), zparams TSRMLS_CC);
 -
 -      zval_ptr_dtor(&zparams);
 +      if (Z_TYPE_P(zparams) == IS_ARRAY) {
 +              zend_symtable_del(Z_ARRVAL_P(zparams), name);
 +      }
  }
  
  ZEND_BEGIN_ARG_INFO_EX(ai_HttpParams_offsetSet, 0, 0, 2)
  ZEND_END_ARG_INFO();
  PHP_METHOD(HttpParams, offsetSet)
  {
 -      zval *nvalue;
 -      char *name_str;
 -      int name_len;
 -      zval **zparam, *zparams;
 +      zend_string *name;
 +      zval zparams_tmp, *zparam, *zparams, *nvalue;
  
 -      if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz", &name_str, &name_len, &nvalue)) {
 +      if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS(), "Sz", &name, &nvalue)) {
                return;
        }
  
 -      zparams = php_http_zsep(1, IS_ARRAY, zend_read_property(php_http_params_class_entry, getThis(), ZEND_STRL("params"), 0 TSRMLS_CC));
 +      zparams = zend_read_property(php_http_params_class_entry, getThis(), ZEND_STRL("params"), 0, &zparams_tmp);
 +      convert_to_array(zparams);
  
 -      if (name_len) {
 +      if (name->len) {
                if (Z_TYPE_P(nvalue) == IS_ARRAY) {
 -                      zval *new_zparam;
 -
 -                      if (SUCCESS == zend_symtable_find(Z_ARRVAL_P(zparams), name_str, name_len + 1, (void *) &zparam)) {
 -                              new_zparam = php_http_zsep(1, IS_ARRAY, *zparam);
 -                              array_join(Z_ARRVAL_P(nvalue), Z_ARRVAL_P(new_zparam), 0, 0);
 +                      if ((zparam = zend_symtable_find(Z_ARRVAL_P(zparams), name))) {
 +                              convert_to_array(zparam);
 +                              array_join(Z_ARRVAL_P(nvalue), Z_ARRVAL_P(zparam), 0, 0);
                        } else {
 -                              new_zparam = nvalue;
 -                              Z_ADDREF_P(new_zparam);
 +                              Z_TRY_ADDREF_P(nvalue);
 +                              add_assoc_zval_ex(zparams, name->val, name->len, nvalue);
                        }
 -                      add_assoc_zval_ex(zparams, name_str, name_len + 1, new_zparam);
                } else {
 -                      zval *tmp;
 +                      zval tmp;
  
 -                      if (SUCCESS == zend_symtable_find(Z_ARRVAL_P(zparams), name_str, name_len + 1, (void *) &zparam)) {
 -                              tmp = php_http_zsep(1, IS_ARRAY, *zparam);
 +                      if ((zparam = zend_symtable_find(Z_ARRVAL_P(zparams), name))) {
 +                              ZVAL_DUP(&tmp, zparam);
 +                              convert_to_array(&tmp);
                        } else {
 -                              MAKE_STD_ZVAL(tmp);
 -                              array_init(tmp);
 +                              array_init(&tmp);
                        }
  
 -                      Z_ADDREF_P(nvalue);
 -                      add_assoc_zval_ex(tmp, ZEND_STRS("value"), nvalue);
 -                      add_assoc_zval_ex(zparams, name_str, name_len + 1, tmp);
 +                      Z_TRY_ADDREF_P(nvalue);
 +                      add_assoc_zval_ex(&tmp, ZEND_STRL("value"), nvalue);
 +                      add_assoc_zval_ex(zparams, name->val, name->len, &tmp);
                }
        } else {
 -              zval *tmp = php_http_ztyp(IS_STRING, nvalue), *arr;
 +              zval arr;
 +              zend_string *zs = zval_get_string(nvalue);
  
 -              MAKE_STD_ZVAL(arr);
 -              array_init(arr);
 -              add_assoc_bool_ex(arr, ZEND_STRS("value"), 1);
 -              add_assoc_zval_ex(zparams, Z_STRVAL_P(tmp), Z_STRLEN_P(tmp) + 1, arr);
 -              zval_ptr_dtor(&tmp);
 +              array_init(&arr);
 +              add_assoc_bool_ex(&arr, ZEND_STRL("value"), 1);
 +              add_assoc_zval_ex(zparams, zs->val, zs->len, &arr);
 +              zend_string_release(zs);
        }
 -
 -      zend_update_property(php_http_params_class_entry, getThis(), ZEND_STRL("params"), zparams TSRMLS_CC);
 -      zval_ptr_dtor(&zparams);
  }
  
  static zend_function_entry php_http_params_methods[] = {
        EMPTY_FUNCTION_ENTRY
  };
  
 -zend_class_entry *php_http_params_class_entry;
 -
  PHP_MINIT_FUNCTION(http_params)
  {
        zend_class_entry ce = {0};
  
        INIT_NS_CLASS_ENTRY(ce, "http", "Params", php_http_params_methods);
 -      php_http_params_class_entry = zend_register_internal_class(&ce TSRMLS_CC);
 +      php_http_params_class_entry = zend_register_internal_class(&ce);
        php_http_params_class_entry->create_object = php_http_params_object_new;
 -      zend_class_implements(php_http_params_class_entry TSRMLS_CC, 1, zend_ce_arrayaccess);
 -
 -      zend_declare_class_constant_stringl(php_http_params_class_entry, ZEND_STRL("DEF_PARAM_SEP"), ZEND_STRL(",") TSRMLS_CC);
 -      zend_declare_class_constant_stringl(php_http_params_class_entry, ZEND_STRL("DEF_ARG_SEP"), ZEND_STRL(";") TSRMLS_CC);
 -      zend_declare_class_constant_stringl(php_http_params_class_entry, ZEND_STRL("DEF_VAL_SEP"), ZEND_STRL("=") TSRMLS_CC);
 -      zend_declare_class_constant_stringl(php_http_params_class_entry, ZEND_STRL("COOKIE_PARAM_SEP"), ZEND_STRL("") TSRMLS_CC);
 -
 -      zend_declare_class_constant_long(php_http_params_class_entry, ZEND_STRL("PARSE_RAW"), PHP_HTTP_PARAMS_RAW TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_params_class_entry, ZEND_STRL("PARSE_ESCAPED"), PHP_HTTP_PARAMS_ESCAPED TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_params_class_entry, ZEND_STRL("PARSE_URLENCODED"), PHP_HTTP_PARAMS_URLENCODED TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_params_class_entry, ZEND_STRL("PARSE_DIMENSION"), PHP_HTTP_PARAMS_DIMENSION TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_params_class_entry, ZEND_STRL("PARSE_RFC5987"), PHP_HTTP_PARAMS_RFC5987 TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_params_class_entry, ZEND_STRL("PARSE_RFC5988"), PHP_HTTP_PARAMS_RFC5988 TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_params_class_entry, ZEND_STRL("PARSE_DEFAULT"), PHP_HTTP_PARAMS_DEFAULT TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_params_class_entry, ZEND_STRL("PARSE_QUERY"), PHP_HTTP_PARAMS_QUERY TSRMLS_CC);
 -
 -      zend_declare_property_null(php_http_params_class_entry, ZEND_STRL("params"), ZEND_ACC_PUBLIC TSRMLS_CC);
 -      zend_declare_property_stringl(php_http_params_class_entry, ZEND_STRL("param_sep"), ZEND_STRL(","), ZEND_ACC_PUBLIC TSRMLS_CC);
 -      zend_declare_property_stringl(php_http_params_class_entry, ZEND_STRL("arg_sep"), ZEND_STRL(";"), ZEND_ACC_PUBLIC TSRMLS_CC);
 -      zend_declare_property_stringl(php_http_params_class_entry, ZEND_STRL("val_sep"), ZEND_STRL("="), ZEND_ACC_PUBLIC TSRMLS_CC);
 -      zend_declare_property_long(php_http_params_class_entry, ZEND_STRL("flags"), PHP_HTTP_PARAMS_DEFAULT, ZEND_ACC_PUBLIC TSRMLS_CC);
 +      zend_class_implements(php_http_params_class_entry, 1, zend_ce_arrayaccess);
 +
 +      zend_declare_class_constant_stringl(php_http_params_class_entry, ZEND_STRL("DEF_PARAM_SEP"), ZEND_STRL(","));
 +      zend_declare_class_constant_stringl(php_http_params_class_entry, ZEND_STRL("DEF_ARG_SEP"), ZEND_STRL(";"));
 +      zend_declare_class_constant_stringl(php_http_params_class_entry, ZEND_STRL("DEF_VAL_SEP"), ZEND_STRL("="));
 +      zend_declare_class_constant_stringl(php_http_params_class_entry, ZEND_STRL("COOKIE_PARAM_SEP"), ZEND_STRL(""));
 +
 +      zend_declare_class_constant_long(php_http_params_class_entry, ZEND_STRL("PARSE_RAW"), PHP_HTTP_PARAMS_RAW);
 +      zend_declare_class_constant_long(php_http_params_class_entry, ZEND_STRL("PARSE_ESCAPED"), PHP_HTTP_PARAMS_ESCAPED);
 +      zend_declare_class_constant_long(php_http_params_class_entry, ZEND_STRL("PARSE_URLENCODED"), PHP_HTTP_PARAMS_URLENCODED);
 +      zend_declare_class_constant_long(php_http_params_class_entry, ZEND_STRL("PARSE_DIMENSION"), PHP_HTTP_PARAMS_DIMENSION);
 +      zend_declare_class_constant_long(php_http_params_class_entry, ZEND_STRL("PARSE_RFC5987"), PHP_HTTP_PARAMS_RFC5987);
 +      zend_declare_class_constant_long(php_http_params_class_entry, ZEND_STRL("PARSE_RFC5988"), PHP_HTTP_PARAMS_RFC5988);
 +      zend_declare_class_constant_long(php_http_params_class_entry, ZEND_STRL("PARSE_DEFAULT"), PHP_HTTP_PARAMS_DEFAULT);
 +      zend_declare_class_constant_long(php_http_params_class_entry, ZEND_STRL("PARSE_QUERY"), PHP_HTTP_PARAMS_QUERY);
 +
 +      zend_declare_property_null(php_http_params_class_entry, ZEND_STRL("params"), ZEND_ACC_PUBLIC);
 +      zend_declare_property_stringl(php_http_params_class_entry, ZEND_STRL("param_sep"), ZEND_STRL(","), ZEND_ACC_PUBLIC);
 +      zend_declare_property_stringl(php_http_params_class_entry, ZEND_STRL("arg_sep"), ZEND_STRL(";"), ZEND_ACC_PUBLIC);
 +      zend_declare_property_stringl(php_http_params_class_entry, ZEND_STRL("val_sep"), ZEND_STRL("="), ZEND_ACC_PUBLIC);
 +      zend_declare_property_long(php_http_params_class_entry, ZEND_STRL("flags"), PHP_HTTP_PARAMS_DEFAULT, ZEND_ACC_PUBLIC);
  
        return SUCCESS;
  }
diff --combined src/php_http_version.c
index e763a855a12169a6df342e49b2316c5e41793b21,c861a6470a2ba084a5502e3066193547e43967c8..7f7d34245384f000376d2ba6f1725dac3131c466
@@@ -12,7 -12,7 +12,7 @@@
  
  #include "php_http_api.h"
  
 -php_http_version_t *php_http_version_init(php_http_version_t *v, unsigned major, unsigned minor TSRMLS_DC)
 +php_http_version_t *php_http_version_init(php_http_version_t *v, unsigned major, unsigned minor)
  {
        if (!v) {
                v = emalloc(sizeof(*v));
@@@ -24,7 -24,7 +24,7 @@@
        return v;
  }
  
 -php_http_version_t *php_http_version_parse(php_http_version_t *v, const char *str TSRMLS_DC)
 +php_http_version_t *php_http_version_parse(php_http_version_t *v, const char *str)
  {
        long major, minor;
        char separator = 0;
                major = *ptr++ - '0';
                if (major >= 0 && major <= 9) {
                        separator = *ptr++;
-                       if (separator) {
-                               if (separator != '.' && separator != ',') {
-                                       php_error_docref(NULL, E_NOTICE, "Non-standard version separator '%c' in HTTP protocol version '%s'", separator, ptr - 2);
-                               }
+                       switch (separator) {
+                       default:
 -                              php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Non-standard version separator '%c' in HTTP protocol version '%s'", separator, ptr - 2);
++                              php_error_docref(NULL, E_NOTICE, "Non-standard version separator '%c' in HTTP protocol version '%s'", separator, ptr - 2);
+                               /* no break */
+                       case '.':
+                       case ',':
                                minor = *ptr - '0';
-                               if (minor >= 0 && minor <= 9) {
-                                       return php_http_version_init(v, major, minor);
+                               break;
+                       case ' ':
+                               if (major > 1) {
+                                       minor = 0;
+                               } else {
+                                       goto error;
                                }
                        }
 -                              return php_http_version_init(v, major, minor TSRMLS_CC);
+                       if (minor >= 0 && minor <= 9) {
++                              return php_http_version_init(v, major, minor);
+                       }
                }
        }
  
 -      php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not parse HTTP protocol version '%s'", str);
+       error:
 +      php_error_docref(NULL, E_WARNING, "Could not parse HTTP protocol version '%s'", str);
        return NULL;
  }
  
 -void php_http_version_to_string(php_http_version_t *v, char **str, size_t *len, const char *pre, const char *post TSRMLS_DC)
 +void php_http_version_to_string(php_http_version_t *v, char **str, size_t *len, const char *pre, const char *post)
  {
        *len = spprintf(str, 0, "%s%u.%u%s", pre ? pre : "", v->major, v->minor, post ? post : "");
  }
diff --combined tests/gh-issue47.phpt
index 0000000000000000000000000000000000000000,6956588cf077d0bcdb7351f94a74198b70899393..1a09b8d3579dca58b2c05b4211f6c4ce48791ada
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,27 +1,27 @@@
 -$url0=new http\Url($urls[0]);
+ --TEST--
+ Null pointer deref in sanitize_value
+ --SKIPIF--
+ <?php
+ include "skipif.inc";
+ ?>
+ --FILE--
+ <?php
+ echo "Test\n";
+ $urls = [
+     "",
+     "? = ="
+ ];
++$url0=new http\Url($urls[0], null, http\Url::FROM_ENV);
+ $url1=$url0->mod($urls[1]);
+ echo $url1;
+ ?>
+ ===DONE===
+ --EXPECTF--
+ Test
+ http://%s/
+ ===DONE===