Merge branch 'v2.6.x'
authorMichael Wallner <mike@php.net>
Mon, 22 Aug 2016 11:38:39 +0000 (13:38 +0200)
committerMichael Wallner <mike@php.net>
Mon, 22 Aug 2016 11:38:39 +0000 (13:38 +0200)
1  2 
package.xml
src/php_http_client_request.c
src/php_http_info.c
src/php_http_message.c
src/php_http_url.c
src/php_http_url.h
tests/gh-issue7.phpt

diff --cc package.xml
@@@ -35,21 -31,40 +31,40 @@@ https://mdref.m6w6.name/htt
    <email>mike@php.net</email>
    <active>yes</active>
   </lead>
-  <date>2016-03-09</date>
+  <date>2016-08-22</date>
   <version>
-   <release>3.0.1</release>
-   <api>3.0.0</api>
 -  <release>2.6.0beta1</release>
 -  <api>2.6.0</api>
++  <release>3.1.0beta1</release>
++  <api>3.1.0</api>
   </version>
   <stability>
-   <release>stable</release>
+   <release>beta</release>
    <api>stable</api>
   </stability>
   <license uri="http://copyfree.org/content/standard/licenses/2bsd/license.txt">BSD-2-Clause</license>
   <notes><![CDATA[
- * Fix php-bug #71719: Buffer overflow in HTTP url parsing functions (Mike, rc0r)
- * Fix gh-issue #28: Possible null pointer dereference in php_http_url_mod() (rc0r)
- * Fix gh-issue #21: Fix PHP7 config.w32 (Jan Ehrhardt)
- * Fix gh-issue #20: setSslOptions notice with curl 7.43 (Mike, Vitaliy Demidov)
+ + Added http\Client\Curl\User interface for userland event loops
+ + Added http\Url::IGNORE_ERRORS, http\Url::SILENT_ERRORS and http\Url::STDFLAGS
+ + Added http\Client::setDebug(callable $debug)
+ + Added http\Client\Curl\FEATURES constants and namespace
+ + Added http\Client\Curl\VERSIONS constants and namespace
+ + Added share_cookies and share_ssl (libcurl >= 7.23.0) options to http\Client::configure()
+ + http\Client uses curl_share handles to properly share cookies and SSL/TLS sessions between requests
+ + Improved configure checks for default CA bundles
+ + Improved negotiation precision
+ * Fixed regression introduced by http\Params::PARSE_RFC5987: negotiation using the params parser would receive param keys without the trailing asterisk, stripped by http\Params::PARSE_RFC5987.
+ * Fix gh-issue #50: http\Client::dequeue() within http\Client::setDebug() causes segfault (Mike, Maik Wagner)
+ * Fix gh-issue #47: http\Url: Null pointer deref in sanitize_value() (Mike, @rc0r)
+ * Fix gh-issue #45: HTTP/2 response message parsing broken with libcurl >= 7.49.1 (Mike)
+ * Fix gh-issue #43: Joining query with empty original variable in query (Mike, Sander Backus)
+ * Fix gh-issue #42: fatal error when using punycode in URLs (Mike, Sebastian Thielen)
+ * Fix gh-issue #41: Use curl_version_info_data.features when initializing options (Mike)
+ * Fix gh-issue #40: determinde the SSL backend used by curl at runtime (Mike, @rcanavan)
+ * Fix gh-issue #39: Notice: http\Client::enqueue(): Could not set option proxy_service_name (Mike, @rcanavan)
+ * Fix gh-issue #38: Persistent curl handles: error code not properly reset (Mike, @afflerbach)
+ * Fix gh-issue #36: Unexpected cookies sent if persistent_handle_id is used (Mike, @rcanavan, @afflerbach)
+ * Fix gh-issue #34: allow setting multiple headers with the same name (Mike, @rcanavan)
+ * Fix gh-issue #33: allow setting prodyhost request option to NULL (Mike, @rcanavan)
 -* Fix gh-issue #31: add/improve configure checks for default CA bundle/path (Mike, @rcanavan) 
++* Fix gh-issue #31: add/improve configure checks for default CA bundle/path (Mike, @rcanavan)
  ]]></notes>
   <contents>
    <dir name="/">
       <file role="test" name="filterchunked.phpt"/>
       <file role="test" name="filterzlib.phpt"/>
       <file role="test" name="gh-issue6.phpt"/>
--     <file role="test" name="gh-issue7.phpt"/>
       <file role="test" name="gh-issue12.phpt"/>
+      <file role="test" name="gh-issue42.phpt"/>
+      <file role="test" name="gh-issue47.phpt"/>
+      <file role="test" name="gh-issue48.phpt"/>
+      <file role="test" name="gh-issue50.phpt"/>
       <file role="test" name="header001.phpt"/>
       <file role="test" name="header002.phpt"/>
       <file role="test" name="header003.phpt"/>
