From: Michael Wallner Date: Fri, 12 Jun 2015 08:30:13 +0000 (+0200) Subject: Merge branch 'master' into phpng X-Git-Tag: RELEASE_3_0_0_RC1~50 X-Git-Url: https://git.m6w6.name/?p=m6w6%2Fext-http;a=commitdiff_plain;h=468e8d748d365811af4ce890fd8fc4c1f88cc08a;hp=af63b51f8172e530c3fd9d780ceff76918f0ebd3 Merge branch 'master' into phpng --- diff --git a/.gitignore b/.gitignore index 93781ff..c78e2b8 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,4 @@ tests/*.php tests/*.sh lcov_data *~ +*.phar diff --git a/check_package-xml.php b/check_package-xml.php index 4000054..32a1e73 100755 --- a/check_package-xml.php +++ b/check_package-xml.php @@ -36,6 +36,11 @@ if (($xml = simplexml_load_file($file))) { } } } + foreach ($xml_files as $file) { + if (!file_exists($file)) { + echo "Extraneous file $file\n"; + } + } } ### diff --git a/config9.m4 b/config9.m4 index bd5ba2f..b82a3a7 100644 --- a/config9.m4 +++ b/config9.m4 @@ -11,7 +11,7 @@ PHP_ARG_WITH([http-libcurl-dir], [], PHP_ARG_WITH([http-libevent-dir], [], [ --with-http-libevent-dir[=DIR] HTTP: where to find libevent], $PHP_HTTP_LIBCURL_DIR, "") PHP_ARG_WITH([http-libidn-dir], [], -[ --with-http-libidn-dir=[=DIR] HTTP: where to find libidn], $PHP_HTTP_LIBCURL_DIR, "") +[ --with-http-libidn-dir[=DIR] HTTP: where to find libidn], $PHP_HTTP_LIBCURL_DIR, "") if test "$PHP_HTTP" != "no"; then @@ -120,18 +120,70 @@ dnl ---- break; fi done - if test "x$IDNA_DIR" = "x"; then - AC_MSG_RESULT([not found]) - case $host_os in - darwin*) - AC_CHECK_HEADERS(unicode/uidna.h) - PHP_CHECK_FUNC(uidna_IDNToASCII, icucore);; - esac - else + if test "x$IDNA_DIR" != "x"; then AC_MSG_RESULT([found in $IDNA_DIR]) AC_DEFINE([PHP_HTTP_HAVE_IDN], [1], [Have libidn support]) PHP_ADD_INCLUDE($IDNA_DIR/include) PHP_ADD_LIBRARY_WITH_PATH(idn, $IDNA_DIR/$PHP_LIBDIR, HTTP_SHARED_LIBADD) + AC_MSG_CHECKING([for libidn version]) + IDNA_VER=$(pkg-config --version libidn 2>/dev/null || echo unknown) + AC_MSG_RESULT([$IDNA_VER]) + AC_DEFINE_UNQUOTED([PHP_HTTP_LIBIDN_VERSION], "$IDNA_VER", [ ]) + else + AC_MSG_RESULT([not found]) + AC_MSG_CHECKING([for idn2.h]) + IDNA_DIR= + for i in "$PHP_HTTP_LIBIDN_DIR" "$IDN_DIR" /usr/local /usr /opt; do + if test -f "$i/include/idn2.h"; then + IDNA_DIR=$i + break; + fi + done + if test "x$IDNA_DIR" != "x"; then + AC_MSG_RESULT([found in $IDNA_DIR]) + AC_DEFINE([PHP_HTTP_HAVE_IDN2], [1], [Have libidn2 support]) + PHP_ADD_INCLUDE($IDNA_DIR/include) + PHP_ADD_LIBRARY_WITH_PATH(idn2, $IDNA_DIR/$PHP_LIBDIR, HTTP_SHARED_LIBADD) + AC_MSG_CHECKING([for libidn2 version]) + IDNA_VER=`$EGREP "define IDN2_VERSION " $IDNA_DIR/include/idn2.h | $SED -e's/^.*VERSION //g' -e 's/[[^0-9\.]]//g'` + AC_MSG_RESULT([$IDNA_VER]) + AC_DEFINE_UNQUOTED([PHP_HTTP_LIBIDN2_VERSION], "$IDNA_VER", [ ]) + else + AC_MSG_RESULT([not found]) + AC_CHECK_HEADERS([unicode/uidna.h]) + case $host_os in + darwin*) + PHP_CHECK_FUNC(uidna_IDNToASCII, icucore);; + *) + AC_PATH_PROG(ICU_CONFIG, icu-config, no, [$PATH:/usr/local/bin]) + if test ! -x "$ICU_CONFIG"; then + ICU_CONFIG="icu-config" + fi + AC_MSG_CHECKING([for uidna_IDNToASCII]) + if ! test -x "$ICU_CONFIG"; then + ICU_CONFIG=icu-config + fi + if $ICU_CONFIG --exists 2>/dev/null >&2; then + save_LIBS=$LIBS + LIBS=$($ICU_CONFIG --ldflags) + AC_TRY_RUN([ + #include + int main(int argc, char *argv[]) { + return uidna_IDNToASCII(0, 0, 0, 0, 0, 0, 0); + } + ], [ + AC_MSG_RESULT([yes]) + AC_DEFINE([HAVE_UIDNA_IDNTOASCII], [1], [ ]) + LIBS=$save_LIBS + PHP_EVAL_LIBLINE(`$ICU_CONFIG --ldflags`, HTTP_SHARED_LIBADD) + ], [ + LIBS=$save_LIBS + AC_MSG_RESULT([no]) + ]) + fi + ;; + esac + fi fi dnl ---- diff --git a/php_http.c b/php_http.c index d430e1f..6d4384d 100644 --- a/php_http.c +++ b/php_http.c @@ -28,8 +28,10 @@ # endif # endif #endif -#if PHP_HTTP_HAVE_SERF -# include +#if PHP_HTTP_HAVE_IDN2 +# include +#elif PHP_HTTP_HAVE_IDN +# include #endif ZEND_DECLARE_MODULE_GLOBALS(php_http); @@ -220,6 +222,12 @@ PHP_MINFO_FUNCTION(http) php_info_print_table_row(3, "libevent", "disabled", "disabled"); #endif +#if PHP_HTTP_HAVE_IDN2 + php_info_print_table_row(3, "libidn2 (IDNA2008)", IDN2_VERSION, idn2_check_version(NULL)); +#elif PHP_HTTP_HAVE_IDN + php_info_print_table_row(3, "libidn (IDNA2003)", PHP_HTTP_LIBIDN_VERSION, "unknown"); +#endif + php_info_print_table_end(); DISPLAY_INI_ENTRIES(); diff --git a/php_http_client_curl.c b/php_http_client_curl.c index ac464ab..9b44aa0 100644 --- a/php_http_client_curl.c +++ b/php_http_client_curl.c @@ -611,7 +611,14 @@ static php_http_message_t *php_http_curlm_responseparser(php_http_client_curl_ha response = php_http_message_init(NULL, 0, h->response.body TSRMLS_CC); php_http_header_parser_init(&parser TSRMLS_CC); - php_http_header_parser_parse(&parser, &h->response.headers, PHP_HTTP_HEADER_PARSER_CLEANUP, &response->hdrs, (php_http_info_callback_t) php_http_message_info_callback, (void *) &response); + while (h->response.headers.used) { + php_http_header_parser_state_t st = php_http_header_parser_parse(&parser, + &h->response.headers, PHP_HTTP_HEADER_PARSER_CLEANUP, &response->hdrs, + (php_http_info_callback_t) php_http_message_info_callback, (void *) &response); + if (PHP_HTTP_HEADER_PARSER_STATE_FAILURE == st) { + break; + } + } php_http_header_parser_dtor(&parser); /* move body to right message */ @@ -621,6 +628,7 @@ static php_http_message_t *php_http_curlm_responseparser(php_http_client_curl_ha while (ptr->parent) { ptr = ptr->parent; } + php_http_message_body_free(&response->body); response->body = ptr->body; ptr->body = NULL; } @@ -653,7 +661,8 @@ static php_http_message_t *php_http_curlm_responseparser(php_http_client_curl_ha static void php_http_curlm_responsehandler(php_http_client_t *context) { - int remaining = 0; + int err_count = 0, remaining = 0; + php_http_curle_storage_t *st, *err = NULL; php_http_client_enqueue_t *enqueue; php_http_client_curl_t *curl = context->ctx; @@ -662,8 +671,18 @@ static void php_http_curlm_responsehandler(php_http_client_t *context) if (msg && CURLMSG_DONE == msg->msg) { if (CURLE_OK != msg->data.result) { - php_http_curle_storage_t *st = php_http_curle_get_storage(msg->easy_handle); - php_error_docref(NULL, E_WARNING, "%s; %s (%s)", curl_easy_strerror(st->errorcode = msg->data.result), STR_PTR(st->errorbuffer), STR_PTR(st->url)); + st = php_http_curle_get_storage(msg->easy_handle); + st->errorcode = msg->data.result; + + /* defer the warnings/exceptions, so the callback is still called for this request */ + if (!err) { + err = ecalloc(remaining + 1, sizeof(*err)); + } + memcpy(&err[err_count], st, sizeof(*st)); + if (st->url) { + err[err_count].url = estrdup(st->url); + } + err_count++; } if ((enqueue = php_http_client_enqueued(context, msg->easy_handle, compare_queue))) { @@ -677,6 +696,19 @@ static void php_http_curlm_responsehandler(php_http_client_t *context) } } } while (remaining); + + if (err_count) { + int i = 0; + + do { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s; %s (%s)", curl_easy_strerror(err[i].errorcode), err[i].errorbuffer, STR_PTR(err[i].url)); + if (err[i].url) { + efree(err[i].url); + } + } while (++i < err_count); + + efree(err); + } } #if PHP_HTTP_HAVE_EVENT @@ -1910,35 +1942,6 @@ static ZEND_RESULT_CODE php_http_client_curl_handler_prepare(php_http_client_cur php_http_url_to_string(PHP_HTTP_INFO(msg).request.url, &storage->url, NULL, 1); curl_easy_setopt(curl->handle, CURLOPT_URL, storage->url); - /* request method */ - switch (php_http_select_str(PHP_HTTP_INFO(msg).request.method, 4, "GET", "HEAD", "POST", "PUT")) { - case 0: - curl_easy_setopt(curl->handle, CURLOPT_HTTPGET, 1L); - break; - - case 1: - curl_easy_setopt(curl->handle, CURLOPT_NOBODY, 1L); - break; - - case 2: - curl_easy_setopt(curl->handle, CURLOPT_POST, 1L); - break; - - case 3: - curl_easy_setopt(curl->handle, CURLOPT_UPLOAD, 1L); - break; - - default: { - if (PHP_HTTP_INFO(msg).request.method) { - curl_easy_setopt(curl->handle, CURLOPT_CUSTOMREQUEST, PHP_HTTP_INFO(msg).request.method); - } else { - php_error_docref(NULL, E_WARNING, "Cannot use empty request method"); - return FAILURE; - } - break; - } - } - /* apply options */ php_http_options_apply(&php_http_curle_options, enqueue->options, curl); @@ -1989,6 +1992,7 @@ static ZEND_RESULT_CODE php_http_client_curl_handler_prepare(php_http_client_cur curl_easy_setopt(curl->handle, CURLOPT_READDATA, msg->body); curl_easy_setopt(curl->handle, CURLOPT_INFILESIZE, body_size); curl_easy_setopt(curl->handle, CURLOPT_POSTFIELDSIZE, body_size); + curl_easy_setopt(curl->handle, CURLOPT_POST, 1L); } else { curl_easy_setopt(curl->handle, CURLOPT_SEEKDATA, NULL); curl_easy_setopt(curl->handle, CURLOPT_READDATA, NULL); @@ -1996,6 +2000,29 @@ static ZEND_RESULT_CODE php_http_client_curl_handler_prepare(php_http_client_cur curl_easy_setopt(curl->handle, CURLOPT_POSTFIELDSIZE, 0L); } + /* + * Always use CUSTOMREQUEST, else curl won't send any request body for GET etc. + * See e.g. bug #69313. + * + * Here's what curl does: + * - CURLOPT_HTTPGET: ignore request body + * - CURLOPT_UPLOAD: set "Expect: 100-continue" header + * - CURLOPT_POST: set "Content-Type: application/x-www-form-urlencoded" header + * Now select the least bad. + * + * See also https://tools.ietf.org/html/rfc7231#section-5.1.1 + */ + if (PHP_HTTP_INFO(msg).request.method) { + if (!strcasecmp("PUT", PHP_HTTP_INFO(msg).request.method)) { + curl_easy_setopt(curl->handle, CURLOPT_UPLOAD, 1L); + } else { + curl_easy_setopt(curl->handle, CURLOPT_CUSTOMREQUEST, PHP_HTTP_INFO(msg).request.method); + } + } else { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot use empty request method"); + return FAILURE; + } + return SUCCESS; } @@ -2309,11 +2336,11 @@ static ZEND_RESULT_CODE php_http_client_curl_exec(php_http_client_t *h) php_error_docref(NULL, E_ERROR, "Error in event_base_dispatch()"); return FAILURE; } - } while (curl->unfinished); + } while (curl->unfinished && !EG(exception)); } else #endif { - while (php_http_client_curl_once(h)) { + while (php_http_client_curl_once(h) && !EG(exception)) { if (SUCCESS != php_http_client_curl_wait(h, NULL)) { #ifdef PHP_WIN32 /* see http://msdn.microsoft.com/library/en-us/winsock/winsock/windows_sockets_error_codes_2.asp */ diff --git a/php_http_env_response.c b/php_http_env_response.c index 5b46820..557bb10 100644 --- a/php_http_env_response.c +++ b/php_http_env_response.c @@ -207,7 +207,9 @@ php_http_cache_status_t php_http_env_is_response_cached_by_last_modified(zval *o static zend_bool php_http_env_response_is_cacheable(php_http_env_response_t *r, php_http_message_t *request) { - if (r->ops->get_status(r) >= 400) { + long status = r->ops->get_status(r); + + if (status && status / 100 != 2) { return 0; } @@ -1157,7 +1159,12 @@ static PHP_METHOD(HttpEnvResponse, __invoke) PHP_HTTP_ENV_RESPONSE_OBJECT_INIT(obj); php_http_message_object_init_body_object(obj); - php_http_message_body_append(obj->message->body, ob_str, ob_len); + + if (ob_flags & PHP_OUTPUT_HANDLER_CLEAN) { + php_stream_truncate_set_size(php_http_message_body_stream(obj->message->body), 0); + } else { + php_http_message_body_append(obj->message->body, ob_str, ob_len); + } RETURN_TRUE; } } diff --git a/php_http_info.h b/php_http_info.h index b771c5c..8a52ee2 100644 --- a/php_http_info.h +++ b/php_http_info.h @@ -41,6 +41,8 @@ typedef struct php_http_info_data { php_http_version_t version; } php_http_info_data_t; +#undef PHP_HTTP_REQUEST +#undef PHP_HTTP_RESPONSE typedef enum php_http_info_type { PHP_HTTP_NONE = 0, PHP_HTTP_REQUEST, diff --git a/php_http_message_parser.c b/php_http_message_parser.c index 7bf9a16..92c8e40 100644 --- a/php_http_message_parser.c +++ b/php_http_message_parser.c @@ -59,19 +59,21 @@ php_http_message_parser_t *php_http_message_parser_init(php_http_message_parser_ php_http_message_parser_state_t php_http_message_parser_state_push(php_http_message_parser_t *parser, unsigned argc, ...) { - php_http_message_parser_state_t state; + php_http_message_parser_state_t state = PHP_HTTP_MESSAGE_PARSER_STATE_FAILURE; va_list va_args; unsigned i; - /* short circuit */ - ZEND_PTR_STACK_RESIZE_IF_NEEDED((&parser->stack), argc); + if (argc > 0) { + /* short circuit */ + ZEND_PTR_STACK_RESIZE_IF_NEEDED((&parser->stack), argc); - va_start(va_args, argc); - for (i = 0; i < argc; ++i) { - state = va_arg(va_args, php_http_message_parser_state_t); - zend_ptr_stack_push(&parser->stack, (void *) state); + va_start(va_args, argc); + for (i = 0; i < argc; ++i) { + state = va_arg(va_args, php_http_message_parser_state_t); + zend_ptr_stack_push(&parser->stack, (void *) state); + } + va_end(va_args); } - va_end(va_args); return state; } diff --git a/php_http_misc.h b/php_http_misc.h index 421c991..36f4020 100644 --- a/php_http_misc.h +++ b/php_http_misc.h @@ -139,6 +139,11 @@ static inline const char *php_http_locate_bin_eol(const char *bin, size_t len, i /* ZEND */ +#ifdef PHP_DEBUG +# undef HASH_OF +# define HASH_OF(p) ((HashTable*)(Z_TYPE_P(p)==IS_ARRAY ? Z_ARRVAL_P(p) : ((Z_TYPE_P(p)==IS_OBJECT ? Z_OBJ_HT_P(p)->get_properties((p)) : NULL)))) +#endif + static inline void *PHP_HTTP_OBJ(zend_object *zo, zval *zv) { if (!zo) { diff --git a/php_http_params.c b/php_http_params.c index 6e5dd4a..ced2507 100644 --- a/php_http_params.c +++ b/php_http_params.c @@ -63,9 +63,27 @@ static inline void sanitize_escaped(zval *zv) php_stripcslashes(Z_STR_P(zv)); } -static inline void prepare_escaped(zval *zv) +static inline void quote_string(zend_string **zs, zend_bool force) { - if (Z_TYPE_P(zv) == IS_STRING) { + int len = (*zs)->len; + + *zs = php_addcslashes(*zs, 1, ZEND_STRL("\0..\37\173\\\"")); + + if (force || len != (*zs)->len || strpbrk((*zs)->val, "()<>@,;:\"[]?={} ")) { + int len = (*zs)->len + 2; + + *zs = zend_string_extend(*zs, 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); + } +} + +/* 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\\\"")); @@ -86,6 +104,12 @@ static inline void prepare_escaped(zval *zv) zval_dtor(zv); ZVAL_STR(zv, stripped); } +*/ + +static inline void prepare_escaped(zval *zv) +{ + if (Z_TYPE_P(zv) == IS_STRING) { + quote_string(&Z_STR_P(zv), 0); } else { zval_dtor(zv); ZVAL_EMPTY_STRING(zv); @@ -291,6 +315,23 @@ static inline void sanitize_rfc5987(zval *zv, char **language, zend_bool *latin1 } } +static inline void sanitize_rfc5988(char *str, size_t len, zval *zv TSRMLS_DC) +{ + zend_string *zs = zend_string_init(str, len, 0); + + zval_dtor(zv); + ZVAL_STR(zv, php_trim(zs, " ><", 3, 3)); + zend_string_release(zs); +} + +static inline void prepare_rfc5988(zval *zv TSRMLS_DC) +{ + if (Z_TYPE_P(zv) != IS_STRING) { + zval_dtor(zv); + ZVAL_EMPTY_STRING(zv); + } +} + static void utf8encode(zval *zv) { size_t pos, len = 0; @@ -363,7 +404,11 @@ static inline void prepare_key(unsigned flags, char *old_key, size_t old_len, ch } if (flags & PHP_HTTP_PARAMS_ESCAPED) { - prepare_escaped(&zv); + if (flags & PHP_HTTP_PARAMS_RFC5988) { + prepare_rfc5988(&zv); + } else { + prepare_escaped(&zv); + } } *new_key = estrndup(Z_STRVAL(zv), Z_STRLEN(zv)); @@ -544,12 +589,16 @@ static void push_param(HashTable *params, php_http_params_state_t *state, const zend_bool rfc5987 = 0; ZVAL_NULL(&key); - sanitize_key(opts->flags, state->param.str, state->param.len, &key, &rfc5987); - state->rfc5987 = rfc5987; + if (opts->flags & PHP_HTTP_PARAMS_RFC5988) { + sanitize_rfc5988(state->param.str, state->param.len, &key); + } else { + sanitize_key(opts->flags, state->param.str, state->param.len, &key, &rfc5987); + state->rfc5987 = rfc5987; + } 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)) { - //array_init_size(&prm, 2); + // FIXME: array_init_size(&prm, 2); array_init(&prm); if (!Z_ISUNDEF(opts->defval)) { @@ -563,7 +612,7 @@ static void push_param(HashTable *params, php_http_params_state_t *state, const } else { state->current.val = zend_hash_str_update(Z_ARRVAL(prm), "value", lenof("value"), &val); } - //array_init_size(&arg, 3); + // 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); @@ -623,7 +672,13 @@ HashTable *php_http_params_parse(HashTable *params, const php_http_params_opts_t } while (state.input.len) { - if (*state.input.str == '"' && !state.escape) { + if ((opts->flags & PHP_HTTP_PARAMS_RFC5988) && !state.arg.str) { + if (*state.input.str == '<') { + state.quotes = 1; + } else if (*state.input.str == '>') { + state.quotes = 0; + } + } else if (*state.input.str == '"' && !state.escape) { state.quotes = !state.quotes; } else { state.escape = (*state.input.str == '\\'); @@ -735,6 +790,33 @@ static inline void shift_rfc5987(php_http_buffer_t *buf, zval *zvalue, const cha } } +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; + + if (buf->used) { + php_http_buffer_append(buf, ass, asl); + } + + 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) +{ + zend_string *zs = zval_get_string(zv); + + quote_string(&zs, 1); + php_http_buffer_append(buf, vss, vsl); + php_http_buffer_append(buf, zs->val, zs->len); + + zend_string_release(zs); +} + static inline void shift_val(php_http_buffer_t *buf, zval *zvalue, const char *vss, size_t vsl, unsigned flags) { zval tmp; @@ -788,6 +870,21 @@ static void shift_arg(php_http_buffer_t *buf, char *key_str, size_t key_len, zva ZEND_HASH_FOREACH_END(); } else { shift_key(buf, key_str, key_len, ass, asl, flags); + + if (flags & PHP_HTTP_PARAMS_RFC5988) { + switch (key_len) { + case lenof("rel"): + case lenof("title"): + 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); + return; + } + break; + } + } + shift_val(buf, zvalue, vss, vsl, flags); } } @@ -808,6 +905,11 @@ static void shift_param(php_http_buffer_t *buf, char *key_str, size_t key_len, z } } else { shift_key(buf, key_str, key_len, pss, psl, flags); + if (flags & PHP_HTTP_PARAMS_RFC5988) { + shift_rfc5988(buf, key_str, key_len, pss, psl, flags); + } else { + shift_key(buf, key_str, key_len, pss, psl, flags); + } shift_val(buf, zvalue, vss, vsl, flags); } } @@ -1201,6 +1303,7 @@ PHP_MINIT_FUNCTION(http_params) 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); diff --git a/php_http_params.h b/php_http_params.h index 1803c84..b889210 100644 --- a/php_http_params.h +++ b/php_http_params.h @@ -23,6 +23,7 @@ typedef struct php_http_params_token { #define PHP_HTTP_PARAMS_URLENCODED 0x04 #define PHP_HTTP_PARAMS_DIMENSION 0x08 #define PHP_HTTP_PARAMS_RFC5987 0x10 +#define PHP_HTTP_PARAMS_RFC5988 0x20 #define PHP_HTTP_PARAMS_QUERY (PHP_HTTP_PARAMS_URLENCODED|PHP_HTTP_PARAMS_DIMENSION) #define PHP_HTTP_PARAMS_DEFAULT (PHP_HTTP_PARAMS_ESCAPED|PHP_HTTP_PARAMS_RFC5987) diff --git a/php_http_url.c b/php_http_url.c index 5a267d8..18ac182 100644 --- a/php_http_url.c +++ b/php_http_url.c @@ -12,7 +12,9 @@ #include "php_http_api.h" -#ifdef PHP_HTTP_HAVE_IDN +#if PHP_HTTP_HAVE_IDN2 +# include +#elif PHP_HTTP_HAVE_IDN # include #endif @@ -567,8 +569,8 @@ HashTable *php_http_url_to_struct(const php_http_url_t *url, zval *strct) ZEND_RESULT_CODE php_http_url_encode_hash(HashTable *hash, const char *pre_encoded_str, size_t pre_encoded_len, char **encoded_str, size_t *encoded_len) { - const char *arg_sep_str; - size_t arg_sep_len; + const char *arg_sep_str = "&"; + size_t arg_sep_len = 1; php_http_buffer_t *qstr = php_http_buffer_new(); php_http_url_argsep(&arg_sep_str, &arg_sep_len); @@ -837,7 +839,7 @@ static ZEND_RESULT_CODE parse_userinfo(struct parse_state *state, const char *pt #if defined(PHP_WIN32) || defined(HAVE_UIDNA_IDNTOASCII) typedef size_t (*parse_mb_func)(unsigned *wc, const char *ptr, const char *end); -static ZEND_RESULT_CODE to_utf16(parse_mb_func fn, const char *u8, uint16_t **u16, size_t *len) +static ZEND_RESULT_CODE to_utf16(parse_mb_func fn, const char *u8, uint16_t **u16, size_t *len TSRMLS_DC) { size_t offset = 0, u8_len = strlen(u8); @@ -880,7 +882,33 @@ static ZEND_RESULT_CODE to_utf16(parse_mb_func fn, const char *u8, uint16_t **u1 # define MAXHOSTNAMELEN 256 #endif -#ifdef PHP_HTTP_HAVE_IDN +#if PHP_HTTP_HAVE_IDN2 +static ZEND_RESULT_CODE parse_idn2(struct parse_state *state, size_t prev_len) +{ + char *idn = NULL; + int rv = -1; + TSRMLS_FETCH_FROM_CTX(state->ts); + + if (state->flags & PHP_HTTP_URL_PARSE_MBUTF8) { + rv = idn2_lookup_u8((const unsigned char *) state->url.host, (unsigned char **) &idn, IDN2_NFC_INPUT); + } +# ifdef PHP_HTTP_HAVE_WCHAR + else if (state->flags & PHP_HTTP_URL_PARSE_MBLOC) { + rv = idn2_lookup_ul(state->url.host, &idn, 0); + } +# endif + if (rv != IDN2_OK) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to parse IDN; %s", idn2_strerror(rv)); + return FAILURE; + } else { + size_t idnlen = strlen(idn); + memcpy(state->url.host, idn, idnlen + 1); + free(idn); + state->offset += idnlen - prev_len; + return SUCCESS; + } +} +#elif PHP_HTTP_HAVE_IDN static ZEND_RESULT_CODE parse_idn(struct parse_state *state, size_t prev_len) { char *idn = NULL; @@ -924,12 +952,12 @@ static ZEND_RESULT_CODE parse_uidn(struct parse_state *state) TSRMLS_FETCH_FROM_CTX(state->ts); if (state->flags & PHP_HTTP_URL_PARSE_MBUTF8) { - if (SUCCESS != to_utf16(parse_mb_utf8, state->url.host, &uhost_str, &uhost_len)) { + if (SUCCESS != to_utf16(parse_mb_utf8, state->url.host, &uhost_str, &uhost_len TSRMLS_CC)) { return FAILURE; } #ifdef PHP_HTTP_HAVE_WCHAR } else if (state->flags & PHP_HTTP_URL_PARSE_MBLOC) { - if (SUCCESS != to_utf16(parse_mb_loc, state->url.host, &uhost_str, &uhost_len)) { + if (SUCCESS != to_utf16(parse_mb_loc, state->url.host, &uhost_str, &uhost_len TSRMLS_CC)) { return FAILURE; } #endif @@ -1113,7 +1141,9 @@ static ZEND_RESULT_CODE parse_hostinfo(struct parse_state *state, const char *pt } if (state->flags & PHP_HTTP_URL_PARSE_TOIDN) { -#ifdef PHP_HTTP_HAVE_IDN +#if PHP_HTTP_HAVE_IDN2 + return parse_idn2(state, len); +#elif PHP_HTTP_HAVE_IDN return parse_idn(state, len); #endif #ifdef HAVE_UIDNA_IDNTOASCII @@ -1245,7 +1275,7 @@ static const char *parse_query(struct parse_state *state) tmp = ++state->ptr; state->url.query = &state->buffer[state->offset]; - do { + while (state->ptr < state->end) { switch (*state->ptr) { case '#': goto done; @@ -1297,7 +1327,9 @@ static const char *parse_query(struct parse_state *state) } state->ptr += mb - 1; } - } while (++state->ptr < state->end); + + ++state->ptr; + } done: state->buffer[state->offset++] = 0; @@ -1541,7 +1573,7 @@ ZEND_END_ARG_INFO(); PHP_METHOD(HttpUrl, mod) { zval *new_url = NULL; - zend_long flags = PHP_HTTP_URL_JOIN_PATH | PHP_HTTP_URL_JOIN_QUERY; + 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(), "z!|l", &new_url, &flags), invalid_arg, return); @@ -1656,7 +1688,7 @@ PHP_MINIT_FUNCTION(http_url) 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); -#if defined(PHP_HTTP_HAVE_IDN) || defined(HAVE_UIDNA_IDNTOASCII) +#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); #endif zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("PARSE_TOPCT"), PHP_HTTP_URL_PARSE_TOPCT); diff --git a/reflection2php.php b/reflection2php.php index e486012..20a1f0f 100755 --- a/reflection2php.php +++ b/reflection2php.php @@ -98,11 +98,11 @@ foreach ($namespaces as $ns) { fprintf($out, "\n\tfunction %s(", $fn); $ps = array(); foreach ($f->getParameters() as $p) { - $p1 = sfprintf($out, "%s%s\$%s", t($p), - $p->isPassedByReference()?"&":"", $p->getName()); + $p1 = sprintf("%s%s\$%s", t($p), + $p->isPassedByReference()?"&":"", trim($p->getName(), "\"")); if ($p->isOptional()) { if ($p->isDefaultValueAvailable()) { - $p1 .= sfprintf($out, " = %s", + $p1 .= sprintf(" = %s", var_export($p->getDefaultValue(), true)); } elseif (!($p->isArray() || $p->getClass()) || $p->allowsNull()) { $p1 .= " = NULL"; diff --git a/tests/bug69076.phpt b/tests/bug69076.phpt new file mode 100644 index 0000000..cd64958 --- /dev/null +++ b/tests/bug69076.phpt @@ -0,0 +1,17 @@ +--TEST-- +Bug #69076 (URL parsing throws exception on empty query string) +--SKIPIF-- + +--FILE-- + + +===DONE=== +--EXPECT-- +Test +http://foo.bar/ +===DONE=== diff --git a/tests/bug69313.phpt b/tests/bug69313.phpt new file mode 100644 index 0000000..c1d56ef --- /dev/null +++ b/tests/bug69313.phpt @@ -0,0 +1,46 @@ +--TEST-- +Bug #69313 (http\Client doesn't send GET body) +--SKIPIF-- + +--FILE-- +setHeader("Content-Type", "text/plain"); + $request->getBody()->append("foo"); + $client = new http\Client(); + $client->enqueue($request); + $client->send(); + dump_message(null, $client->getResponse()); +}); + +?> + +Done +--EXPECTF-- +Test +HTTP/1.1 200 OK +Accept-Ranges: bytes +Content-Length: %d +Etag: "%s" +X-Original-Transfer-Encoding: chunked + +GET / HTTP/1.1 +Accept: */* +Content-Length: 3 +Content-Type: text/plain +Host: localhost:%d +User-Agent: %s +X-Original-Content-Length: 3 + +foo +Done diff --git a/tests/bug69357.phpt b/tests/bug69357.phpt new file mode 100644 index 0000000..ac93baf --- /dev/null +++ b/tests/bug69357.phpt @@ -0,0 +1,41 @@ +--TEST-- +Bug #69357 (HTTP/1.1 100 Continue overriding subsequent 200 response code with PUT request) +--SKIPIF-- + +--FILE-- +append("foo") + ); + $c = new \http\Client; + $c->setOptions(["expect_100_timeout" => 0]); + $c->enqueue($r)->send(); + + var_dump($c->getResponse($r)->getInfo()); + var_dump($c->getResponse($r)->getHeaders()); +}); + +?> +===DONE=== +--EXPECTF-- +Test +string(15) "HTTP/1.1 200 OK" +array(4) { + ["Accept-Ranges"]=> + string(5) "bytes" + ["Etag"]=> + string(10) ""%x"" + ["X-Original-Transfer-Encoding"]=> + string(7) "chunked" + ["Content-Length"]=> + int(%d) +} +===DONE=== diff --git a/tests/client019.phpt b/tests/client019.phpt index c41a260..9666b97 100644 --- a/tests/client019.phpt +++ b/tests/client019.phpt @@ -41,8 +41,9 @@ server("proxy.inc", function($port, $stdin, $stdout, $stderr) { Test Server on port %d CONNECT www.example.com:80 HTTP/1.1 +Hello: there! Host: www.example.com:80 -User-Agent: PECL_HTTP/%s PHP/%s libcurl/%s Proxy-Connection: Keep-Alive -Hello: there! +User-Agent: PECL_HTTP/%s PHP/%s libcurl/%s + ===DONE=== diff --git a/tests/client020.phpt b/tests/client020.phpt index 7ea5d60..ed86f4a 100644 --- a/tests/client020.phpt +++ b/tests/client020.phpt @@ -35,7 +35,8 @@ server("proxy.inc", function($port, $stdin, $stdout, $stderr) { Test Server on port %d GET / HTTP/1.1 -User-Agent: PECL_HTTP/%s PHP/%s libcurl/%s -Host: localhost:%d Accept: */* +Host: localhost:%d +User-Agent: PECL_HTTP/%s PHP/%s libcurl/%s + ===DONE=== diff --git a/tests/client025.phpt b/tests/client025.phpt index 3c4793e..3f90cbd 100644 --- a/tests/client025.phpt +++ b/tests/client025.phpt @@ -7,6 +7,7 @@ include "skipif.inc"; --FILE-- setOptions(array("resume" => 1, "expect_100_timeout" => 0)); $request->getBody()->append("123"); - echo $client->enqueue($request)->send()->getResponse(); + dump_message(null, $client->enqueue($request)->send()->getResponse()); }); // Content-length is 2 instead of 3 in older libcurls ?> @@ -25,17 +26,17 @@ server("proxy.inc", function($port) { Test HTTP/1.1 200 OK Accept-Ranges: bytes +Content-Length: %d Etag: "%x" X-Original-Transfer-Encoding: chunked -Content-Length: %d PUT / HTTP/1.1 -Content-Range: bytes 1-2/3 -User-Agent: %s -Host: localhost:%d Accept: */* Content-Length: %d +Content-Range: bytes 1-2/3 Expect: 100-continue +Host: localhost:%d +User-Agent: %s X-Original-Content-Length: %d 23===DONE=== diff --git a/tests/helper/dump.inc b/tests/helper/dump.inc new file mode 100644 index 0000000..5f5f367 --- /dev/null +++ b/tests/helper/dump.inc @@ -0,0 +1,21 @@ +getInfo()); + $headers = $msg->getHeaders(); + ksort($headers); + foreach ($headers as $key => $val) { + fprintf($stream, "%s: %s\n", $key, $val); + } + fprintf($stream, "\n"); + $msg->getBody()->toStream($stream); + + if ($parent && ($msg = $msg->getParentMessage())) { + dump_message($stream, $msg, true); + } +} + +?> \ No newline at end of file diff --git a/tests/helper/proxy.inc b/tests/helper/proxy.inc index 80a0073..f99dd97 100644 --- a/tests/helper/proxy.inc +++ b/tests/helper/proxy.inc @@ -1,5 +1,6 @@ getBody()->append($request); */ - $request->toStream($response->getBody()->getResource()); + dump_message($response->getBody()->getResource(), $request); $response->send($client); }); diff --git a/tests/helper/upload.inc b/tests/helper/upload.inc new file mode 100644 index 0000000..ddc06a8 --- /dev/null +++ b/tests/helper/upload.inc @@ -0,0 +1,21 @@ +getHeader("Expect") === "100-continue") { + $response = new http\Env\Response; + $response->setEnvRequest($request); + $response->setResponseCode(100); + $response->send($client); + } + + /* return the initial message as response body */ + $response = new http\Env\Response; + /* avoid OOM with $response->getBody()->append($request); */ + dump_message($response->getBody()->getResource(), $request); + $response->send($client); +}); diff --git a/tests/params016.phpt b/tests/params016.phpt new file mode 100644 index 0000000..e5fbd97 --- /dev/null +++ b/tests/params016.phpt @@ -0,0 +1,46 @@ +--TEST-- +header params rfc5988 +--SKIPIF-- + +--FILE-- +; rel="next", ; rel="last" +EOF; + +$p = new http\Params($link, ",", ";", "=", + http\Params::PARSE_RFC5988 | http\Params::PARSE_ESCAPED); +var_dump($p->params); +var_dump((string)$p); +?> +===DONE=== +--EXPECT-- +Test +array(2) { + ["https://api.github.com/search/code?q=addClass+user%3Amozilla&page=2"]=> + array(2) { + ["value"]=> + bool(true) + ["arguments"]=> + array(1) { + ["rel"]=> + string(4) "next" + } + } + ["https://api.github.com/search/code?q=addClass+user%3Amozilla&page=34"]=> + array(2) { + ["value"]=> + bool(true) + ["arguments"]=> + array(1) { + ["rel"]=> + string(4) "last" + } + } +} +string(162) ";rel="next",;rel="last"" +===DONE=== diff --git a/tests/params017.phpt b/tests/params017.phpt new file mode 100644 index 0000000..b3587e5 --- /dev/null +++ b/tests/params017.phpt @@ -0,0 +1,69 @@ +--TEST-- +header params rfc5988 +--SKIPIF-- + +--FILE-- +; + rel="previous"; title*=UTF-8'de'letztes%20Kapitel, + ; + rel="next"; title*=UTF-8'de'n%c3%a4chstes%20Kapitel +EOF; + +$p = current(http\Header::parse($link, "http\\Header"))->getParams( + http\Params::DEF_PARAM_SEP, + http\Params::DEF_ARG_SEP, + http\Params::DEF_VAL_SEP, + http\Params::PARSE_RFC5988 | http\Params::PARSE_ESCAPED +); +var_dump($p->params); +var_dump((string)$p); +?> +===DONE=== +--EXPECTF-- +Test +array(2) { + ["/TheBook/chapter2"]=> + array(2) { + ["value"]=> + bool(true) + ["arguments"]=> + array(2) { + ["rel"]=> + string(8) "previous" + ["*rfc5987*"]=> + array(1) { + ["title"]=> + array(1) { + ["de"]=> + string(15) "letztes Kapitel" + } + } + } + } + ["/TheBook/chapter4"]=> + array(2) { + ["value"]=> + bool(true) + ["arguments"]=> + array(2) { + ["rel"]=> + string(4) "next" + ["*rfc5987*"]=> + array(1) { + ["title"]=> + array(1) { + ["de"]=> + string(17) "nächstes Kapitel" + } + } + } + } +} +string(139) ";rel="previous";title*=utf-8'de'letztes%20Kapitel,;rel="next";title*=utf-8'de'n%C3%A4chstes%20Kapitel" +===DONE===