CURLOPT_PROXYHEADER support; default chunked encoding for stream
authorMichael Wallner <mike@php.net>
Thu, 5 Feb 2015 10:18:19 +0000 (11:18 +0100)
committerMichael Wallner <mike@php.net>
Thu, 5 Feb 2015 10:18:46 +0000 (11:18 +0100)
responses

14 files changed:
php_http_client_curl.c
php_http_env_response.c
tests/envresponse006.phpt
tests/envresponse007.phpt
tests/envresponse008.phpt
tests/envresponse009.phpt
tests/envresponse010.phpt
tests/envresponse014.phpt
tests/envresponse015.phpt
tests/envresponsecookie001.phpt
tests/proxy.inc [new file with mode: 0644]
tests/proxy001.phpt [new file with mode: 0644]
tests/proxy002.phpt [new file with mode: 0644]
tests/skipif.inc

index 01b0a0f..c6e80b4 100644 (file)
@@ -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);
index 52d561a..04c8a98 100644 (file)
@@ -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;
 
index 76141c1..248b999 100644 (file)
@@ -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
index 5495fe2..d41be58 100644 (file)
@@ -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
index 379ab57..acbda89 100644 (file)
@@ -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
+
+"
index 2a337e4..1ca5fad 100644 (file)
@@ -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
 <?php
 $f = tmpfile();
 $r = new http\Env\Response;
@@ -35,5 +37,8 @@ $r->send($f);
 rewind($f);
 var_dump(stream_get_contents($f));
 ?>
+%c
+0%c
+%c
 "
 
index d365497..9b040d5 100644 (file)
@@ -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
+"
index ef85e2c..6b71aac 100644 (file)
@@ -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
 
 "
 
index abad2bb..1e8b15e 100644 (file)
@@ -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
+
index 1fe11a0..c0e93ed 100644 (file)
@@ -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 (file)
index 0000000..89d31f4
--- /dev/null
@@ -0,0 +1,26 @@
+<?php 
+
+foreach (range(8000, 9000) as $port) {
+       if (($server = stream_socket_server("tcp://localhost:$port"))) {
+               fprintf(STDERR, "%s\n", $port);
+               if (($client = stream_socket_accept($server))) {
+                       /* this might be a proxy connect or a standard request */
+                       $request = new http\Message($client, false);
+                       
+                       if ($request->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 (file)
index 0000000..c8a2e63
--- /dev/null
@@ -0,0 +1,48 @@
+--TEST--
+proxy - send proxy headers for a proxy request
+--SKIPIF--
+<?php 
+include "skipif.inc";
+skip_client_test();
+?>
+--FILE--
+<?php
+
+echo "Test\n";
+
+$spec = array(array("pipe","r"), array("pipe","w"), array("pipe","w"));
+if (($proc = proc_open(PHP_BINARY . " proxy.inc", $spec, $pipes, __DIR__))) {
+       $port = trim(fgets($pipes[2]));
+       echo "Server on port $port\n";
+       $c = new http\Client;
+       $r = new http\Client\Request("GET", "http://www.example.com/");
+       $r->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 (file)
index 0000000..86bee61
--- /dev/null
@@ -0,0 +1,44 @@
+--TEST--
+proxy - don't send proxy headers for a standard request
+--SKIPIF--
+<?php 
+include "skipif.inc";
+skip_client_test();
+?>
+--FILE--
+<?php
+
+echo "Test\n";
+
+$spec = array(array("pipe","r"), array("pipe","w"), array("pipe","w"));
+if (($proc = proc_open(PHP_BINARY . " proxy.inc", $spec, $pipes, __DIR__))) {
+       $port = trim(fgets($pipes[2]));
+       echo "Server on port $port\n";
+       $c = new http\Client;
+       $r = new http\Client\Request("GET", "http://localhost:$port/");
+       $r->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===
index 28d9a13..4b2627a 100644 (file)
@@ -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);
+       }
+}