+static inline void sanitize_rfc5987(zval *zv, char **language, zend_bool *latin1)
+{
+ char *ptr;
+
+ /* examples:
+ * iso-8850-1'de'bl%f6der%20schei%df%21
+ * utf-8'de-DE'bl%c3%b6der%20schei%c3%9f%21
+ */
+
+ switch (Z_STRVAL_P(zv)[0]) {
+ case 'I':
+ case 'i':
+ if (!strncasecmp(Z_STRVAL_P(zv), "iso-8859-1", lenof("iso-8859-1"))) {
+ *latin1 = 1;
+ ptr = Z_STRVAL_P(zv) + lenof("iso-8859-1");
+ break;
+ }
+ /* no break */
+ case 'U':
+ case 'u':
+ if (!strncasecmp(Z_STRVAL_P(zv), "utf-8", lenof("utf-8"))) {
+ *latin1 = 0;
+ ptr = Z_STRVAL_P(zv) + lenof("utf-8");
+ break;
+ }
+ /* no break */
+ default:
+ return;
+ }
+
+ /* extract language */
+ if (*ptr == '\'') {
+ for (*language = ++ptr; *ptr && *ptr != '\''; ++ptr);
+ if (!*ptr) {
+ *language = NULL;
+ return;
+ }
+ *language = estrndup(*language, ptr - *language);
+
+ /* remainder */
+ ptr = estrdup(++ptr);
+ zval_dtor(zv);
+ ZVAL_STR(zv, php_http_cs2zs(ptr, strlen(ptr)));
+ }
+}
+
+static void utf8encode(zval *zv)
+{
+ size_t pos, len = 0;
+ unsigned char *ptr = (unsigned char *) Z_STRVAL_P(zv);
+
+ while (*ptr) {
+ if (*ptr++ >= 0x80) {
+ ++len;
+ }
+ ++len;
+ }
+
+ ptr = safe_emalloc(1, len, 1);
+ for (len = 0, pos = 0; len <= Z_STRLEN_P(zv); ++len, ++pos) {
+ ptr[pos] = Z_STRVAL_P(zv)[len];
+ if ((ptr[pos]) >= 0x80) {
+ ptr[pos + 1] = 0x80 | (ptr[pos] & 0x3f);
+ ptr[pos] = 0xc0 | ((ptr[pos] >> 6) & 0x1f);
+ ++pos;
+ }
+ }
+ zval_dtor(zv);
+ ZVAL_STR(zv, php_http_cs2zs((char *) ptr, pos-1));
+}
+
+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);
+ ZVAL_STR(zv, php_trim(zs, NULL, 0, 3));
+ zend_string_release(zs);
+
+ if (rfc5987) {
+ sanitize_rfc5987(zv, &language, &latin1);
+ }
+
+ if (flags & PHP_HTTP_PARAMS_ESCAPED) {
+ sanitize_escaped(zv);
+ }
+
+ if ((flags & PHP_HTTP_PARAMS_URLENCODED) || (rfc5987 && language)) {
+ sanitize_urlencoded(zv);
+ }
+
+ if (rfc5987 && language) {
+ zval tmp;
+
+ if (latin1) {
+ utf8encode(zv);
+ }
+
+ ZVAL_COPY_VALUE(&tmp, zv);
+ array_init(zv);
+ 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)
+{
+ zval zv;
+
+ ZVAL_STRINGL(&zv, old_key, old_len);
+
+ if (flags & PHP_HTTP_PARAMS_URLENCODED) {
+ prepare_urlencoded(&zv);
+ }
+
+ if (flags & PHP_HTTP_PARAMS_ESCAPED) {
+ prepare_escaped(&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)
+{
+ if (flags & PHP_HTTP_PARAMS_URLENCODED) {
+ prepare_urlencoded(zv);
+ }
+
+ if (flags & PHP_HTTP_PARAMS_ESCAPED) {
+ prepare_escaped(zv);
+ }
+}
+
+static void merge_param(HashTable *params, zval *zdata, zval **current_param, zval **current_args)
+{
+ 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);
+ fprintf(stderr, "\n");
+ }
+#endif
+
+ zend_hash_get_current_key(Z_ARRVAL_P(zdata), &hkey.key, &hkey.h);
+
+ if ((hkey.key && !zend_hash_exists(params, hkey.key))
+ || (!hkey.key && !zend_hash_index_exists(params, hkey.h))
+ ) {
+ zval tmp, arg, *args;
+
+ /* create the entry if it doesn't exist */
+ 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.key) {
+ ptr = zend_hash_update(params, hkey.key, &tmp);
+ } else {
+ ptr = zend_hash_index_update(params, hkey.h, &tmp);
+ }
+ } else {
+ /* merge */
+ if (hkey.key) {
+ ptr = zend_hash_find(params, hkey.key);
+ } else {
+ ptr = zend_hash_index_find(params, hkey.h);
+ }
+
+ zdata_ptr = zdata;
+
+ 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]]]
+ * ^- ptr
+ * zdata = [arr => [0 => NULL]]
+ * ^- zdata_ptr
+ */
+ zval *test_ptr;
+
+ 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) {
+ /* now find key in ptr */
+ if (HASH_KEY_IS_STRING == zend_hash_get_current_key(Z_ARRVAL_P(zdata_ptr), &hkey.key, &hkey.h)) {
+ zval *tmp_ptr = ptr;
+
+ if ((ptr = zend_hash_find(Z_ARRVAL_P(ptr), hkey.key))) {
+ zdata_ptr = test_ptr;
+ } else {
+ ptr = tmp_ptr;
+ Z_TRY_ADDREF_P(test_ptr);
+ ptr = zend_hash_update(Z_ARRVAL_P(ptr), hkey.key, test_ptr);
+ break;
+ }
+ } else {
+ if ((ptr = zend_hash_index_find(Z_ARRVAL_P(ptr), hkey.h))) {
+ zdata_ptr = test_ptr;
+ } else if (hkey.h) {
+ Z_TRY_ADDREF_P(test_ptr);
+ ptr = zend_hash_index_update(Z_ARRVAL_P(ptr), hkey.h, test_ptr);
+ break;
+ } else {
+ 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_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(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 {
+ ptr = zend_hash_next_index_insert(Z_ARRVAL_P(ptr), test_ptr);
+ }
+ break;
+ }
+ }
+
+ }
+ }
+
+ /* bubble up */
+ 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)