struct {
HashTable cache;
+ struct curl_slist *proxyheaders;
struct curl_slist *headers;
struct curl_slist *resolve;
php_http_buffer_t cookies;
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)
{
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;
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);
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)
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;
} 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);
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;
}
}
}
+ 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)
}
}
+ 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;
#else
php_end_ob_buffers(1 TSRMLS_CC);
#endif
+
if (zstream) {
php_http_env_response_t *r;
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
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
rewind($f);
var_dump(stream_get_contents($f));
+?>
--EXPECTREGEX--
string\(\d+\) "HTTP\/1\.1 200 OK
Accept-Ranges: bytes
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
+
+"
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;
rewind($f);
var_dump(stream_get_contents($f));
?>
+%c
+0%c
+%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
+"
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
"
HTTP/1.1 200 OK
Accept-Ranges: bytes
ETag: "fc8305a1"
+Transfer-Encoding: chunked
+9
foo: bar
+
+0
+
Set-Cookie: baz=1;
Set-Cookie: 123=1;
ETag: ""
+Transfer-Encoding: chunked
+
+0
--- /dev/null
+<?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
--- /dev/null
+--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===
--- /dev/null
+--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===
}
}
-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);
+ }
+}