From: Michael Wallner Date: Thu, 5 Feb 2015 10:18:19 +0000 (+0100) Subject: CURLOPT_PROXYHEADER support; default chunked encoding for stream X-Git-Tag: RELEASE_2_3_0_RC1~60 X-Git-Url: https://git.m6w6.name/?p=m6w6%2Fext-http;a=commitdiff_plain;h=22d8bd9cce8969e37269f1e817da0904a6af522c CURLOPT_PROXYHEADER support; default chunked encoding for stream responses --- diff --git a/php_http_client_curl.c b/php_http_client_curl.c index 01b0a0f..c6e80b4 100644 --- a/php_http_client_curl.c +++ b/php_http_client_curl.c @@ -70,6 +70,7 @@ typedef struct php_http_client_curl_handler { struct { HashTable cache; + struct curl_slist *proxyheaders; struct curl_slist *headers; struct curl_slist *resolve; php_http_buffer_t cookies; @@ -1095,6 +1096,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) { @@ -1146,6 +1184,12 @@ 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 + /* dns */ if ((opt = php_http_option_register(registry, ZEND_STRL("dns_cache_timeout"), CURLOPT_DNS_CACHE_TIMEOUT, IS_LONG))) { Z_LVAL(opt->defval) = 60; @@ -1550,6 +1594,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); diff --git a/php_http_env_response.c b/php_http_env_response.c index 52d561a..04c8a98 100644 --- a/php_http_env_response.c +++ b/php_http_env_response.c @@ -886,6 +886,7 @@ typedef struct php_http_env_response_stream_ctx { 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 +904,7 @@ 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; r->ctx = ctx; @@ -929,6 +931,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); @@ -943,7 +951,12 @@ 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)); php_http_env_response_stream_header(ctx, &ctx->header TSRMLS_CC); - php_stream_write_string(ctx->stream, PHP_HTTP_CRLF); + /* enable chunked transfer encoding */ + if (ctx->chunked) { + php_stream_write_string(ctx->stream, "Transfer-Encoding: chunked" PHP_HTTP_CRLF PHP_HTTP_CRLF); + } else { + php_stream_write_string(ctx->stream, PHP_HTTP_CRLF); + } ctx->started = 1; return SUCCESS; } @@ -1061,10 +1074,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 +1118,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 +1392,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/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/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); + } +}