From: Michael Wallner Date: Sat, 7 Feb 2015 11:30:04 +0000 (+0100) Subject: Merge branch 'R_2_2' X-Git-Tag: RELEASE_2_3_0_RC1~48 X-Git-Url: https://git.m6w6.name/?p=m6w6%2Fext-http;a=commitdiff_plain;h=90b581e9c564acde783926e412d7c5d737c4cbe4;hp=5a3a84f296489462537ce6b6d1b2e646d22d24b0 Merge branch 'R_2_2' --- diff --git a/config9.m4 b/config9.m4 index 3b1f701..20e7d6c 100644 --- a/config9.m4 +++ b/config9.m4 @@ -202,6 +202,14 @@ dnl ---- if test `echo $CURL_VERSION | $SED -e 's/[[^0-9]]/ /g' | $AWK '{print $1*10000 + $2*100 + $3}'` -lt 71802; then AC_MSG_ERROR([libcurl version greater or equal to 7.18.2 required]) fi + + AC_MSG_CHECKING([for HTTP2 support in libcurl]) + if $CURL_CONFIG --features | $EGREP -q HTTP2; then + AC_MSG_RESULT([yes]) + AC_DEFINE([PHP_HTTP_HAVE_HTTP2], [1], [ ]) + else + AC_MSG_RESULT([no]) + fi dnl dnl compile tests @@ -210,11 +218,11 @@ dnl ---- save_INCLUDES="$INCLUDES" INCLUDES= save_LIBS="$LIBS" - LIBS= + LIBS=-lcurl save_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS `$CURL_CONFIG --cflags`" save_LDFLAGS="$LDFLAGS" - LDFLAGS="$LDFLAGS `$CURL_CONFIG --libs` $ld_runpath_switch$CURL_DIR/$PHP_LIBDIR" + LDFLAGS="$ld_runpath_switch$CURL_DIR/$PHP_LIBDIR" AC_MSG_CHECKING([for SSL support in libcurl]) CURL_SSL=`$CURL_CONFIG --feature | $EGREP SSL` @@ -296,6 +304,37 @@ dnl ---- ], [ AC_MSG_RESULT([no]) ]) + + AC_MSG_CHECKING([whether CURLOPT_TLSAUTH_TYPE expects CURL_TLSAUTH_SRP or literal "SRP"]) + AC_TRY_RUN([ + #include + int main(int argc, char *argv[]) { + CURL *ch = curl_easy_init(); + return curl_easy_setopt(ch, CURLOPT_TLSAUTH_TYPE, CURL_TLSAUTH_SRP); + } + ], [ + AC_MSG_RESULT([CURL_TLSAUTH_SRP]) + AC_DEFINE([PHP_HTTP_CURL_TLSAUTH_SRP], [CURL_TLSAUTH_SRP], [ ]) + AC_DEFINE([PHP_HTTP_CURL_TLSAUTH_DEF], [CURL_TLSAUTH_NONE], [ ]) + ], [ + AC_TRY_RUN([ + #include + int main(int argc, char *argv[]) { + CURL *ch = curl_easy_init(); + return curl_easy_setopt(ch, CURLOPT_TLSAUTH_TYPE, "SRP"); + } + ], [ + AC_MSG_RESULT(["SRP"]) + AC_DEFINE([PHP_HTTP_CURL_TLSAUTH_SRP], ["SRP"], [ ]) + AC_DEFINE([PHP_HTTP_CURL_TLSAUTH_DEF], [""], [ ]) + ], [ + AC_MSG_RESULT([neither]) + ], [ + AC_MSG_RESULT([neither]) + ]) + ], [ + AC_MSG_RESULT([neither]) + ]) INCLUDES="$save_INCLUDES" LIBS="$save_LIBS" diff --git a/gen_curlinfo.php b/gen_curlinfo.php index 0e8dfa1..076a10d 100755 --- a/gen_curlinfo.php +++ b/gen_curlinfo.php @@ -41,7 +41,8 @@ $ifdefs = array( ); $exclude = array( 'PRIVATE', 'LASTSOCKET', 'FTP_ENTRY_PATH', 'CERTINFO', 'TLS_SESSION', - 'RTSP_SESSION_ID', 'RTSP_CLIENT_CSEQ', 'RTSP_SERVER_CSEQ', 'RTSP_CSEQ_RECV' + 'RTSP_SESSION_ID', 'RTSP_CLIENT_CSEQ', 'RTSP_SERVER_CSEQ', 'RTSP_CSEQ_RECV', + 'COOKIELIST' ); $translate = array( diff --git a/package.xml b/package.xml index 9b35848..77cd1a0 100644 --- a/package.xml +++ b/package.xml @@ -37,10 +37,10 @@ v2: http://dev.iworks.at/ext-http/lcov/ext/http/ mike@php.net yes - 2015-02-07 + 2015-01-27 - 2.2.2 - 2.2.0 + 2.3.0 + 2.3.0 stable @@ -48,6 +48,9 @@ v2: http://dev.iworks.at/ext-http/lcov/ext/http/ BSD, revised = 7.33.0) ++ Added pinned_publickey SSL request option (libcurl >= 7.39.0) ]]> diff --git a/php_http.h b/php_http.h index 63ecc19..5a0575d 100644 --- a/php_http.h +++ b/php_http.h @@ -13,7 +13,7 @@ #ifndef PHP_EXT_HTTP_H #define PHP_EXT_HTTP_H -#define PHP_PECL_HTTP_VERSION "2.2.2dev" +#define PHP_PECL_HTTP_VERSION "2.3.0dev" extern zend_module_entry http_module_entry; #define phpext_http_ptr &http_module_entry diff --git a/php_http_client.c b/php_http_client.c index f96164b..3050bd8 100644 --- a/php_http_client.c +++ b/php_http_client.c @@ -329,6 +329,8 @@ void php_http_client_object_free(void *object TSRMLS_DC) php_http_client_object_t *o = (php_http_client_object_t *) object; php_http_client_free(&o->client); + php_http_object_method_dtor(&o->notify); + php_http_object_method_free(&o->update); zend_object_std_dtor((zend_object *) o TSRMLS_CC); efree(o); } @@ -375,7 +377,7 @@ static void handle_history(zval *zclient, php_http_message_t *request, php_http_ zval_ptr_dtor(&new_hist); } -static STATUS handle_response(void *arg, php_http_client_t *client, php_http_client_enqueue_t *e, php_http_message_t **request, php_http_message_t **response) +static STATUS handle_response(void *arg, php_http_client_t *client, php_http_client_enqueue_t *e, php_http_message_t **response) { zend_bool dequeue = 0; zval zclient; @@ -395,7 +397,7 @@ static STATUS handle_response(void *arg, php_http_client_t *client, php_http_cli php_http_message_set_type(msg, PHP_HTTP_RESPONSE); if (z_is_true(zend_read_property(php_http_client_class_entry, &zclient, ZEND_STRL("recordHistory"), 0 TSRMLS_CC))) { - handle_history(&zclient, *request, *response TSRMLS_CC); + handle_history(&zclient, e->request, *response TSRMLS_CC); } /* hard detach, redirects etc. are in the history */ @@ -457,14 +459,18 @@ static STATUS handle_response(void *arg, php_http_client_t *client, php_http_cli static void handle_progress(void *arg, php_http_client_t *client, php_http_client_enqueue_t *e, php_http_client_progress_state_t *progress) { - zval *zrequest, *zprogress, *retval = NULL, *zclient; + zval *zrequest, *zprogress, *zclient, **args[2]; + php_http_client_object_t *client_obj = arg; zend_error_handling zeh; TSRMLS_FETCH_FROM_CTX(client->ts); MAKE_STD_ZVAL(zclient); - ZVAL_OBJVAL(zclient, ((php_http_client_object_t *) arg)->zv, 1); + ZVAL_OBJVAL(zclient, client_obj->zv, 1); + MAKE_STD_ZVAL(zrequest); ZVAL_OBJVAL(zrequest, ((php_http_message_object_t *) e->opaque)->zv, 1); + args[0] = &zrequest; + MAKE_STD_ZVAL(zprogress); object_init(zprogress); add_property_bool(zprogress, "started", progress->started); @@ -474,15 +480,15 @@ static void handle_progress(void *arg, php_http_client_t *client, php_http_clien add_property_double(zprogress, "dlnow", progress->dl.now); add_property_double(zprogress, "ultotal", progress->ul.total); add_property_double(zprogress, "ulnow", progress->ul.now); + args[1] = &zprogress; + zend_replace_error_handling(EH_NORMAL, NULL, &zeh TSRMLS_CC); - zend_call_method_with_2_params(&zclient, NULL, NULL, "notify", &retval, zrequest, zprogress); + php_http_object_method_call(&client_obj->notify, zclient, NULL, 2, args TSRMLS_CC); zend_restore_error_handling(&zeh TSRMLS_CC); + zval_ptr_dtor(&zclient); zval_ptr_dtor(&zrequest); zval_ptr_dtor(&zprogress); - if (retval) { - zval_ptr_dtor(&retval); - } } static void response_dtor(void *data) @@ -537,6 +543,8 @@ static PHP_METHOD(HttpClient, __construct) php_http_expect(obj->client = php_http_client_init(NULL, driver.client_ops, rf, NULL TSRMLS_CC), runtime, return); + php_http_object_method_init(&obj->notify, getThis(), ZEND_STRL("notify") TSRMLS_CC); + obj->client->callback.response.func = handle_response; obj->client->callback.response.arg = obj; obj->client->callback.progress.func = handle_progress; @@ -869,13 +877,20 @@ static PHP_METHOD(HttpClient, enableEvents) RETVAL_ZVAL(getThis(), 1, 0); } +struct notify_arg { + php_http_object_method_t *cb; + zval **args[3]; + int argc; +}; + static int notify(zend_object_iterator *iter, void *puser TSRMLS_DC) { - zval **observer = NULL, ***args = puser; + zval **observer = NULL; + struct notify_arg *arg = puser; iter->funcs->get_current_data(iter, &observer TSRMLS_CC); if (observer) { - return php_http_method_call(*observer, ZEND_STRL("update"), args[2]?3:args[1]?2:args[0]?1:0, args, NULL TSRMLS_CC); + return php_http_object_method_call(arg->cb, *observer, NULL, arg->argc, arg->args TSRMLS_CC); } return FAILURE; } @@ -885,10 +900,13 @@ ZEND_BEGIN_ARG_INFO_EX(ai_HttpClient_notify, 0, 0, 0) ZEND_END_ARG_INFO(); static PHP_METHOD(HttpClient, notify) { - zval *request = NULL, *zprogress = NULL, *observers, **args[3]; + zval *request = NULL, *zprogress = NULL, *observers; + php_http_client_object_t *client_obj; + struct notify_arg arg = {NULL}; php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|O!o!", &request, php_http_client_request_class_entry, &zprogress), invalid_arg, return); + client_obj = zend_object_store_get_object(getThis() TSRMLS_CC); observers = zend_read_property(php_http_client_class_entry, getThis(), ZEND_STRL("observers"), 0 TSRMLS_CC); if (Z_TYPE_P(observers) != IS_OBJECT) { @@ -896,23 +914,34 @@ static PHP_METHOD(HttpClient, notify) return; } - Z_ADDREF_P(getThis()); - args[0] = &getThis(); - if (request) { - Z_ADDREF_P(request); - } - args[1] = &request; - if (zprogress) { - Z_ADDREF_P(zprogress); - } - args[2] = &zprogress; - spl_iterator_apply(observers, notify, args TSRMLS_CC); - zval_ptr_dtor(&getThis()); - if (request) { - zval_ptr_dtor(&request); - } - if (zprogress) { - zval_ptr_dtor(&zprogress); + if (client_obj->update) { + arg.cb = client_obj->update; + + Z_ADDREF_P(getThis()); + arg.args[0] = &getThis(); + arg.argc = 1; + + if (request) { + Z_ADDREF_P(request); + arg.args[1] = &request; + arg.argc += 1; + } + + if (zprogress) { + Z_ADDREF_P(zprogress); + arg.args[2] = &zprogress; + arg.argc += 1; + } + + spl_iterator_apply(observers, notify, &arg TSRMLS_CC); + + zval_ptr_dtor(&getThis()); + if (request) { + zval_ptr_dtor(&request); + } + if (zprogress) { + zval_ptr_dtor(&zprogress); + } } RETVAL_ZVAL(getThis(), 1, 0); @@ -924,9 +953,11 @@ ZEND_END_ARG_INFO(); static PHP_METHOD(HttpClient, attach) { zval *observers, *observer, *retval = NULL; + php_http_client_object_t *client_obj; php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O", &observer, spl_ce_SplObserver), invalid_arg, return); + client_obj = zend_object_store_get_object(getThis() TSRMLS_CC); observers = zend_read_property(php_http_client_class_entry, getThis(), ZEND_STRL("observers"), 0 TSRMLS_CC); if (Z_TYPE_P(observers) != IS_OBJECT) { @@ -934,6 +965,10 @@ static PHP_METHOD(HttpClient, attach) return; } + if (!client_obj->update) { + client_obj->update = php_http_object_method_init(NULL, observer, ZEND_STRL("update") TSRMLS_CC); + } + zend_call_method_with_1_params(&observers, NULL, NULL, "attach", &retval, observer); if (retval) { zval_ptr_dtor(&retval); diff --git a/php_http_client.h b/php_http_client.h index 6c45516..b536753 100644 --- a/php_http_client.h +++ b/php_http_client.h @@ -84,7 +84,7 @@ typedef struct php_http_client_progress_state { unsigned finished:1; } php_http_client_progress_state_t; -typedef STATUS (*php_http_client_response_callback_t)(void *arg, struct php_http_client *client, php_http_client_enqueue_t *e, php_http_message_t **request, php_http_message_t **response); +typedef STATUS (*php_http_client_response_callback_t)(void *arg, struct php_http_client *client, php_http_client_enqueue_t *e, php_http_message_t **response); typedef void (*php_http_client_progress_callback_t)(void *arg, struct php_http_client *client, php_http_client_enqueue_t *e, php_http_client_progress_state_t *state); typedef struct php_http_client { @@ -118,6 +118,8 @@ typedef struct php_http_client_object { zend_object_value zv; php_http_client_t *client; long iterator; + php_http_object_method_t *update; + php_http_object_method_t notify; } php_http_client_object_t; PHP_HTTP_API php_http_client_t *php_http_client_init(php_http_client_t *h, php_http_client_ops_t *ops, php_resource_factory_t *rf, void *init_arg TSRMLS_DC); diff --git a/php_http_client_curl.c b/php_http_client_curl.c index b057216..3b4d5b1 100644 --- a/php_http_client_curl.c +++ b/php_http_client_curl.c @@ -60,24 +60,17 @@ typedef struct php_http_client_curl_handler { php_resource_factory_t *rf; php_http_client_t *client; php_http_client_progress_state_t progress; - php_http_client_enqueue_t queue; struct { - php_http_message_parser_t *parser; - php_http_message_t *message; - php_http_buffer_t *buffer; - } request; - - struct { - php_http_message_parser_t *parser; - php_http_message_t *message; - php_http_buffer_t *buffer; + php_http_buffer_t headers; + php_http_message_body_t *body; } response; struct { HashTable cache; + struct curl_slist *proxyheaders; struct curl_slist *headers; struct curl_slist *resolve; php_http_buffer_t cookies; @@ -225,29 +218,23 @@ static int php_http_curle_progress_callback(void *ctx, double dltotal, double dl return 0; } -static curlioerr php_http_curle_ioctl_callback(CURL *ch, curliocmd cmd, void *ctx) +static int php_http_curle_seek_callback(void *userdata, curl_off_t offset, int origin) { - php_http_message_body_t *body = ctx; + php_http_message_body_t *body = userdata; + TSRMLS_FETCH_FROM_CTX(body->ts); - if (cmd != CURLIOCMD_RESTARTREAD) { - return CURLIOE_UNKNOWNCMD; + if (!body) { + return 1; } - - if (body) { - TSRMLS_FETCH_FROM_CTX(body->ts); - - if (SUCCESS == php_stream_rewind(php_http_message_body_stream(body))) { - return CURLIOE_OK; - } + if (0 == php_stream_seek(php_http_message_body_stream(body), offset, origin)) { + return 0; } - - return CURLIOE_FAILRESTART; + return 2; } static int php_http_curle_raw_callback(CURL *ch, curl_infotype type, char *data, size_t length, void *ctx) { php_http_client_curl_handler_t *h = ctx; - unsigned flags = 0; /* catch progress */ switch (type) { @@ -304,32 +291,6 @@ static int php_http_curle_raw_callback(CURL *ch, curl_infotype type, char *data, default: break; } - /* process data */ - switch (type) { - case CURLINFO_HEADER_IN: - case CURLINFO_DATA_IN: - php_http_buffer_append(h->response.buffer, data, length); - - if (h->options.redirects) { - flags |= PHP_HTTP_MESSAGE_PARSER_EMPTY_REDIRECTS; - } - - if (PHP_HTTP_MESSAGE_PARSER_STATE_FAILURE == php_http_message_parser_parse(h->response.parser, h->response.buffer, flags, &h->response.message)) { - return -1; - } - break; - - case CURLINFO_HEADER_OUT: - case CURLINFO_DATA_OUT: - php_http_buffer_append(h->request.buffer, data, length); - - if (PHP_HTTP_MESSAGE_PARSER_STATE_FAILURE == php_http_message_parser_parse(h->request.parser, h->request.buffer, flags, &h->request.message)) { - return -1; - } - break; - default: - break; - } #if 0 /* debug */ @@ -339,9 +300,18 @@ static int php_http_curle_raw_callback(CURL *ch, curl_infotype type, char *data, return 0; } -static int php_http_curle_dummy_callback(char *data, size_t n, size_t l, void *s) +static int php_http_curle_header_callback(char *data, size_t n, size_t l, void *arg) +{ + php_http_client_curl_handler_t *h = arg; + + return php_http_buffer_append(&h->response.headers, data, n * l); +} + +static int php_http_curle_body_callback(char *data, size_t n, size_t l, void *arg) { - return n*l; + php_http_client_curl_handler_t *h = arg; + + return php_http_message_body_append(h->response.body, data, n*l); } static STATUS php_http_curle_get_info(CURL *ch, HashTable *info) @@ -440,17 +410,6 @@ static STATUS php_http_curle_get_info(CURL *ch, HashTable *info) add_assoc_zval_ex(&array, "ssl_engines", sizeof("ssl_engines"), subarray); curl_slist_free_all(s); } - if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_COOKIELIST, &s)) { - MAKE_STD_ZVAL(subarray); - array_init(subarray); - for (p = s; p; p = p->next) { - if (p->data) { - add_next_index_string(subarray, p->data, 1); - } - } - add_assoc_zval_ex(&array, "cookies", sizeof("cookies"), subarray); - curl_slist_free_all(s); - } if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_REDIRECT_URL, &c)) { add_assoc_string_ex(&array, "redirect_url", sizeof("redirect_url"), c ? c : "", 1); } @@ -617,6 +576,50 @@ static int compare_queue(php_http_client_enqueue_t *e, void *handle) return handle == ((php_http_client_curl_handler_t *) e->opaque)->handle; } +static php_http_message_t *php_http_curlm_responseparser(php_http_client_curl_handler_t *h TSRMLS_DC) +{ + php_http_message_t *response; + php_http_header_parser_t parser; + zval *zh; + + 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); + php_http_header_parser_dtor(&parser); + + /* move body to right message */ + if (response->body != h->response.body) { + php_http_message_t *ptr = response; + + while (ptr->parent) { + ptr = ptr->parent; + } + response->body = ptr->body; + ptr->body = NULL; + } + php_http_message_body_addref(h->response.body); + + /* let's update the response headers */ + if ((zh = php_http_message_header(response, ZEND_STRL("Content-Length"), 1))) { + zend_hash_update(&response->hdrs, "X-Original-Content-Length", sizeof("X-Original-Content-Length"), &zh, sizeof(zval *), NULL); + } + if ((zh = php_http_message_header(response, ZEND_STRL("Transfer-Encoding"), 0))) { + zend_hash_update(&response->hdrs, "X-Original-Transfer-Encoding", sizeof("X-Original-Transfer-Encoding"), (void *) &zh, sizeof(zval *), NULL); + zend_hash_del(&response->hdrs, "Transfer-Encoding", sizeof("Transfer-Encoding")); + } + if ((zh = php_http_message_header(response, ZEND_STRL("Content-Range"), 0))) { + zend_hash_update(&response->hdrs, "X-Original-Content-Range", sizeof("X-Original-Content-Range"), &zh, sizeof(zval *), NULL); + zend_hash_del(&response->hdrs, "Content-Range", sizeof("Content-Range")); + } + if ((zh = php_http_message_header(response, ZEND_STRL("Content-Encoding"), 0))) { + zend_hash_update(&response->hdrs, "X-Original-Content-Encoding", sizeof("X-Original-Content-Encoding"), &zh, sizeof(zval *), NULL); + zend_hash_del(&response->hdrs, "Content-Encoding", sizeof("Content-Encoding")); + } + php_http_message_update_headers(response); + + return response; +} + static void php_http_curlm_responsehandler(php_http_client_t *context) { int remaining = 0; @@ -635,8 +638,12 @@ static void php_http_curlm_responsehandler(php_http_client_t *context) if ((enqueue = php_http_client_enqueued(context, msg->easy_handle, compare_queue))) { php_http_client_curl_handler_t *handler = enqueue->opaque; + php_http_message_t *response = php_http_curlm_responseparser(handler TSRMLS_CC); - context->callback.response.func(context->callback.response.arg, context, &handler->queue, &handler->request.message, &handler->response.message); + if (response) { + context->callback.response.func(context->callback.response.arg, context, &handler->queue, &response); + php_http_message_free(&response); + } } } } while (remaining); @@ -925,9 +932,10 @@ static STATUS php_http_curle_option_set_lastmodified(php_http_option_t *opt, zva static STATUS php_http_curle_option_set_compress(php_http_option_t *opt, zval *val, void *userdata) { php_http_client_curl_handler_t *curl = userdata; + CURL *ch = curl->handle; - if (Z_BVAL_P(val)) { - curl->options.headers = curl_slist_append(curl->options.headers, "Accept-Encoding: gzip;q=1.0,deflate;q=0.5"); + if (CURLE_OK != curl_easy_setopt(ch, CURLOPT_ACCEPT_ENCODING, Z_BVAL_P(val) ? "" : NULL)) { + return FAILURE; } return SUCCESS; } @@ -1073,6 +1081,43 @@ static STATUS php_http_curle_option_set_portrange(php_http_option_t *opt, zval * return SUCCESS; } +#if PHP_HTTP_CURL_VERSION(7,37,0) +static STATUS php_http_curle_option_set_proxyheader(php_http_option_t *opt, zval *val, void *userdata) +{ + php_http_client_curl_handler_t *curl = userdata; + TSRMLS_FETCH_FROM_CTX(curl->client->ts); + + if (val && Z_TYPE_P(val) != IS_NULL) { + php_http_array_hashkey_t header_key = php_http_array_hashkey_init(0); + zval **header_val, *header_cpy; + HashPosition pos; + php_http_buffer_t header; + + php_http_buffer_init(&header); + FOREACH_KEYVAL(pos, val, header_key, header_val) { + if (header_key.type == HASH_KEY_IS_STRING) { + header_cpy = php_http_ztyp(IS_STRING, *header_val); + php_http_buffer_appendf(&header, "%s: %s", header_key.str, Z_STRVAL_P(header_cpy)); + php_http_buffer_fix(&header); + curl->options.proxyheaders = curl_slist_append(curl->options.proxyheaders, header.data); + php_http_buffer_reset(&header); + + zval_ptr_dtor(&header_cpy); + } + } + php_http_buffer_dtor(&header); + } + if (CURLE_OK != curl_easy_setopt(curl->handle, CURLOPT_PROXYHEADER, curl->options.proxyheaders)) { + return FAILURE; + } + if (CURLE_OK != curl_easy_setopt(curl->handle, CURLOPT_HEADEROPT, CURLHEADER_SEPARATE)) { + curl_easy_setopt(curl->handle, CURLOPT_PROXYHEADER, NULL); + return FAILURE; + } + return SUCCESS; +} +#endif + #if PHP_HTTP_CURL_VERSION(7,21,3) static STATUS php_http_curle_option_set_resolve(php_http_option_t *opt, zval *val, void *userdata) { @@ -1103,6 +1148,30 @@ static STATUS php_http_curle_option_set_resolve(php_http_option_t *opt, zval *va } #endif +#if PHP_HTTP_CURL_VERSION(7,21,4) +static STATUS php_http_curle_option_set_ssl_tlsauthtype(php_http_option_t *opt, zval *val, void *userdata) +{ + php_http_client_curl_handler_t *curl = userdata; + CURL *ch = curl->handle; + + if (val && Z_LVAL_P(val)) { + switch (Z_LVAL_P(val)) { + case CURL_TLSAUTH_SRP: + if (CURLE_OK == curl_easy_setopt(ch, CURLOPT_TLSAUTH_TYPE, PHP_HTTP_CURL_TLSAUTH_SRP)) { + return SUCCESS; + } + /* no break */ + default: + return FAILURE; + } + } + if (CURLE_OK != curl_easy_setopt(ch, CURLOPT_TLSAUTH_TYPE, PHP_HTTP_CURL_TLSAUTH_DEF)) { + return FAILURE; + } + return SUCCESS; +} +#endif + static void php_http_curle_options_init(php_http_options_t *registry TSRMLS_DC) { php_http_option_t *opt; @@ -1124,6 +1193,19 @@ static void php_http_curle_options_init(php_http_options_t *registry TSRMLS_DC) php_http_option_register(registry, ZEND_STRL("noproxy"), CURLOPT_NOPROXY, IS_STRING); #endif +#if PHP_HTTP_CURL_VERSION(7,37,0) + if ((opt = php_http_option_register(registry, ZEND_STRL("proxyheader"), CURLOPT_PROXYHEADER, IS_ARRAY))) { + opt->setter = php_http_curle_option_set_proxyheader; + } +#endif + +#if PHP_HTTP_CURL_VERSION(7,40,0) + if ((opt = php_http_option_register(registry, ZEND_STRL("unix_socket_path"), CURLOPT_UNIX_SOCKET_PATH, IS_STRING))) { + opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_STRLEN; + opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_BASEDIR; + } +#endif + /* dns */ if ((opt = php_http_option_register(registry, ZEND_STRL("dns_cache_timeout"), CURLOPT_DNS_CACHE_TIMEOUT, IS_LONG))) { Z_LVAL(opt->defval) = 60; @@ -1336,6 +1418,9 @@ static void php_http_curle_options_init(php_http_options_t *registry TSRMLS_DC) ZVAL_BOOL(&opt->defval, 1); opt->setter = php_http_curle_option_set_ssl_verifyhost; } +#if PHP_HTTP_CURL_VERSION(7,41,0) + php_http_option_register(registry, ZEND_STRL("verifystatus"), CURLOPT_SSL_VERIFYSTATUS, IS_BOOL); +#endif php_http_option_register(registry, ZEND_STRL("cipher_list"), CURLOPT_SSL_CIPHER_LIST, IS_STRING); if ((opt = php_http_option_register(registry, ZEND_STRL("cainfo"), CURLOPT_CAINFO, IS_STRING))) { opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_STRLEN; @@ -1378,6 +1463,23 @@ static void php_http_curle_options_init(php_http_options_t *registry TSRMLS_DC) if ((opt = php_http_option_register(registry, ZEND_STRL("enable_alpn"), CURLOPT_SSL_ENABLE_ALPN, IS_BOOL))) { ZVAL_BOOL(&opt->defval, 1); } +#endif +#if PHP_HTTP_CURL_VERSION(7,39,0) + if ((opt = php_http_option_register(registry, ZEND_STRL("pinned_publickey"), CURLOPT_PINNEDPUBLICKEY, IS_STRING))) { + opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_STRLEN; + opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_BASEDIR; + } +#endif +#if PHP_HTTP_CURL_VERSION(7,21,4) + if ((opt = php_http_option_register(registry, ZEND_STRL("tlsauthtype"), CURLOPT_TLSAUTH_TYPE, IS_LONG))) { + opt->setter = php_http_curle_option_set_ssl_tlsauthtype; + } + if ((opt = php_http_option_register(registry, ZEND_STRL("tlsauthuser"), CURLOPT_TLSAUTH_USERNAME, IS_STRING))) { + opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_STRLEN; + } + if ((opt = php_http_option_register(registry, ZEND_STRL("tlsauthpass"), CURLOPT_TLSAUTH_PASSWORD, IS_STRING))) { + opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_STRLEN; + } #endif } } @@ -1522,6 +1624,10 @@ static STATUS php_http_client_curl_handler_reset(php_http_client_curl_handler_t curl_slist_free_all(curl->options.headers); curl->options.headers = NULL; } + if (curl->options.proxyheaders) { + curl_slist_free_all(curl->options.proxyheaders); + curl->options.proxyheaders = NULL; + } php_http_buffer_reset(&curl->options.cookies); php_http_buffer_reset(&curl->options.ranges); @@ -1544,12 +1650,8 @@ static php_http_client_curl_handler_t *php_http_client_curl_handler_init(php_htt handler->rf = rf; handler->client = h; handler->handle = handle; - handler->request.buffer = php_http_buffer_init(NULL); - handler->request.parser = php_http_message_parser_init(NULL TSRMLS_CC); - handler->request.message = php_http_message_init(NULL, 0, NULL TSRMLS_CC); - handler->response.buffer = php_http_buffer_init(NULL); - handler->response.parser = php_http_message_parser_init(NULL TSRMLS_CC); - handler->response.message = php_http_message_init(NULL, 0, NULL TSRMLS_CC); + handler->response.body = php_http_message_body_init(NULL, NULL TSRMLS_CC); + php_http_buffer_init(&handler->response.headers); php_http_buffer_init(&handler->options.cookies); php_http_buffer_init(&handler->options.ranges); zend_hash_init(&handler->options.cache, 0, NULL, ZVAL_PTR_DTOR, 0); @@ -1562,11 +1664,11 @@ static php_http_client_curl_handler_t *php_http_client_curl_handler_init(php_htt curl_easy_setopt(handle, CURLOPT_AUTOREFERER, 1L); curl_easy_setopt(handle, CURLOPT_VERBOSE, 1L); curl_easy_setopt(handle, CURLOPT_NOPROGRESS, 0L); - curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, NULL); - curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, php_http_curle_dummy_callback); + curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, php_http_curle_header_callback); + curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, php_http_curle_body_callback); curl_easy_setopt(handle, CURLOPT_DEBUGFUNCTION, php_http_curle_raw_callback); curl_easy_setopt(handle, CURLOPT_READFUNCTION, php_http_curle_read_callback); - curl_easy_setopt(handle, CURLOPT_IOCTLFUNCTION, php_http_curle_ioctl_callback); + curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, php_http_curle_seek_callback); #if PHP_HTTP_CURL_VERSION(7,32,0) curl_easy_setopt(handle, CURLOPT_XFERINFOFUNCTION, php_http_curle_xferinfo_callback); curl_easy_setopt(handle, CURLOPT_XFERINFODATA, handler); @@ -1575,6 +1677,8 @@ static php_http_client_curl_handler_t *php_http_client_curl_handler_init(php_htt curl_easy_setopt(handle, CURLOPT_PROGRESSDATA, handler); #endif curl_easy_setopt(handle, CURLOPT_DEBUGDATA, handler); + curl_easy_setopt(handle, CURLOPT_WRITEDATA, handler); + curl_easy_setopt(handle, CURLOPT_HEADERDATA, handler); php_http_client_curl_handler_reset(handler); @@ -1630,6 +1734,9 @@ static STATUS php_http_client_curl_handler_prepare(php_http_client_curl_handler_ } } + /* apply options */ + php_http_options_apply(&php_http_curle_options, enqueue->options, curl); + /* request headers */ php_http_message_update_headers(msg); if (zend_hash_num_elements(&msg->hdrs)) { @@ -1674,19 +1781,17 @@ static STATUS php_http_client_curl_handler_prepare(php_http_client_curl_handler_ * does not allow a request body. */ php_stream_rewind(php_http_message_body_stream(msg->body)); - curl_easy_setopt(curl->handle, CURLOPT_IOCTLDATA, msg->body); + curl_easy_setopt(curl->handle, CURLOPT_SEEKDATA, msg->body); 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); } else { - curl_easy_setopt(curl->handle, CURLOPT_IOCTLDATA, NULL); + curl_easy_setopt(curl->handle, CURLOPT_SEEKDATA, NULL); curl_easy_setopt(curl->handle, CURLOPT_READDATA, NULL); curl_easy_setopt(curl->handle, CURLOPT_INFILESIZE, 0L); curl_easy_setopt(curl->handle, CURLOPT_POSTFIELDSIZE, 0L); } - php_http_options_apply(&php_http_curle_options, enqueue->options, curl); - return SUCCESS; } @@ -1711,12 +1816,8 @@ static void php_http_client_curl_handler_dtor(php_http_client_curl_handler_t *ha php_resource_factory_handle_dtor(handler->rf, handler->handle TSRMLS_CC); php_resource_factory_free(&handler->rf); - php_http_message_parser_free(&handler->request.parser); - php_http_message_free(&handler->request.message); - php_http_buffer_free(&handler->request.buffer); - php_http_message_parser_free(&handler->response.parser); - php_http_message_free(&handler->response.message); - php_http_buffer_free(&handler->response.buffer); + php_http_message_body_free(&handler->response.body); + php_http_buffer_dtor(&handler->response.headers); php_http_buffer_dtor(&handler->options.ranges); php_http_buffer_dtor(&handler->options.cookies); zend_hash_destroy(&handler->options.cache); @@ -2123,6 +2224,9 @@ PHP_MINIT_FUNCTION(http_client_curl) */ REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "HTTP_VERSION_1_0", CURL_HTTP_VERSION_1_0, CONST_CS|CONST_PERSISTENT); REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "HTTP_VERSION_1_1", CURL_HTTP_VERSION_1_1, CONST_CS|CONST_PERSISTENT); +#if PHP_HTTP_CURL_VERSION(7,33,0) + REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "HTTP_VERSION_2_0", CURL_HTTP_VERSION_2_0, CONST_CS|CONST_PERSISTENT); +#endif REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "HTTP_VERSION_ANY", CURL_HTTP_VERSION_NONE, CONST_CS|CONST_PERSISTENT); /* @@ -2137,6 +2241,9 @@ PHP_MINIT_FUNCTION(http_client_curl) REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "SSL_VERSION_SSLv2", CURL_SSLVERSION_SSLv2, CONST_CS|CONST_PERSISTENT); REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "SSL_VERSION_SSLv3", CURL_SSLVERSION_SSLv3, CONST_CS|CONST_PERSISTENT); REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "SSL_VERSION_ANY", CURL_SSLVERSION_DEFAULT, CONST_CS|CONST_PERSISTENT); +#if PHP_HTTP_CURL_VERSION(7,21,4) + REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "TLSAUTH_SRP", CURL_TLSAUTH_SRP, CONST_CS|CONST_PERSISTENT); +#endif /* * DNS IPvX resolving diff --git a/php_http_env_response.c b/php_http_env_response.c index 52d561a..a4431b6 100644 --- a/php_http_env_response.c +++ b/php_http_env_response.c @@ -883,9 +883,11 @@ typedef struct php_http_env_response_stream_ctx { long status_code; php_stream *stream; + php_http_message_t *request; unsigned started:1; unsigned finished:1; + unsigned chunked:1; } php_http_env_response_stream_ctx_t; static STATUS php_http_env_response_stream_init(php_http_env_response_t *r, void *init_arg) @@ -903,6 +905,13 @@ static STATUS php_http_env_response_stream_init(php_http_env_response_t *r, void zend_hash_init(&ctx->header, 0, NULL, ZVAL_PTR_DTOR, 0); php_http_version_init(&ctx->version, 1, 1 TSRMLS_CC); ctx->status_code = 200; + ctx->chunked = 1; + ctx->request = get_request(r->options TSRMLS_CC); + + /* there are some limitations regarding TE:chunked, see https://tools.ietf.org/html/rfc7230#section-3.3.1 */ + if (ctx->request && ctx->request->http.version.major == 1 && ctx->request->http.version.minor == 0) { + ctx->version.minor = 0; + } r->ctx = ctx; @@ -929,6 +938,12 @@ static void php_http_env_response_stream_header(php_http_env_response_stream_ctx } else { zval *tmp = php_http_ztyp(IS_STRING, *val); + if (ctx->chunked) { + /* disable chunked transfer encoding if we've got an explicit content-length */ + if (!strncasecmp(Z_STRVAL_P(tmp), "Content-Length:", lenof("Content-Length:"))) { + ctx->chunked = 0; + } + } php_stream_write(ctx->stream, Z_STRVAL_P(tmp), Z_STRLEN_P(tmp)); php_stream_write_string(ctx->stream, PHP_HTTP_CRLF); zval_ptr_dtor(&tmp); @@ -942,9 +957,26 @@ static STATUS php_http_env_response_stream_start(php_http_env_response_stream_ct } php_stream_printf(ctx->stream TSRMLS_CC, "HTTP/%u.%u %ld %s" PHP_HTTP_CRLF, ctx->version.major, ctx->version.minor, ctx->status_code, php_http_env_get_response_status_for_code(ctx->status_code)); + + /* there are some limitations regarding TE:chunked, see https://tools.ietf.org/html/rfc7230#section-3.3.1 */ + if (ctx->version.major == 1 && ctx->version.minor == 0) { + ctx->chunked = 0; + } else if (ctx->status_code == 204 || ctx->status_code/100 == 1) { + ctx->chunked = 0; + } else if (ctx->request && ctx->status_code/100 == 2 && !strcasecmp(ctx->request->http.info.request.method, "CONNECT")) { + ctx->chunked = 0; + } + php_http_env_response_stream_header(ctx, &ctx->header TSRMLS_CC); + + /* enable chunked transfer encoding */ + if (ctx->chunked) { + php_stream_write_string(ctx->stream, "Transfer-Encoding: chunked" PHP_HTTP_CRLF); + } php_stream_write_string(ctx->stream, PHP_HTTP_CRLF); + ctx->started = 1; + return SUCCESS; } static long php_http_env_response_stream_get_status(php_http_env_response_t *r) @@ -1061,10 +1093,18 @@ static STATUS php_http_env_response_stream_write(php_http_env_response_t *r, con } } + if (stream_ctx->chunked && 0 == php_stream_printf(stream_ctx->stream TSRMLS_CC, "%lx" PHP_HTTP_CRLF, (unsigned long) data_len)) { + return FAILURE; + } + if (data_len != php_stream_write(stream_ctx->stream, data_str, data_len)) { return FAILURE; } + if (stream_ctx->chunked && 2 != php_stream_write_string(stream_ctx->stream, PHP_HTTP_CRLF)) { + return FAILURE; + } + return SUCCESS; } static STATUS php_http_env_response_stream_flush(php_http_env_response_t *r) @@ -1097,6 +1137,10 @@ static STATUS php_http_env_response_stream_finish(php_http_env_response_t *r) } } + if (stream_ctx->chunked && 5 != php_stream_write_string(stream_ctx->stream, "0" PHP_HTTP_CRLF PHP_HTTP_CRLF)) { + return FAILURE; + } + stream_ctx->finished = 1; return SUCCESS; @@ -1367,6 +1411,7 @@ static PHP_METHOD(HttpEnvResponse, send) #else php_end_ob_buffers(1 TSRMLS_CC); #endif + if (zstream) { php_http_env_response_t *r; diff --git a/php_http_info.c b/php_http_info.c index 7efd70e..9050919 100644 --- a/php_http_info.c +++ b/php_http_info.c @@ -65,15 +65,15 @@ php_http_info_t *php_http_info_parse(php_http_info_t *info, const char *pre_head } /* there must be HTTP/1.x in the line */ - if (!(http = php_http_locate_str(pre_header, end - pre_header, "HTTP/1.", lenof("HTTP/1.")))) { + if (!(http = php_http_locate_str(pre_header, end - pre_header, "HTTP/", lenof("HTTP/")))) { return NULL; } info = php_http_info_init(info TSRMLS_CC); - /* and nothing than SPACE or NUL after HTTP/1.x */ + /* and nothing than SPACE or NUL after HTTP/X.x */ if (!php_http_version_parse(&info->http.version, http TSRMLS_CC) - || (http[lenof("HTTP/1.1")] && (!PHP_HTTP_IS_CTYPE(space, http[lenof("HTTP/1.1")])))) { + || (http[lenof("HTTP/X.x")] && (!PHP_HTTP_IS_CTYPE(space, http[lenof("HTTP/X.x")])))) { if (free_info) { php_http_info_free(&info); } @@ -90,13 +90,22 @@ php_http_info_t *php_http_info_parse(php_http_info_t *info, const char *pre_head /* is response */ if (pre_header == http) { - char *status = NULL; - const char *code = http + sizeof("HTTP/1.1"); + const char *status = NULL, *code = http + sizeof("HTTP/X.x"); info->type = PHP_HTTP_RESPONSE; while (' ' == *code) ++code; if (code && end > code) { - PHP_HTTP_INFO(info).response.code = strtol(code, &status, 10); + /* 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'); + PHP_HTTP_INFO(info).response.code += *code++ - '0'; + if (PHP_HTTP_INFO(info).response.code < 100 || PHP_HTTP_INFO(info).response.code > 599) { + if (free_info) { + php_http_info_free(&info); + } + return NULL; + } + status = code; } else { PHP_HTTP_INFO(info).response.code = 0; } @@ -111,16 +120,25 @@ php_http_info_t *php_http_info_parse(php_http_info_t *info, const char *pre_head } /* is request */ - else if (*(http - 1) == ' ' && (!http[lenof("HTTP/1.x")] || http[lenof("HTTP/1.x")] == '\r' || http[lenof("HTTP/1.x")] == '\n')) { + else if (*(http - 1) == ' ' && (!http[lenof("HTTP/X.x")] || http[lenof("HTTP/X.x")] == '\r' || http[lenof("HTTP/X.x")] == '\n')) { const char *url = strchr(pre_header, ' '); info->type = PHP_HTTP_REQUEST; if (url && http > url) { - PHP_HTTP_INFO(info).request.method = estrndup(pre_header, url - pre_header); + size_t url_len = url - pre_header; + + PHP_HTTP_INFO(info).request.method = estrndup(pre_header, url_len); + while (' ' == *url) ++url; while (' ' == *(http-1)) --http; + if (http > url) { - PHP_HTTP_INFO(info).request.url = php_http_url_parse(url, http - url, ~0 TSRMLS_CC); + /* 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); + } else { + PHP_HTTP_INFO(info).request.url = php_http_url_parse_authority(url, http - url, ~0 TSRMLS_CC); + } } else { PTR_SET(PHP_HTTP_INFO(info).request.method, NULL); return NULL; @@ -133,7 +151,7 @@ php_http_info_t *php_http_info_parse(php_http_info_t *info, const char *pre_head return info; } - /* some darn header containing HTTP/1.x */ + /* some darn header containing HTTP/X.x */ else { if (free_info) { php_http_info_free(&info); diff --git a/php_http_info.h b/php_http_info.h index d31b505..4f02908 100644 --- a/php_http_info.h +++ b/php_http_info.h @@ -18,7 +18,9 @@ #define PHP_HTTP_INFO_REQUEST_FMT_ARGS(_http_ptr, tmp, eol) "%s %s HTTP/%u.%u" eol, \ (_http_ptr)->info.request.method?(_http_ptr)->info.request.method:"UNKNOWN", \ - (_http_ptr)->info.request.url?php_http_url_to_string((_http_ptr)->info.request.url, &(tmp), NULL, 0):"/", \ + (_http_ptr)->info.request.method&&!strcasecmp((_http_ptr)->info.request.method,"CONNECT")?( \ + (_http_ptr)->info.request.url?php_http_url_authority_to_string((_http_ptr)->info.request.url, &(tmp), NULL):"0"):( \ + (_http_ptr)->info.request.url?php_http_url_to_string((_http_ptr)->info.request.url, &(tmp), NULL, 0):"/"), \ (_http_ptr)->version.major||(_http_ptr)->version.major?(_http_ptr)->version.major:1, \ (_http_ptr)->version.major||(_http_ptr)->version.minor?(_http_ptr)->version.minor:1 diff --git a/php_http_message.c b/php_http_message.c index 8a7e564..5a278f6 100644 --- a/php_http_message.c +++ b/php_http_message.c @@ -1161,17 +1161,19 @@ static PHP_METHOD(HttpMessage, getHeader) if (!header_ce) { RETURN_ZVAL(header, 1, 1); } else if (instanceof_function(header_ce, php_http_header_class_entry TSRMLS_CC)) { + php_http_object_method_t cb; zval *header_name, **argv[2]; MAKE_STD_ZVAL(header_name); ZVAL_STRINGL(header_name, header_str, header_len, 1); - Z_ADDREF_P(header); argv[0] = &header_name; argv[1] = &header; object_init_ex(return_value, header_ce); - php_http_method_call(return_value, ZEND_STRL("__construct"), 2, argv, NULL TSRMLS_CC); + php_http_object_method_init(&cb, return_value, ZEND_STRL("__construct") TSRMLS_CC); + php_http_object_method_call(&cb, return_value, NULL, 2, argv TSRMLS_CC); + php_http_object_method_dtor(&cb); zval_ptr_dtor(&header_name); zval_ptr_dtor(&header); diff --git a/php_http_object.c b/php_http_object.c index abf7229..7d902ea 100644 --- a/php_http_object.c +++ b/php_http_object.c @@ -53,30 +53,71 @@ STATUS php_http_new(zend_object_value *ovp, zend_class_entry *ce, php_http_new_t return SUCCESS; } -STATUS php_http_method_call(zval *object, const char *method_str, size_t method_len, int argc, zval **argv[], zval **retval_ptr TSRMLS_DC) +php_http_object_method_t *php_http_object_method_init(php_http_object_method_t *cb, zval *zobject, const char *method_str, size_t method_len TSRMLS_DC) +{ + zval *zfn; + + if (!cb) { + cb = ecalloc(1, sizeof(*cb)); + } else { + memset(cb, 0, sizeof(*cb)); + } + + MAKE_STD_ZVAL(zfn); + ZVAL_STRINGL(zfn, method_str, method_len, 1); + + cb->fci.size = sizeof(cb->fci); + cb->fci.function_name = zfn; + cb->fcc.initialized = 1; + cb->fcc.calling_scope = cb->fcc.called_scope = Z_OBJCE_P(zobject); + cb->fcc.function_handler = Z_OBJ_HT_P(zobject)->get_method(&zobject, Z_STRVAL_P(cb->fci.function_name), Z_STRLEN_P(cb->fci.function_name), NULL TSRMLS_CC); + + return cb; +} + +void php_http_object_method_dtor(php_http_object_method_t *cb) +{ + if (cb->fci.function_name) { + zval_ptr_dtor(&cb->fci.function_name); + cb->fci.function_name = NULL; + } +} + +void php_http_object_method_free(php_http_object_method_t **cb) +{ + if (*cb) { + php_http_object_method_dtor(*cb); + efree(*cb); + *cb = NULL; + } +} + +STATUS php_http_object_method_call(php_http_object_method_t *cb, zval *zobject, zval **retval_ptr, int argc, zval ***args TSRMLS_DC) { - zend_fcall_info fci; - zval zmethod; - zval *retval; STATUS rv; + zval *retval = NULL; - fci.size = sizeof(fci); - fci.object_ptr = object; - fci.function_name = &zmethod; - fci.retval_ptr_ptr = retval_ptr ? retval_ptr : &retval; - fci.param_count = argc; - fci.params = argv; - fci.no_separation = 1; - fci.symbol_table = NULL; - fci.function_table = NULL; + Z_ADDREF_P(zobject); + cb->fci.object_ptr = zobject; + cb->fcc.object_ptr = zobject; - INIT_PZVAL(&zmethod); - ZVAL_STRINGL(&zmethod, method_str, method_len, 0); - rv = zend_call_function(&fci, NULL TSRMLS_CC); + cb->fci.retval_ptr_ptr = retval_ptr ? retval_ptr : &retval; + cb->fci.param_count = argc; + cb->fci.params = args; + + if (cb->fcc.called_scope != Z_OBJCE_P(zobject)) { + cb->fcc.called_scope = Z_OBJCE_P(zobject); + cb->fcc.function_handler = Z_OBJ_HT_P(zobject)->get_method(&zobject, Z_STRVAL_P(cb->fci.function_name), Z_STRLEN_P(cb->fci.function_name), NULL TSRMLS_CC); + } + + rv = zend_call_function(&cb->fci, &cb->fcc TSRMLS_CC); + + zval_ptr_dtor(&zobject); if (!retval_ptr && retval) { zval_ptr_dtor(&retval); } + return rv; } diff --git a/php_http_object.h b/php_http_object.h index 63730b4..1fbd817 100644 --- a/php_http_object.h +++ b/php_http_object.h @@ -24,7 +24,16 @@ zend_object_value php_http_object_new_ex(zend_class_entry *ce, void *nothing, ph typedef zend_object_value (*php_http_new_t)(zend_class_entry *ce, void *, void ** TSRMLS_DC); STATUS php_http_new(zend_object_value *ov, zend_class_entry *ce, php_http_new_t create, zend_class_entry *parent_ce, void *intern_ptr, void **obj_ptr TSRMLS_DC); -STATUS php_http_method_call(zval *object, const char *method_str, size_t method_len, int argc, zval **argv[], zval **retval_ptr TSRMLS_DC); + +typedef struct php_http_method { + zend_fcall_info fci; + zend_fcall_info_cache fcc; +} php_http_object_method_t; + +php_http_object_method_t *php_http_object_method_init(php_http_object_method_t *cb, zval *zobject, const char *method_str, size_t method_len TSRMLS_DC); +STATUS php_http_object_method_call(php_http_object_method_t *cb, zval *zobject, zval **retval, int argc, zval ***args TSRMLS_DC); +void php_http_object_method_dtor(php_http_object_method_t *cb); +void php_http_object_method_free(php_http_object_method_t **cb); #endif diff --git a/php_http_url.c b/php_http_url.c index b4a614e..5316265 100644 --- a/php_http_url.c +++ b/php_http_url.c @@ -325,8 +325,8 @@ php_http_url_t *php_http_url_mod(const php_http_url_t *old_url, const php_http_u } /* unset default ports */ if (url(buf)->port) { - if ( ((url(buf)->port == 80) && !strcmp(url(buf)->scheme, "http")) - || ((url(buf)->port ==443) && !strcmp(url(buf)->scheme, "https")) + if ( ((url(buf)->port == 80) && url(buf)->scheme && !strcmp(url(buf)->scheme, "http")) + || ((url(buf)->port ==443) && url(buf)->scheme && !strcmp(url(buf)->scheme, "https")) ) { url(buf)->port = 0; } @@ -398,6 +398,42 @@ char *php_http_url_to_string(const php_http_url_t *url, char **url_str, size_t * return buf.data; } +char *php_http_url_authority_to_string(const php_http_url_t *url, char **url_str, size_t *url_len) +{ + php_http_buffer_t buf; + + php_http_buffer_init(&buf); + + if (url->user && *url->user) { + php_http_buffer_appendl(&buf, url->user); + if (url->pass && *url->pass) { + php_http_buffer_appends(&buf, ":"); + php_http_buffer_appendl(&buf, url->pass); + } + php_http_buffer_appends(&buf, "@"); + } + + if (url->host && *url->host) { + php_http_buffer_appendl(&buf, url->host); + if (url->port) { + php_http_buffer_appendf(&buf, ":%hu", url->port); + } + } + + php_http_buffer_shrink(&buf); + php_http_buffer_fix(&buf); + + if (url_len) { + *url_len = buf.used; + } + + if (url_str) { + *url_str = buf.data; + } + + return buf.data; +} + php_http_url_t *php_http_url_from_zval(zval *value, unsigned flags TSRMLS_DC) { zval *zcpy; @@ -890,7 +926,9 @@ static STATUS parse_hostinfo(struct parse_state *state, const char *ptr) break; default: - if (port) { + if (ptr == end) { + break; + } else if (port) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to parse port; unexpected byte 0x%02x at pos %u in '%s'", (unsigned char) *ptr, (unsigned) (ptr - tmp), tmp); @@ -961,6 +999,7 @@ static const char *parse_authority(struct parse_state *state) case '?': case '#': case '\0': + EOD: /* host delimiter */ if (tmp != state->ptr && SUCCESS != parse_hostinfo(state, tmp)) { return NULL; @@ -969,7 +1008,8 @@ static const char *parse_authority(struct parse_state *state) } } while (++state->ptr <= state->end); - return NULL; + --state->ptr; + goto EOD; } static const char *parse_path(struct parse_state *state) @@ -1270,6 +1310,33 @@ php_http_url_t *php_http_url_parse(const char *str, size_t len, unsigned flags T return (php_http_url_t *) state; } +php_http_url_t *php_http_url_parse_authority(const char *str, size_t len, unsigned flags TSRMLS_DC) +{ + size_t maxlen = 3 * len; + struct parse_state *state = ecalloc(1, sizeof(*state) + maxlen); + + state->end = str + len; + state->ptr = str; + state->flags = flags; + state->maxlen = maxlen; + TSRMLS_SET_CTX(state->ts); + + if (!(state->ptr = parse_authority(state))) { + efree(state); + return NULL; + } + + if (state->ptr != state->end) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "Failed to parse URL authority, unexpected character at pos %u in '%s'", + (unsigned) (state->ptr - str), str); + efree(state); + return NULL; + } + + return (php_http_url_t *) state; +} + ZEND_BEGIN_ARG_INFO_EX(ai_HttpUrl___construct, 0, 0, 0) ZEND_ARG_INFO(0, old_url) ZEND_ARG_INFO(0, new_url) diff --git a/php_http_url.h b/php_http_url.h index fb91932..9268048 100644 --- a/php_http_url.h +++ b/php_http_url.h @@ -15,6 +15,7 @@ #include +/* php_http_url_mod() */ #define PHP_HTTP_URL_REPLACE 0x000 #define PHP_HTTP_URL_JOIN_PATH 0x001 #define PHP_HTTP_URL_JOIN_QUERY 0x002 @@ -57,6 +58,7 @@ typedef struct php_http_url { } php_http_url_t; PHP_HTTP_API php_http_url_t *php_http_url_parse(const char *str, size_t len, unsigned flags TSRMLS_DC); +PHP_HTTP_API php_http_url_t *php_http_url_parse_authority(const char *str, size_t len, unsigned flags TSRMLS_DC); /* deprecated */ PHP_HTTP_API void php_http_url(int flags, const php_url *old_url, const php_url *new_url, php_url **url_ptr, char **url_str, size_t *url_len TSRMLS_DC); /* use this instead */ @@ -66,9 +68,9 @@ PHP_HTTP_API php_http_url_t *php_http_url_from_struct(HashTable *ht); PHP_HTTP_API php_http_url_t *php_http_url_from_zval(zval *value, unsigned flags TSRMLS_DC); PHP_HTTP_API HashTable *php_http_url_to_struct(const php_http_url_t *url, zval *strct TSRMLS_DC); PHP_HTTP_API char *php_http_url_to_string(const php_http_url_t *url, char **url_str, size_t *url_len, zend_bool persistent); +PHP_HTTP_API char *php_http_url_authority_to_string(const php_http_url_t *url, char **url_str, size_t *url_len); PHP_HTTP_API void php_http_url_free(php_http_url_t **url); - PHP_HTTP_API STATUS php_http_url_encode_hash(HashTable *hash, const char *pre_encoded_str, size_t pre_encoded_len, char **encoded_str, size_t *encoded_len TSRMLS_DC); PHP_HTTP_API STATUS php_http_url_encode_hash_ex(HashTable *hash, php_http_buffer_t *qstr, const char *arg_sep_str, size_t arg_sep_len, const char *val_sep_str, size_t val_sep_len, const char *pre_encoded_str, size_t pre_encoded_len TSRMLS_DC); diff --git a/php_http_version.c b/php_http_version.c index f4fcfc8..7adef9d 100644 --- a/php_http_version.c +++ b/php_http_version.c @@ -27,7 +27,7 @@ php_http_version_t *php_http_version_init(php_http_version_t *v, unsigned major, php_http_version_t *php_http_version_parse(php_http_version_t *v, const char *str TSRMLS_DC) { long major, minor; - char separator = 0, *stop = NULL; + char separator = 0; register const char *ptr = str; switch (*ptr) { @@ -40,16 +40,16 @@ php_http_version_t *php_http_version_parse(php_http_version_t *v, const char *st ++ptr; /* no break */ default: - major = strtol(ptr, &stop, 10); - if (stop && stop != ptr && major != LONG_MIN && major != LONG_MAX) { - separator = *stop; + /* rfc7230#2.6 The HTTP version number consists of two decimal digits separated by a "." (period or decimal point) */ + major = *ptr++ - '0'; + if (major >= 0 && major <= 9) { + separator = *ptr++; if (separator) { if (separator != '.' && separator != ',') { - php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Non-standard version separator '%c' in HTTP protocol version '%s'", separator, ptr); + php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Non-standard version separator '%c' in HTTP protocol version '%s'", separator, ptr - 2); } - ptr = stop + 1; - minor = strtol(ptr, &stop, 10); - if (minor != LONG_MIN && minor != LONG_MAX) { + minor = *ptr - '0'; + if (minor >= 0 && minor <= 9) { return php_http_version_init(v, major, minor TSRMLS_CC); } } diff --git a/tests/client011.phpt b/tests/client011.phpt index 17c60ed..284edb2 100644 --- a/tests/client011.phpt +++ b/tests/client011.phpt @@ -31,13 +31,8 @@ echo $client->getHistory()->toString(true); Done --EXPECTF-- Test -POST /ext-http/.print_request.php HTTP/1.1 -User-Agent: %s -Host: dev.iworks.at -Accept: */* +POST http://dev.iworks.at/ext-http/.print_request.php HTTP/1.1 Content-Length: 6 -Content-Type: application/x-www-form-urlencoded -X-Original-Content-Length: 6 foobar HTTP/1.1 200 OK @@ -50,13 +45,8 @@ Content-Length: 19 string(6) "foobar" -POST /ext-http/.print_request.php HTTP/1.1 -User-Agent: %s -Host: dev.iworks.at -Accept: */* +POST http://dev.iworks.at/ext-http/.print_request.php HTTP/1.1 Content-Length: 6 -Content-Type: application/x-www-form-urlencoded -X-Original-Content-Length: 6 foobar HTTP/1.1 200 OK @@ -69,13 +59,8 @@ Content-Length: 19 string(6) "foobar" -POST /ext-http/.print_request.php HTTP/1.1 -User-Agent: %s -Host: dev.iworks.at -Accept: */* +POST http://dev.iworks.at/ext-http/.print_request.php HTTP/1.1 Content-Length: 6 -Content-Type: application/x-www-form-urlencoded -X-Original-Content-Length: 6 foobar HTTP/1.1 200 OK diff --git a/tests/client012.phpt b/tests/client012.phpt index ad6c2eb..e4c188c 100644 --- a/tests/client012.phpt +++ b/tests/client012.phpt @@ -12,12 +12,12 @@ echo "Test\n"; $client = new http\Client; -$client->setSslOptions(array("verify_peer" => true)); -$client->addSslOptions(array("verify_host" => 2)); +$client->setSslOptions(array("verifypeer" => true)); +$client->addSslOptions(array("verifyhost" => 2)); var_dump( array( - "verify_peer" => true, - "verify_host" => 2, + "verifypeer" => true, + "verifyhost" => 2, ) === $client->getSslOptions() ); diff --git a/tests/client017.phpt b/tests/client017.phpt new file mode 100644 index 0000000..ea54146 --- /dev/null +++ b/tests/client017.phpt @@ -0,0 +1,33 @@ +--TEST-- +client request gzip +--SKIPIF-- + +--FILE-- +setOptions(["compress" => true]); + +$client->enqueue(new http\Client\Request("GET", "http://dev.iworks.at/ext-http/.print_request.php")); +$client->send(); + +echo $client->getResponse(); + +?> +===DONE=== +--EXPECTF-- +Test +HTTP/1.1 200 OK +Vary: Accept-Encoding +Content-Type: text/html +Date: %s +Server: %s +X-Original-Transfer-Encoding: chunked +X-Original-Content-Encoding: gzip +===DONE=== \ No newline at end of file diff --git a/tests/envresponse006.phpt b/tests/envresponse006.phpt index 76141c1..248b999 100644 --- a/tests/envresponse006.phpt +++ b/tests/envresponse006.phpt @@ -22,10 +22,15 @@ var_dump(stream_get_contents($f)); Done --EXPECT-- Test -string(77) "HTTP/1.1 200 OK +string(115) "HTTP/1.1 200 OK Accept-Ranges: bytes Foo: bar, baz ETag: "8c736521" +Transfer-Encoding: chunked -foo" +3 +foo +0 + +" Done diff --git a/tests/envresponse007.phpt b/tests/envresponse007.phpt index 5495fe2..d41be58 100644 --- a/tests/envresponse007.phpt +++ b/tests/envresponse007.phpt @@ -34,6 +34,11 @@ Accept-Ranges: bytes%c X-Powered-By: %s%c Content-Type: text/plain%c Content-Range: bytes 2-4/9%c +Transfer-Encoding: chunked%c %c -234" +3%c +234%c +0%c +%c +" Done diff --git a/tests/envresponse008.phpt b/tests/envresponse008.phpt index 379ab57..acbda89 100644 --- a/tests/envresponse008.phpt +++ b/tests/envresponse008.phpt @@ -18,6 +18,7 @@ $r->send($f); rewind($f); var_dump(stream_get_contents($f)); +?> --EXPECTREGEX-- string\(\d+\) "HTTP\/1\.1 200 OK Accept-Ranges: bytes @@ -26,5 +27,10 @@ Content-Encoding: gzip Vary: Accept-Encoding ETag: "\w+-\w+-\w+" Last-Modified: \w+, \d+ \w+ \d{4} \d\d:\d\d:\d\d GMT +Transfer-Encoding: chunked +d0 \x1f\x8b\x08.+ +0 + +" diff --git a/tests/envresponse009.phpt b/tests/envresponse009.phpt index 2a337e4..1ca5fad 100644 --- a/tests/envresponse009.phpt +++ b/tests/envresponse009.phpt @@ -24,7 +24,9 @@ Accept-Ranges: bytes%c X-Powered-By: %s%c ETag: "abc"%c Last-Modified: %s%c +Transfer-Encoding: chunked%c %c +e1%c send($f); rewind($f); var_dump(stream_get_contents($f)); ?> +%c +0%c +%c " diff --git a/tests/envresponse010.phpt b/tests/envresponse010.phpt index d365497..9b040d5 100644 --- a/tests/envresponse010.phpt +++ b/tests/envresponse010.phpt @@ -25,5 +25,10 @@ Accept-Ranges: bytes%c X-Powered-By: PHP/%s%c Content-Type: text/plain%c Content-Range: bytes 2-4/311%c +Transfer-Encoding: chunked%c %c -php" +3%c +php%c +0%c +%c +" diff --git a/tests/envresponse014.phpt b/tests/envresponse014.phpt index ef85e2c..6b71aac 100644 --- a/tests/envresponse014.phpt +++ b/tests/envresponse014.phpt @@ -16,9 +16,12 @@ $res->send($f); rewind($f); var_dump(stream_get_contents($f)); --EXPECTF-- -string(96) "HTTP/1.1 416 Requested Range Not Satisfiable +string(129) "HTTP/1.1 416 Requested Range Not Satisfiable Accept-Ranges: bytes Content-Range: bytes */6 +Transfer-Encoding: chunked + +0 " diff --git a/tests/envresponse015.phpt b/tests/envresponse015.phpt index abad2bb..1e8b15e 100644 --- a/tests/envresponse015.phpt +++ b/tests/envresponse015.phpt @@ -32,5 +32,10 @@ echo stream_get_contents($f); HTTP/1.1 200 OK Accept-Ranges: bytes ETag: "fc8305a1" +Transfer-Encoding: chunked +9 foo: bar + +0 + diff --git a/tests/envresponse017.phpt b/tests/envresponse017.phpt new file mode 100644 index 0000000..ffa40cb --- /dev/null +++ b/tests/envresponse017.phpt @@ -0,0 +1,28 @@ +--TEST-- +env response stream: no chunked transfer encoding for CONNECTs +--SKIPIF-- + +--FILE-- +setRequestMethod("CONNECT"); +$req->setRequestUrl(["host"=>"www.example.com", "port"=>80]); + +echo $req; + +$res = new http\Env\Response; +$res->setEnvRequest($req); +$res->send(STDOUT); + +?> +===DONE=== +--EXPECTF-- +Test +CONNECT www.example.com:80 HTTP/1.1 +HTTP/1.1 200 OK + +===DONE=== diff --git a/tests/envresponsecookie001.phpt b/tests/envresponsecookie001.phpt index 1fe11a0..c0e93ed 100644 --- a/tests/envresponsecookie001.phpt +++ b/tests/envresponsecookie001.phpt @@ -23,4 +23,7 @@ Set-Cookie: foo=bar; max-age=60; Set-Cookie: baz=1; Set-Cookie: 123=1; ETag: "" +Transfer-Encoding: chunked + +0 diff --git a/tests/proxy.inc b/tests/proxy.inc new file mode 100644 index 0000000..89d31f4 --- /dev/null +++ b/tests/proxy.inc @@ -0,0 +1,26 @@ +getHeader("Proxy-Connection")) { + $response = new http\Env\Response; + $response->setHeader("Content-Length", 0); + $response->send($client); + + /* soak up the request following the connect */ + new http\Message($client, false); + } + + /* return the initial message as response body */ + $response = new http\Env\Response; + $response->getBody()->append($request); + $response->send($client); + } + return; + } +} \ No newline at end of file diff --git a/tests/proxy001.phpt b/tests/proxy001.phpt new file mode 100644 index 0000000..c8a2e63 --- /dev/null +++ b/tests/proxy001.phpt @@ -0,0 +1,48 @@ +--TEST-- +proxy - send proxy headers for a proxy request +--SKIPIF-- + +--FILE-- +setOptions(array( + "timeout" => 3, + "proxytunnel" => true, + "proxyheader" => array("Hello" => "there!"), + "proxyhost" => "localhost", + "proxyport" => $port, + )); + try { + $c->enqueue($r)->send(); + } catch (Exception $e) { + echo $e; + } + echo $c->getResponse()->getBody(); + while (!feof($pipes[1])) { + echo fgets($pipes[1]); + } + unset($r, $client); +} +?> +===DONE=== +--EXPECTF-- +Test +Server on port %d +CONNECT www.example.com:80 HTTP/1.1 +Host: www.example.com:80 +User-Agent: PECL_HTTP/%s PHP/%s libcurl/%s +Proxy-Connection: Keep-Alive +Hello: there! +Content-Length: 0 +===DONE=== diff --git a/tests/proxy002.phpt b/tests/proxy002.phpt new file mode 100644 index 0000000..86bee61 --- /dev/null +++ b/tests/proxy002.phpt @@ -0,0 +1,44 @@ +--TEST-- +proxy - don't send proxy headers for a standard request +--SKIPIF-- + +--FILE-- +setOptions(array( + "timeout" => 3, + "proxyheader" => array("Hello" => "there!"), + )); + try { + $c->enqueue($r)->send(); + } catch (Exception $e) { + echo $e; + } + echo $c->getResponse()->getBody(); + while (!feof($pipes[1])) { + echo fgets($pipes[1]); + } + unset($r, $client); +} +?> +===DONE=== +--EXPECTF-- +Test +Server on port %d +GET / HTTP/1.1 +User-Agent: PECL_HTTP/%s PHP/%s libcurl/%s +Host: localhost:%d +Accept: */* +Content-Length: 0 +===DONE=== diff --git a/tests/skipif.inc b/tests/skipif.inc index 28d9a13..4b2627a 100644 --- a/tests/skipif.inc +++ b/tests/skipif.inc @@ -8,8 +8,14 @@ function skip_online_test($message = "skip test requiring internet connection\n" } } -function skip_slow_test($message = "skip slow test") { +function skip_slow_test($message = "skip slow test\n") { if (getenv("SKIP_SLOW_TESTS")) { die($message); } } + +function skip_client_test($message = "skip need a client driver\n") { + if (!http\Client::getAvailableDrivers()) { + die($message); + } +}