@@@ -59,7 -53,7 +59,7 @@@ static PHP_METHOD(HttpClientRequest, __
                PHP_HTTP_INFO(obj->message).request.method = estrndup(meth_str, meth_len);
        }
        if (zurl) {
-               PHP_HTTP_INFO(obj->message).request.url = php_http_url_from_zval(zurl, ~0);
 -              PHP_HTTP_INFO(obj->message).request.url = php_http_url_from_zval(zurl, PHP_HTTP_URL_STDFLAGS TSRMLS_CC);
++              PHP_HTTP_INFO(obj->message).request.url = php_http_url_from_zval(zurl, PHP_HTTP_URL_STDFLAGS);
        }
        if (zheaders) {
                array_copy(Z_ARRVAL_P(zheaders), &obj->message->hdrs);
@@@ -147,9 -147,9 +147,9 @@@ php_http_info_t *php_http_info_parse(ph
                        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);
 -                                      PHP_HTTP_INFO(info).request.url = php_http_url_parse(url, http - url, PHP_HTTP_URL_STDFLAGS TSRMLS_CC);
++                                      PHP_HTTP_INFO(info).request.url = php_http_url_parse(url, http - url, PHP_HTTP_URL_STDFLAGS);
                                } else {
-                                       PHP_HTTP_INFO(info).request.url = php_http_url_parse_authority(url, http - url, ~0);
 -                                      PHP_HTTP_INFO(info).request.url = php_http_url_parse_authority(url, http - url, PHP_HTTP_URL_STDFLAGS TSRMLS_CC);
++                                      PHP_HTTP_INFO(info).request.url = php_http_url_parse_authority(url, http - url, PHP_HTTP_URL_STDFLAGS);
                                }
                                if (!PHP_HTTP_INFO(info).request.url) {
                                        PTR_SET(PHP_HTTP_INFO(info).request.method, NULL);
@@@ -58,20 -59,20 +58,20 @@@ php_http_message_t *php_http_message_in
  
        switch (type) {
                case PHP_HTTP_REQUEST:
 -                      mbody = php_http_env_get_request_body(TSRMLS_C);
 +                      mbody = php_http_env_get_request_body();
                        php_http_message_body_addref(mbody);
 -                      message = php_http_message_init(message, type, mbody TSRMLS_CC);
 -                      if ((sval = php_http_env_get_server_var(ZEND_STRL("SERVER_PROTOCOL"), 1 TSRMLS_CC)) && !strncmp(Z_STRVAL_P(sval), "HTTP/", lenof("HTTP/"))) {
 -                              php_http_version_parse(&message->http.version, Z_STRVAL_P(sval) TSRMLS_CC);
 +                      message = php_http_message_init(message, type, mbody);
 +                      if ((sval = php_http_env_get_server_var(ZEND_STRL("SERVER_PROTOCOL"), 1)) && !strncmp(Z_STRVAL_P(sval), "HTTP/", lenof("HTTP/"))) {
 +                              php_http_version_parse(&message->http.version, Z_STRVAL_P(sval));
                        }
 -                      if ((sval = php_http_env_get_server_var(ZEND_STRL("REQUEST_METHOD"), 1 TSRMLS_CC))) {
 +                      if ((sval = php_http_env_get_server_var(ZEND_STRL("REQUEST_METHOD"), 1))) {
                                message->http.info.request.method = estrdup(Z_STRVAL_P(sval));
                        }
 -                      if ((sval = php_http_env_get_server_var(ZEND_STRL("REQUEST_URI"), 1 TSRMLS_CC))) {
 -                              message->http.info.request.url = php_http_url_parse(Z_STRVAL_P(sval), Z_STRLEN_P(sval), PHP_HTTP_URL_STDFLAGS TSRMLS_CC);
 +                      if ((sval = php_http_env_get_server_var(ZEND_STRL("REQUEST_URI"), 1))) {
-                               message->http.info.request.url = php_http_url_parse(Z_STRVAL_P(sval), Z_STRLEN_P(sval), ~0);
++                              message->http.info.request.url = php_http_url_parse(Z_STRVAL_P(sval), Z_STRLEN_P(sval), PHP_HTTP_URL_STDFLAGS);
                        }
  
 -                      php_http_env_get_request_headers(&message->hdrs TSRMLS_CC);
 +                      php_http_env_get_request_headers(&message->hdrs);
                        break;
  
                case PHP_HTTP_RESPONSE:
@@@ -577,14 -603,14 +577,14 @@@ static void php_http_message_object_pro
                RETVAL_NULL();
        }
  }
 -static void php_http_message_object_prophandler_set_request_url(php_http_message_object_t *obj, zval *value TSRMLS_DC) {
 +static void php_http_message_object_prophandler_set_request_url(php_http_message_object_t *obj, zval *value) {
        if (PHP_HTTP_MESSAGE_TYPE(REQUEST, obj->message)) {
-               PTR_SET(obj->message->http.info.request.url, php_http_url_from_zval(value, ~0));
 -              PTR_SET(obj->message->http.info.request.url, php_http_url_from_zval(value, PHP_HTTP_URL_STDFLAGS TSRMLS_CC));
++              PTR_SET(obj->message->http.info.request.url, php_http_url_from_zval(value, PHP_HTTP_URL_STDFLAGS));
        }
  }
 -static void php_http_message_object_prophandler_get_response_status(php_http_message_object_t *obj, zval *return_value TSRMLS_DC) {
 +static void php_http_message_object_prophandler_get_response_status(php_http_message_object_t *obj, zval *return_value) {
        if (PHP_HTTP_MESSAGE_TYPE(RESPONSE, obj->message) && obj->message->http.info.response.status) {
 -              RETVAL_STRING(obj->message->http.info.response.status, 1);
 +              RETVAL_STRING(obj->message->http.info.response.status);
        } else {
                RETVAL_NULL();
        }
@@@ -1256,6 -1273,38 +1256,38 @@@ static PHP_METHOD(HttpMessage, setHeade
        RETVAL_ZVAL(getThis(), 1, 0);
  }
  
 -static inline void php_http_message_object_add_header(php_http_message_object_t *obj, const char *name_str, size_t name_len, zval *zvalue TSRMLS_DC)
++static inline void php_http_message_object_add_header(php_http_message_object_t *obj, const char *name_str, size_t name_len, zval *zvalue)
+ {
+       char *name = php_http_pretty_key(estrndup(name_str, name_len), name_len, 1, 1);
 -      zval *header, *cpy;
++      zend_string *hstr, *vstr;
++      zval *header, tmp;
+       if (Z_TYPE_P(zvalue) == IS_NULL) {
+               return;
+       }
 -      cpy = php_http_header_value_to_string(zvalue TSRMLS_CC);
++      vstr = php_http_header_value_to_string(zvalue);
+       if ((name_len != lenof("Set-Cookie") && strcmp(name, "Set-Cookie"))
 -      &&      (header = php_http_message_header(obj->message, name, name_len, 1))) {
 -              zval *tmp;
++      &&      (hstr = php_http_message_header_string(obj->message, name, name_len))) {
+               char *hdr_str;
 -              size_t hdr_len = spprintf(&hdr_str, 0, "%s, %s", Z_STRVAL_P(header), Z_STRVAL_P(cpy));
 -
 -              MAKE_STD_ZVAL(tmp);
 -              ZVAL_STRINGL(tmp, hdr_str, hdr_len, 0);
 -              zend_symtable_update(&obj->message->hdrs, name, name_len + 1, &tmp, sizeof(void *), NULL);
 -              zval_ptr_dtor(&header);
 -              zval_ptr_dtor(&cpy);
 -      } else if ((header = php_http_message_header(obj->message, name, name_len, 0))) {
++              size_t hdr_len = spprintf(&hdr_str, 0, "%s, %s", hstr->val, vstr->val);
++
++              ZVAL_STR(&tmp, php_http_cs2zs(hdr_str, hdr_len));
++              zend_symtable_str_update(&obj->message->hdrs, name, name_len, &tmp);
++              zend_string_release(hstr);
++              zend_string_release(vstr);
++      } else if ((header = php_http_message_header(obj->message, name, name_len))) {
+               convert_to_array(header);
 -              zend_hash_next_index_insert(Z_ARRVAL_P(header), &cpy, sizeof(void *), NULL);
 -              zval_ptr_dtor(&header);
++              ZVAL_STR(&tmp, vstr);
++              zend_hash_next_index_insert(Z_ARRVAL_P(header), &tmp);
+       } else {
 -              zend_symtable_update(&obj->message->hdrs, name, name_len + 1, &cpy, sizeof(void *), NULL);
++              ZVAL_STR(&tmp, vstr);
++              zend_symtable_str_update(&obj->message->hdrs, name, name_len, &tmp);
+       }
+       efree(name);
+ }
  ZEND_BEGIN_ARG_INFO_EX(ai_HttpMessage_addHeader, 0, 0, 2)
        ZEND_ARG_INFO(0, header)
        ZEND_ARG_INFO(0, value)
@@@ -1264,34 -1313,14 +1296,14 @@@ static PHP_METHOD(HttpMessage, addHeade
  {
        zval *zvalue;
        char *name_str;
 -      int name_len;
 +      size_t name_len;
  
 -      if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz", &name_str, &name_len, &zvalue)) {
 -              php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
 +      if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS(), "sz", &name_str, &name_len, &zvalue)) {
 +              php_http_message_object_t *obj = PHP_HTTP_OBJ(NULL, getThis());
-               char *name = php_http_pretty_key(estrndup(name_str, name_len), name_len, 1, 1);
-               zend_string *hstr, *vstr = php_http_header_value_to_string(zvalue);
-               zval tmp, *header;
  
                PHP_HTTP_MESSAGE_OBJECT_INIT(obj);
  
-               if ((name_len != lenof("Set-Cookie") && strcmp(name, "Set-Cookie"))
-               &&      (hstr = php_http_message_header_string(obj->message, name, name_len))) {
-                       char *hdr_str;
-                       size_t hdr_len = spprintf(&hdr_str, 0, "%s, %s", hstr->val, vstr->val);
-                       ZVAL_STR(&tmp, php_http_cs2zs(hdr_str, hdr_len));
-                       zend_symtable_str_update(&obj->message->hdrs, name, name_len, &tmp);
-                       zend_string_release(hstr);
-                       zend_string_release(vstr);
-               } else if ((header = php_http_message_header(obj->message, name, name_len))) {
-                       convert_to_array(header);
-                       ZVAL_STR(&tmp, vstr);
-                       zend_hash_next_index_insert(Z_ARRVAL_P(header), &tmp);
-               } else {
-                       ZVAL_STR(&tmp, vstr);
-                       zend_symtable_str_update(&obj->message->hdrs, name, name_len, &tmp);
-               }
-               efree(name);
 -              php_http_message_object_add_header(obj, name_str, name_len, zvalue TSRMLS_CC);
++              php_http_message_object_add_header(obj, name_str, name_len, zvalue);
        }
        RETVAL_ZVAL(getThis(), 1, 0);
  }
@@@ -1310,7 -1339,19 +1322,20 @@@ static PHP_METHOD(HttpMessage, addHeade
  
                PHP_HTTP_MESSAGE_OBJECT_INIT(obj);
  
-               array_join(Z_ARRVAL_P(new_headers), &obj->message->hdrs, append, ARRAY_JOIN_STRONLY|ARRAY_JOIN_PRETTIFY);
+               if (append) {
 -                      HashPosition pos;
 -                      php_http_array_hashkey_t key = php_http_array_hashkey_init(0);
 -                      zval **val;
 -
 -                      FOREACH_KEYVAL(pos, new_headers, key, val) {
 -                              php_http_array_hashkey_stringify(&key);
 -                              php_http_message_object_add_header(obj, key.str, key.len-1, *val TSRMLS_CC);
 -                              php_http_array_hashkey_stringfree(&key);
++                      php_http_arrkey_t key = {0};
++                      zval *val;
++
++                      ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(new_headers), key.h, key.key, val)
++                      {
++                              php_http_arrkey_stringify(&key, NULL);
++                              php_http_message_object_add_header(obj, key.key->val, key.key->len, val);
++                              php_http_arrkey_dtor(&key);
+                       }
++                      ZEND_HASH_FOREACH_END();
+               } else {
+                       array_join(Z_ARRVAL_P(new_headers), &obj->message->hdrs, 0, ARRAY_JOIN_PRETTIFY|ARRAY_JOIN_STRONLY);
+               }
        }
        RETVAL_ZVAL(getThis(), 1, 0);
  }
@@@ -1629,9 -1670,9 +1654,9 @@@ static PHP_METHOD(HttpMessage, setReque
                return;
        }
  
 -      zend_replace_error_handling(EH_THROW, php_http_exception_bad_url_class_entry, &zeh TSRMLS_CC);
 -      url = php_http_url_from_zval(zurl, PHP_HTTP_URL_STDFLAGS TSRMLS_CC);
 -      zend_restore_error_handling(&zeh TSRMLS_CC);
 +      zend_replace_error_handling(EH_THROW, php_http_get_exception_bad_url_class_entry(), &zeh);
-       url = php_http_url_from_zval(zurl, ~0);
++      url = php_http_url_from_zval(zurl, PHP_HTTP_URL_STDFLAGS);
 +      zend_restore_error_handling(&zeh);
  
        if (url && php_http_url_is_empty(url)) {
                php_http_url_free(&url);
@@@ -785,10 -775,16 +785,16 @@@ static ZEND_RESULT_CODE parse_userinfo(
                switch (*ptr) {
                case ':':
                        if (password) {
-                               php_error_docref(NULL, E_WARNING,
-                                               "Failed to parse password; duplicate ':' at pos %u in '%s'",
-                                               (unsigned) (ptr - tmp), tmp);
-                               return FAILURE;
+                               if (!(state->flags & PHP_HTTP_URL_SILENT_ERRORS)) {
 -                                      php_error_docref(NULL TSRMLS_CC, E_WARNING,
++                                      php_error_docref(NULL, E_WARNING,
+                                                       "Failed to parse password; duplicate ':' at pos %u in '%s'",
+                                                       (unsigned) (ptr - tmp), tmp);
+                               }
+                               if (!(state->flags & PHP_HTTP_URL_IGNORE_ERRORS)) {
+                                       return FAILURE;
+                               }
+                               state->buffer[state->offset++] = *ptr;
+                               break;
                        }
                        password = ptr + 1;
                        state->buffer[state->offset++] = 0;
  
                case '%':
                        if (ptr[1] != '%' && (end - ptr <= 2 || !isxdigit(*(ptr+1)) || !isxdigit(*(ptr+2)))) {
-                               php_error_docref(NULL, E_WARNING,
-                                               "Failed to parse userinfo; invalid percent encoding at pos %u in '%s'",
-                                               (unsigned) (ptr - tmp), tmp);
-                               return FAILURE;
+                               if (!(state->flags & PHP_HTTP_URL_SILENT_ERRORS)) {
 -                                      php_error_docref(NULL TSRMLS_CC, E_WARNING,
++                                      php_error_docref(NULL, E_WARNING,
+                                                       "Failed to parse userinfo; invalid percent encoding at pos %u in '%s'",
+                                                       (unsigned) (ptr - tmp), tmp);
+                               }
+                               if (!(state->flags & PHP_HTTP_URL_IGNORE_ERRORS)) {
+                                       return FAILURE;
+                               }
+                               state->buffer[state->offset++] = *ptr++;
+                               break;
                        }
                        state->buffer[state->offset++] = *ptr++;
                        state->buffer[state->offset++] = *ptr++;
@@@ -898,8 -905,12 +914,12 @@@ static ZEND_RESULT_CODE parse_idn2(stru
        }
  #     endif
        if (rv != IDN2_OK) {
-               php_error_docref(NULL, E_WARNING, "Failed to parse IDN; %s", idn2_strerror(rv));
-               return FAILURE;
+               if (!(state->flags & PHP_HTTP_URL_SILENT_ERRORS)) {
 -                      php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to parse IDN; %s", idn2_strerror(rv));
++                      php_error_docref(NULL, E_WARNING, "Failed to parse IDN; %s", idn2_strerror(rv));
+               }
+               if (!(state->flags & PHP_HTTP_URL_IGNORE_ERRORS)) {
+                       return FAILURE;
+               }
        } else {
                size_t idnlen = strlen(idn);
                memcpy(state->url.host, idn, idnlen + 1);
@@@ -923,8 -935,12 +943,12 @@@ static ZEND_RESULT_CODE parse_idn(struc
        }
  #     endif
        if (rv != IDNA_SUCCESS) {
-               php_error_docref(NULL, E_WARNING, "Failed to parse IDN; %s", idna_strerror(rv));
-               return FAILURE;
+               if (!(state->flags & PHP_HTTP_URL_SILENT_ERRORS)) {
 -                      php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to parse IDN; %s", idna_strerror(rv));
++                      php_error_docref(NULL, E_WARNING, "Failed to parse IDN; %s", idna_strerror(rv));
+               }
+               if (!(state->flags & PHP_HTTP_URL_IGNORE_ERRORS)) {
+                       return FAILURE;
+               }
        } else {
                size_t idnlen = strlen(idn);
                memcpy(state->url.host, idn, idnlen + 1);
@@@ -1030,7 -1048,9 +1054,8 @@@ static ZEND_RESULT_CODE parse_widn(stru
  #ifdef HAVE_INET_PTON
  static const char *parse_ip6(struct parse_state *state, const char *ptr)
  {
+       unsigned pos = 0;
        const char *error = NULL, *end = state->ptr, *tmp = memchr(ptr, ']', end - ptr);
 -      TSRMLS_FETCH_FROM_CTX(state->ts);
  
        if (tmp) {
                size_t addrlen = tmp - ptr + 1;
        }
  
        if (error) {
-               php_error_docref(NULL, E_WARNING, "Failed to parse hostinfo; %s", error);
+               if (!(state->flags & PHP_HTTP_URL_SILENT_ERRORS)) {
 -                      php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to parse hostinfo; %s at pos %u in '%s'", error, pos, ptr);
++                      php_error_docref(NULL, E_WARNING, "Failed to parse hostinfo; %s at pos %u in '%s'", error, pos, ptr);
+               }
                return NULL;
        }
  
  
  static ZEND_RESULT_CODE parse_hostinfo(struct parse_state *state, const char *ptr)
  {
-       size_t mb, len;
+       size_t mb, len = state->offset;
        const char *end = state->ptr, *tmp = ptr, *port = NULL, *label = NULL;
 -      TSRMLS_FETCH_FROM_CTX(state->ts);
  
  #ifdef HAVE_INET_PTON
        if (*ptr == '[' && !(ptr = parse_ip6(state, ptr))) {
                switch (*ptr) {
                case ':':
                        if (port) {
-                               php_error_docref(NULL, E_WARNING,
-                                               "Failed to parse port; unexpected ':' at pos %u in '%s'",
-                                               (unsigned) (ptr - tmp), tmp);
-                               return FAILURE;
+                               if (!(state->flags & PHP_HTTP_URL_SILENT_ERRORS)) {
 -                                      php_error_docref(NULL TSRMLS_CC, E_WARNING,
++                                      php_error_docref(NULL, E_WARNING,
+                                                       "Failed to parse port; unexpected ':' at pos %u in '%s'",
+                                                       (unsigned) (ptr - tmp), tmp);
+                               }
+                               if (!(state->flags & PHP_HTTP_URL_IGNORE_ERRORS)) {
+                                       return FAILURE;
+                               }
                        }
                        port = ptr + 1;
                        break;
  
                case '%':
                        if (ptr[1] != '%' && (end - ptr <= 2 || !isxdigit(*(ptr+1)) || !isxdigit(*(ptr+2)))) {
-                               php_error_docref(NULL, E_WARNING,
-                                               "Failed to parse hostinfo; invalid percent encoding at pos %u in '%s'",
-                                               (unsigned) (ptr - tmp), tmp);
-                               return FAILURE;
+                               if (!(state->flags & PHP_HTTP_URL_SILENT_ERRORS)) {
 -                                      php_error_docref(NULL TSRMLS_CC, E_WARNING,
++                                      php_error_docref(NULL, E_WARNING,
+                                                       "Failed to parse hostinfo; invalid percent encoding at pos %u in '%s'",
+                                                       (unsigned) (ptr - tmp), tmp);
+                               }
+                               if (!(state->flags & PHP_HTTP_URL_IGNORE_ERRORS)) {
+                                       return FAILURE;
+                               }
+                               state->buffer[state->offset++] = *ptr++;
+                               break;
                        }
                        state->buffer[state->offset++] = *ptr++;
                        state->buffer[state->offset++] = *ptr++;
                                /* sort of a compromise, just ensure we don't end up
                                 * with a dot at the beginning or two consecutive dots
                                 */
-                               php_error_docref(NULL, E_WARNING,
-                                               "Failed to parse %s; unexpected '%c' at pos %u in '%s'",
-                                               port ? "port" : "host",
-                                               (unsigned char) *ptr, (unsigned) (ptr - tmp), tmp);
-                               return FAILURE;
+                               if (!(state->flags & PHP_HTTP_URL_SILENT_ERRORS)) {
 -                                      php_error_docref(NULL TSRMLS_CC, E_WARNING,
++                                      php_error_docref(NULL, E_WARNING,
+                                                       "Failed to parse %s; unexpected '%c' at pos %u in '%s'",
+                                                       port ? "port" : "host",
+                                                       (unsigned char) *ptr, (unsigned) (ptr - tmp), tmp);
+                               }
+                               if (!(state->flags & PHP_HTTP_URL_IGNORE_ERRORS)) {
+                                       return FAILURE;
+                               }
+                               break;
                        }
                        state->buffer[state->offset++] = *ptr;
                        label = NULL;
                                /* sort of a compromise, just ensure we don't end up
                                 * with a hyphen at the beginning
                                 */
-                               php_error_docref(NULL, E_WARNING,
-                                               "Failed to parse %s; unexpected '%c' at pos %u in '%s'",
-                                               port ? "port" : "host",
-                                               (unsigned char) *ptr, (unsigned) (ptr - tmp), tmp);
-                               return FAILURE;
+                               if (!(state->flags & PHP_HTTP_URL_SILENT_ERRORS)) {
 -                                      php_error_docref(NULL TSRMLS_CC, E_WARNING,
++                                      php_error_docref(NULL, E_WARNING,
+                                                       "Failed to parse %s; unexpected '%c' at pos %u in '%s'",
+                                                       port ? "port" : "host",
+                                                       (unsigned char) *ptr, (unsigned) (ptr - tmp), tmp);
+                               }
+                               if (!(state->flags & PHP_HTTP_URL_IGNORE_ERRORS)) {
+                                       return FAILURE;
+                               }
+                               break;
                        }
                        /* no break */
                case '_': case '~': /* unreserved */
                case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u':
                case 'v': case 'w': case 'x': case 'y': case 'z':
                        if (port) {
-                               php_error_docref(NULL, E_WARNING,
-                                               "Failed to parse port; unexpected char '%c' at pos %u in '%s'",
-                                               (unsigned char) *ptr, (unsigned) (ptr - tmp), tmp);
-                               return FAILURE;
+                               if (!(state->flags & PHP_HTTP_URL_SILENT_ERRORS)) {
 -                                      php_error_docref(NULL TSRMLS_CC, E_WARNING,
++                                      php_error_docref(NULL, E_WARNING,
+                                                       "Failed to parse port; unexpected char '%c' at pos %u in '%s'",
+                                                       (unsigned char) *ptr, (unsigned) (ptr - tmp), tmp);
+                               }
+                               if (!(state->flags & PHP_HTTP_URL_IGNORE_ERRORS)) {
+                                       return FAILURE;
+                               }
+                               break;
                        }
                        /* no break */
                case '0': case '1': case '2': case '3': case '4': case '5': case '6':
                        if (ptr == end) {
                                break;
                        } else if (port) {
-                               php_error_docref(NULL, E_WARNING,
-                                               "Failed to parse port; unexpected byte 0x%02x at pos %u in '%s'",
-                                               (unsigned char) *ptr, (unsigned) (ptr - tmp), tmp);
-                               return FAILURE;
-                       } else if (!(mb = parse_mb(state, PARSE_HOSTINFO, ptr, end, tmp, 0))) {
-                               return FAILURE;
+                               if (!(state->flags & PHP_HTTP_URL_SILENT_ERRORS)) {
 -                                      php_error_docref(NULL TSRMLS_CC, E_WARNING,
++                                      php_error_docref(NULL, E_WARNING,
+                                                       "Failed to parse port; unexpected byte 0x%02x at pos %u in '%s'",
+                                                       (unsigned char) *ptr, (unsigned) (ptr - tmp), tmp);
+                               }
+                               if (!(state->flags & PHP_HTTP_URL_IGNORE_ERRORS)) {
+                                       return FAILURE;
+                               }
+                               break;
+                       } else if (!(mb = parse_mb(state, PARSE_HOSTINFO, ptr, end, tmp, state->flags & PHP_HTTP_URL_SILENT_ERRORS))) {
+                               if (!(state->flags & PHP_HTTP_URL_IGNORE_ERRORS)) {
+                                       return FAILURE;
+                               }
+                               break;
                        }
                        label = ptr;
                        ptr += mb - 1;
@@@ -1204,9 -1265,15 +1269,14 @@@ static const char *parse_authority(stru
                case '@':
                        /* userinfo delimiter */
                        if (host) {
-                               php_error_docref(NULL, E_WARNING,
-                                               "Failed to parse userinfo; unexpected '@'");
-                               return NULL;
+                               if (!(state->flags & PHP_HTTP_URL_SILENT_ERRORS)) {
 -                                      TSRMLS_FETCH_FROM_CTX(state->ts);
 -                                      php_error_docref(NULL TSRMLS_CC, E_WARNING,
++                                      php_error_docref(NULL, E_WARNING,
+                                                       "Failed to parse userinfo; unexpected '@'");
+                               }
+                               if (!(state->flags & PHP_HTTP_URL_IGNORE_ERRORS)) {
+                                       return NULL;
+                               }
+                               break;
                        }
                        host = state->ptr + 1;
                        if (tmp != state->ptr && SUCCESS != parse_userinfo(state, tmp)) {
@@@ -1252,10 -1320,16 +1322,16 @@@ static const char *parse_path(struct pa
  
                case '%':
                        if (state->ptr[1] != '%' && (state->end - state->ptr <= 2 || !isxdigit(*(state->ptr+1)) || !isxdigit(*(state->ptr+2)))) {
-                               php_error_docref(NULL, E_WARNING,
-                                               "Failed to parse path; invalid percent encoding at pos %u in '%s'",
-                                               (unsigned) (state->ptr - tmp), tmp);
-                               return NULL;
+                               if (!(state->flags & PHP_HTTP_URL_SILENT_ERRORS)) {
 -                                      php_error_docref(NULL TSRMLS_CC, E_WARNING,
++                                      php_error_docref(NULL, E_WARNING,
+                                                       "Failed to parse path; invalid percent encoding at pos %u in '%s'",
+                                                       (unsigned) (state->ptr - tmp), tmp);
+                               }
+                               if (!(state->flags & PHP_HTTP_URL_IGNORE_ERRORS)) {
+                                       return NULL;
+                               }
+                               state->buffer[state->offset++] = *state->ptr;
+                               break;
                        }
                        state->buffer[state->offset++] = *state->ptr++;
                        state->buffer[state->offset++] = *state->ptr++;
@@@ -1320,17 -1398,22 +1399,22 @@@ static const char *parse_query(struct p
  
                case '%':
                        if (state->ptr[1] != '%' && (state->end - state->ptr <= 2 || !isxdigit(*(state->ptr+1)) || !isxdigit(*(state->ptr+2)))) {
-                               php_error_docref(NULL, E_WARNING,
-                                               "Failed to parse query; invalid percent encoding at pos %u in '%s'",
-                                               (unsigned) (state->ptr - tmp), tmp);
-                               return NULL;
+                               if (!(state->flags & PHP_HTTP_URL_SILENT_ERRORS)) {
 -                                      php_error_docref(NULL TSRMLS_CC, E_WARNING,
++                                      php_error_docref(NULL, E_WARNING,
+                                                       "Failed to parse query; invalid percent encoding at pos %u in '%s'",
+                                                       (unsigned) (state->ptr - tmp), tmp);
+                               }
+                               if (!(state->flags & PHP_HTTP_URL_IGNORE_ERRORS)) {
+                                       return NULL;
+                               }
+                               /* fallthrough, pct-encode the percent sign */
+                       } else {
+                               state->buffer[state->offset++] = *state->ptr++;
+                               state->buffer[state->offset++] = *state->ptr++;
+                               state->buffer[state->offset++] = *state->ptr;
+                               break;
                        }
-                       state->buffer[state->offset++] = *state->ptr++;
-                       state->buffer[state->offset++] = *state->ptr++;
-                       state->buffer[state->offset++] = *state->ptr;
-                       break;
-               /* RFC1738 unsafe */
+                       /* no break */
                case '{': case '}':
                case '<': case '>':
                case '[': case ']':
@@@ -1393,19 -1481,37 +1481,37 @@@ static const char *parse_fragment(struc
  
        do {
                switch (*state->ptr) {
-               case '%':
-                       if (state->ptr[1] != '%' && (state->end - state->ptr <= 2 || !isxdigit(*(state->ptr+1)) || !isxdigit(*(state->ptr+2)))) {
+               case '#':
+                       if (!(state->flags & PHP_HTTP_URL_SILENT_ERRORS)) {
 -                              php_error_docref(NULL TSRMLS_CC, E_WARNING,
 +                              php_error_docref(NULL, E_WARNING,
-                                               "Failed to parse fragment; invalid percent encoding at pos %u in '%s'",
+                                               "Failed to parse fragment; invalid fragment identifier at pos %u in '%s'",
                                                (unsigned) (state->ptr - tmp), tmp);
+                       }
+                       if (!(state->flags & PHP_HTTP_URL_IGNORE_ERRORS)) {
                                return NULL;
                        }
-                       state->buffer[state->offset++] = *state->ptr++;
-                       state->buffer[state->offset++] = *state->ptr++;
                        state->buffer[state->offset++] = *state->ptr;
                        break;
  
-               /* RFC1738 unsafe */
+               case '%':
+                       if (state->ptr[1] != '%' && (state->end - state->ptr <= 2 || !isxdigit(*(state->ptr+1)) || !isxdigit(*(state->ptr+2)))) {
+                               if (!(state->flags & PHP_HTTP_URL_SILENT_ERRORS)) {
 -                                      php_error_docref(NULL TSRMLS_CC, E_WARNING,
++                                      php_error_docref(NULL, E_WARNING,
+                                                       "Failed to parse fragment; invalid percent encoding at pos %u in '%s'",
+                                                       (unsigned) (state->ptr - tmp), tmp);
+                               }
+                               if (!(state->flags & PHP_HTTP_URL_IGNORE_ERRORS)) {
+                                       return NULL;
+                               }
+                               /* fallthrough */
+                       } else {
+                               state->buffer[state->offset++] = *state->ptr++;
+                               state->buffer[state->offset++] = *state->ptr++;
+                               state->buffer[state->offset++] = *state->ptr;
+                               break;
+                       }
+                       /* no break */
                case '{': case '}':
                case '<': case '>':
                case '[': case ']':
@@@ -1561,11 -1673,15 +1671,15 @@@ php_http_url_t *php_http_url_parse_auth
        }
  
        if (state->ptr != state->end) {
-               php_error_docref(NULL, E_WARNING,
-                               "Failed to parse URL authority, unexpected character at pos %u in '%s'",
-                               (unsigned) (state->ptr - str), str);
-               efree(state);
-               return NULL;
+               if (!(state->flags & PHP_HTTP_URL_SILENT_ERRORS)) {
 -                      php_error_docref(NULL TSRMLS_CC, E_WARNING,
++                      php_error_docref(NULL, E_WARNING,
+                                       "Failed to parse URL authority, unexpected character at pos %u in '%s'",
+                                       (unsigned) (state->ptr - str), str);
+               }
+               if (!(state->flags & PHP_HTTP_URL_IGNORE_ERRORS)) {
+                       efree(state);
+                       return NULL;
+               }
        }
  
        return (php_http_url_t *) state;
@@@ -1592,17 -1695,18 +1706,23 @@@ ZEND_END_ARG_INFO()
  PHP_METHOD(HttpUrl, __construct)
  {
        zval *new_url = NULL, *old_url = NULL;
 -      long flags = PHP_HTTP_URL_FROM_ENV;
 +      zend_long flags = 0;
        zend_error_handling zeh;
  
 -      php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|z!z!l", &old_url, &new_url, &flags), invalid_arg, return);
 +      php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS(), "|z!z!l", &old_url, &new_url, &flags), invalid_arg, return);
 +
 +      /* always set http\Url::FROM_ENV for instances of http\Env\Url */
 +      if (instanceof_function(Z_OBJCE_P(getThis()), php_http_env_url_class_entry)) {
 +              flags |= PHP_HTTP_URL_FROM_ENV;
 +      }
  
-       zend_replace_error_handling(EH_THROW, php_http_get_exception_bad_url_class_entry(), &zeh);
+       if (flags & PHP_HTTP_URL_SILENT_ERRORS) {
 -              zend_replace_error_handling(EH_SUPPRESS, NULL, &zeh TSRMLS_CC);
++              zend_replace_error_handling(EH_SUPPRESS, NULL, &zeh);
+       } else if (flags & PHP_HTTP_URL_IGNORE_ERRORS) {
 -              zend_replace_error_handling(EH_NORMAL, NULL, &zeh TSRMLS_CC);
++              zend_replace_error_handling(EH_NORMAL, NULL, &zeh);
+       } else {
 -              zend_replace_error_handling(EH_THROW, php_http_exception_bad_url_class_entry, &zeh TSRMLS_CC);
++              zend_replace_error_handling(EH_THROW, php_http_get_exception_bad_url_class_entry(), &zeh);
+       }
        {
                php_http_url_t *res_purl, *new_purl = NULL, *old_purl = NULL;
  
@@@ -1645,12 -1749,18 +1765,18 @@@ ZEND_END_ARG_INFO()
  PHP_METHOD(HttpUrl, mod)
  {
        zval *new_url = NULL;
 -      long flags = PHP_HTTP_URL_JOIN_PATH | PHP_HTTP_URL_JOIN_QUERY | PHP_HTTP_URL_SANITIZE_PATH;
 +      zend_long flags = PHP_HTTP_URL_JOIN_PATH | PHP_HTTP_URL_JOIN_QUERY | PHP_HTTP_URL_SANITIZE_PATH;
        zend_error_handling zeh;
  
 -      php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z!|l", &new_url, &flags), invalid_arg, return);
 +      php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS(), "z!|l", &new_url, &flags), invalid_arg, return);
  
-       zend_replace_error_handling(EH_THROW, php_http_get_exception_bad_url_class_entry(), &zeh);
+       if (flags & PHP_HTTP_URL_SILENT_ERRORS) {
 -              zend_replace_error_handling(EH_SUPPRESS, NULL, &zeh TSRMLS_CC);
++              zend_replace_error_handling(EH_SUPPRESS, NULL, &zeh);
+       } else if (flags & PHP_HTTP_URL_IGNORE_ERRORS) {
 -              zend_replace_error_handling(EH_NORMAL, NULL, &zeh TSRMLS_CC);
++              zend_replace_error_handling(EH_NORMAL, NULL, &zeh);
+       } else {
 -              zend_replace_error_handling(EH_THROW, php_http_exception_bad_url_class_entry, &zeh TSRMLS_CC);
++              zend_replace_error_handling(EH_THROW, php_http_get_exception_bad_url_class_entry(), &zeh);
+       }
        {
                php_http_url_t *new_purl = NULL, *old_purl = NULL;
  
@@@ -1729,43 -1841,45 +1855,48 @@@ PHP_MINIT_FUNCTION(http_url
        zend_class_entry ce = {0};
  
        INIT_NS_CLASS_ENTRY(ce, "http", "Url", php_http_url_methods);
 -      php_http_url_class_entry = zend_register_internal_class(&ce TSRMLS_CC);
 -
 -      zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("scheme"), ZEND_ACC_PUBLIC TSRMLS_CC);
 -      zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("user"), ZEND_ACC_PUBLIC TSRMLS_CC);
 -      zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("pass"), ZEND_ACC_PUBLIC TSRMLS_CC);
 -      zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("host"), ZEND_ACC_PUBLIC TSRMLS_CC);
 -      zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("port"), ZEND_ACC_PUBLIC TSRMLS_CC);
 -      zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("path"), ZEND_ACC_PUBLIC TSRMLS_CC);
 -      zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("query"), ZEND_ACC_PUBLIC TSRMLS_CC);
 -      zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("fragment"), ZEND_ACC_PUBLIC TSRMLS_CC);
 -
 -      zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("REPLACE"), PHP_HTTP_URL_REPLACE TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("JOIN_PATH"), PHP_HTTP_URL_JOIN_PATH TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("JOIN_QUERY"), PHP_HTTP_URL_JOIN_QUERY TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_USER"), PHP_HTTP_URL_STRIP_USER TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_PASS"), PHP_HTTP_URL_STRIP_PASS TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_AUTH"), PHP_HTTP_URL_STRIP_AUTH TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_PORT"), PHP_HTTP_URL_STRIP_PORT TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_PATH"), PHP_HTTP_URL_STRIP_PATH TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_QUERY"), PHP_HTTP_URL_STRIP_QUERY TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_FRAGMENT"), PHP_HTTP_URL_STRIP_FRAGMENT TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_ALL"), PHP_HTTP_URL_STRIP_ALL TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("FROM_ENV"), PHP_HTTP_URL_FROM_ENV TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("SANITIZE_PATH"), PHP_HTTP_URL_SANITIZE_PATH TSRMLS_CC);
 +      php_http_url_class_entry = zend_register_internal_class(&ce);
 +
 +      zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("scheme"), ZEND_ACC_PUBLIC);
 +      zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("user"), ZEND_ACC_PUBLIC);
 +      zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("pass"), ZEND_ACC_PUBLIC);
 +      zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("host"), ZEND_ACC_PUBLIC);
 +      zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("port"), ZEND_ACC_PUBLIC);
 +      zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("path"), ZEND_ACC_PUBLIC);
 +      zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("query"), ZEND_ACC_PUBLIC);
 +      zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("fragment"), ZEND_ACC_PUBLIC);
 +
 +      zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("REPLACE"), PHP_HTTP_URL_REPLACE);
 +      zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("JOIN_PATH"), PHP_HTTP_URL_JOIN_PATH);
 +      zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("JOIN_QUERY"), PHP_HTTP_URL_JOIN_QUERY);
 +      zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_USER"), PHP_HTTP_URL_STRIP_USER);
 +      zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_PASS"), PHP_HTTP_URL_STRIP_PASS);
 +      zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_AUTH"), PHP_HTTP_URL_STRIP_AUTH);
 +      zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_PORT"), PHP_HTTP_URL_STRIP_PORT);
 +      zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_PATH"), PHP_HTTP_URL_STRIP_PATH);
 +      zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_QUERY"), PHP_HTTP_URL_STRIP_QUERY);
 +      zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_FRAGMENT"), PHP_HTTP_URL_STRIP_FRAGMENT);
 +      zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_ALL"), PHP_HTTP_URL_STRIP_ALL);
 +      zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("FROM_ENV"), PHP_HTTP_URL_FROM_ENV);
 +      zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("SANITIZE_PATH"), PHP_HTTP_URL_SANITIZE_PATH);
  
  #ifdef PHP_HTTP_HAVE_WCHAR
 -      zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("PARSE_MBLOC"), PHP_HTTP_URL_PARSE_MBLOC TSRMLS_CC);
 +      zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("PARSE_MBLOC"), PHP_HTTP_URL_PARSE_MBLOC);
  #endif
 -      zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("PARSE_MBUTF8"), PHP_HTTP_URL_PARSE_MBUTF8 TSRMLS_CC);
 +      zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("PARSE_MBUTF8"), PHP_HTTP_URL_PARSE_MBUTF8);
  #if defined(PHP_HTTP_HAVE_IDN2) || defined(PHP_HTTP_HAVE_IDN) || defined(HAVE_UIDNA_IDNTOASCII)
 -      zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("PARSE_TOIDN"), PHP_HTTP_URL_PARSE_TOIDN TSRMLS_CC);
 +      zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("PARSE_TOIDN"), PHP_HTTP_URL_PARSE_TOIDN);
  #endif
 -      zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("PARSE_TOPCT"), PHP_HTTP_URL_PARSE_TOPCT TSRMLS_CC);
 +      zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("PARSE_TOPCT"), PHP_HTTP_URL_PARSE_TOPCT);
 +
 +      INIT_NS_CLASS_ENTRY(ce, "http\\Env", "Url", php_http_url_methods);
 +      php_http_env_url_class_entry = zend_register_internal_class_ex(&ce, php_http_url_class_entry);
  
 -      zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("IGNORE_ERRORS"), PHP_HTTP_URL_IGNORE_ERRORS TSRMLS_CC);
 -      zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("SILENT_ERRORS"), PHP_HTTP_URL_SILENT_ERRORS TSRMLS_CC);
++      zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("IGNORE_ERRORS"), PHP_HTTP_URL_IGNORE_ERRORS);
++      zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("SILENT_ERRORS"), PHP_HTTP_URL_SILENT_ERRORS);
 -      zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STDFLAGS"), PHP_HTTP_URL_STDFLAGS TSRMLS_CC);
++      zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STDFLAGS"), PHP_HTTP_URL_STDFLAGS);
        return SUCCESS;
  }
  
Simple merge
diff --cc tests/gh-issue7.phpt
index 2c3f50d,38e597c..0000000
deleted file mode 100644,100644
+++ /dev/null
@@@ -1,37 -1,32 +1,0 @@@
----TEST--
--crash with querystring and exception from error handler
----SKIPIF--
- <?php
- include "skipif.inc";
- if (version_compare(PHP_VERSION, "7", ">=")) {
-       die("skip PHP>=7\n");
- }
- ?>
 -<?php include "skipif.inc"; ?>
----GET--
--q[]=1&r[]=2
----FILE--
--<?php 
--echo "Test\n";
--
--set_error_handler(function($c,$e) { throw new Exception($e); });
--
--try {
--      $q = http\QueryString::getGlobalInstance();
--      var_dump($q->get("q","s"));
--} catch (\Exception $e) {
--      echo $e->getMessage(),"\n";
--}
--try {
--      $r = new http\Env\Request;
--      var_dump($r->getQuery("r", "s"));
--} catch (\Exception $e) {
--      echo $e->getMessage(),"\n";
--}
--
--?>
--===DONE===
----EXPECT--
--Test
--Array to string conversion
--Array to string conversion
--===DONE===