Merge pull request #7896 from stanley-cheung/php-fix-per-rpc-creds-v1_0
PHP: reject metadata keys that are not legal
diff --git a/package.xml b/package.xml
index 6e3aab7..7da3d48 100644
--- a/package.xml
+++ b/package.xml
@@ -22,7 +22,7 @@
</stability>
<license>BSD</license>
<notes>
-- TBD
+- Reject metadata keys which are not legal #7881
</notes>
<contents>
<dir baseinstalldir="/" name="/">
@@ -1173,7 +1173,7 @@
<date>2016-08-22</date>
<license>BSD</license>
<notes>
-- TBD
+- Reject metadata keys which are not legal #7881
</notes>
</release>
</changelog>
diff --git a/src/php/ext/grpc/call.c b/src/php/ext/grpc/call.c
index 66ca151..31c59fe 100644
--- a/src/php/ext/grpc/call.c
+++ b/src/php/ext/grpc/call.c
@@ -164,6 +164,9 @@
if (key_type1 != HASH_KEY_IS_STRING) {
return false;
}
+ if (!grpc_header_key_is_legal(key1, strlen(key1))) {
+ return false;
+ }
inner_array_hash = Z_ARRVAL_P(inner_array);
PHP_GRPC_HASH_FOREACH_VAL_START(inner_array_hash, value)
if (Z_TYPE_P(value) != IS_STRING) {
diff --git a/src/php/ext/grpc/call_credentials.c b/src/php/ext/grpc/call_credentials.c
index 6921a5d..25c92c9 100644
--- a/src/php/ext/grpc/call_credentials.c
+++ b/src/php/ext/grpc/call_credentials.c
@@ -192,23 +192,15 @@
/* call the user callback function */
zend_call_function(state->fci, state->fci_cache TSRMLS_CC);
- if (Z_TYPE_P(retval) != IS_ARRAY) {
- zend_throw_exception(spl_ce_InvalidArgumentException,
- "plugin callback must return metadata array",
- 1 TSRMLS_CC);
- return;
- }
-
- grpc_metadata_array metadata;
- if (!create_metadata_array(retval, &metadata)) {
- zend_throw_exception(spl_ce_InvalidArgumentException,
- "invalid metadata", 1 TSRMLS_CC);
- grpc_metadata_array_destroy(&metadata);
- return;
- }
-
- /* TODO: handle error */
grpc_status_code code = GRPC_STATUS_OK;
+ grpc_metadata_array metadata;
+
+ if (Z_TYPE_P(retval) != IS_ARRAY) {
+ code = GRPC_STATUS_INVALID_ARGUMENT;
+ } else if (!create_metadata_array(retval, &metadata)) {
+ grpc_metadata_array_destroy(&metadata);
+ code = GRPC_STATUS_INVALID_ARGUMENT;
+ }
/* Pass control back to core */
cb(user_data, metadata.metadata, metadata.count, code, NULL);
diff --git a/src/php/tests/interop/interop_client.php b/src/php/tests/interop/interop_client.php
index bf40549..c94ba61 100755
--- a/src/php/tests/interop/interop_client.php
+++ b/src/php/tests/interop/interop_client.php
@@ -54,6 +54,15 @@
}
}
+function hardAssertIfStatusOk($status)
+{
+ if ($status->code !== Grpc\STATUS_OK) {
+ echo "Call did not complete successfully. Status object:\n";
+ var_dump($status);
+ exit(1);
+ }
+}
+
/**
* Run the empty_unary test.
*
@@ -62,7 +71,7 @@
function emptyUnary($stub)
{
list($result, $status) = $stub->EmptyCall(new grpc\testing\EmptyMessage())->wait();
- hardAssert($status->code === Grpc\STATUS_OK, 'Call did not complete successfully');
+ hardAssertIfStatusOk($status);
hardAssert($result !== null, 'Call completed with a null response');
}
@@ -105,7 +114,7 @@
}
list($result, $status) = $stub->UnaryCall($request, [], $options)->wait();
- hardAssert($status->code === Grpc\STATUS_OK, 'Call did not complete successfully');
+ hardAssertIfStatusOk($status);
hardAssert($result !== null, 'Call returned a null response');
$payload = $result->getPayload();
hardAssert($payload->getType() === grpc\testing\PayloadType::COMPRESSABLE,
@@ -197,7 +206,12 @@
$methodName = $context->method_name;
$auth_credentials = ApplicationDefaultCredentials::getCredentials();
- return $auth_credentials->updateMetadata($metadata = [], $authUri);
+ $metadata = [];
+ $result = $auth_credentials->updateMetadata([], $authUri);
+ foreach ($result as $key => $value) {
+ $metadata[strtolower($key)] = $value;
+ }
+ return $metadata;
}
/**
@@ -242,7 +256,7 @@
$call->write($request);
}
list($result, $status) = $call->wait();
- hardAssert($status->code === Grpc\STATUS_OK, 'Call did not complete successfully');
+ hardAssertIfStatusOk($status);
hardAssert($result->getAggregatedPayloadSize() === 74922,
'aggregated_payload_size was incorrect');
}
@@ -275,8 +289,7 @@
'Response '.$i.' had the wrong length');
$i += 1;
}
- hardAssert($call->getStatus()->code === Grpc\STATUS_OK,
- 'Call did not complete successfully');
+ hardAssertIfStatusOk($call->getStatus());
}
/**
@@ -312,8 +325,7 @@
}
$call->writesDone();
hardAssert($call->read() === null, 'Server returned too many responses');
- hardAssert($call->getStatus()->code === Grpc\STATUS_OK,
- 'Call did not complete successfully');
+ hardAssertIfStatusOk($call->getStatus());
}
/**
@@ -326,8 +338,7 @@
$call = $stub->FullDuplexCall();
$call->writesDone();
hardAssert($call->read() === null, 'Server returned too many responses');
- hardAssert($call->getStatus()->code === Grpc\STATUS_OK,
- 'Call did not complete successfully');
+ hardAssertIfStatusOk($call->getStatus());
}
/**
@@ -419,8 +430,7 @@
'Incorrect initial metadata value');
list($result, $status) = $call->wait();
- hardAssert($status->code === Grpc\STATUS_OK,
- 'Call did not complete successfully');
+ hardAssertIfStatusOk($status);
$trailing_metadata = $call->getTrailingMetadata();
hardAssert(array_key_exists($ECHO_TRAILING_KEY, $trailing_metadata),
@@ -435,8 +445,7 @@
$streaming_call->write($streaming_request);
$streaming_call->writesDone();
- hardAssert($streaming_call->getStatus()->code === Grpc\STATUS_OK,
- 'Call did not complete successfully');
+ hardAssertIfStatusOk($streaming_call->getStatus());
$streaming_trailing_metadata = $streaming_call->getTrailingMetadata();
hardAssert(array_key_exists($ECHO_TRAILING_KEY,
diff --git a/src/php/tests/unit_tests/CallCredentials2Test.php b/src/php/tests/unit_tests/CallCredentials2Test.php
index a57e2b9..b3b98a2 100644
--- a/src/php/tests/unit_tests/CallCredentials2Test.php
+++ b/src/php/tests/unit_tests/CallCredentials2Test.php
@@ -132,4 +132,69 @@
unset($call);
unset($server_call);
}
+
+ public function invalidKeyCallbackFunc($context)
+ {
+ $this->assertTrue(is_string($context->service_url));
+ $this->assertTrue(is_string($context->method_name));
+
+ return ['K1' => ['v1']];
+ }
+
+ public function testCallbackWithInvalidKey()
+ {
+ $deadline = Grpc\Timeval::infFuture();
+ $status_text = 'xyz';
+ $call = new Grpc\Call($this->channel,
+ '/abc/dummy_method',
+ $deadline,
+ $this->host_override);
+
+ $call_credentials = Grpc\CallCredentials::createFromPlugin(
+ array($this, 'invalidKeyCallbackFunc'));
+ $call->setCredentials($call_credentials);
+
+ $event = $call->startBatch([
+ Grpc\OP_SEND_INITIAL_METADATA => [],
+ Grpc\OP_SEND_CLOSE_FROM_CLIENT => true,
+ Grpc\OP_RECV_STATUS_ON_CLIENT => true,
+ ]);
+
+ $this->assertTrue($event->send_metadata);
+ $this->assertTrue($event->send_close);
+ $this->assertTrue($event->status->code == Grpc\STATUS_UNAUTHENTICATED);
+ }
+
+ public function invalidReturnCallbackFunc($context)
+ {
+ $this->assertTrue(is_string($context->service_url));
+ $this->assertTrue(is_string($context->method_name));
+
+ return "a string";
+ }
+
+ public function testCallbackWithInvalidReturnValue()
+ {
+ $deadline = Grpc\Timeval::infFuture();
+ $status_text = 'xyz';
+ $call = new Grpc\Call($this->channel,
+ '/abc/dummy_method',
+ $deadline,
+ $this->host_override);
+
+ $call_credentials = Grpc\CallCredentials::createFromPlugin(
+ array($this, 'invalidReturnCallbackFunc'));
+ $call->setCredentials($call_credentials);
+
+ $event = $call->startBatch([
+ Grpc\OP_SEND_INITIAL_METADATA => [],
+ Grpc\OP_SEND_CLOSE_FROM_CLIENT => true,
+ Grpc\OP_RECV_STATUS_ON_CLIENT => true,
+ ]);
+
+ $this->assertTrue($event->send_metadata);
+ $this->assertTrue($event->send_close);
+ $this->assertTrue($event->status->code == Grpc\STATUS_UNAUTHENTICATED);
+ }
+
}
diff --git a/src/php/tests/unit_tests/CallCredentials3Test.php b/src/php/tests/unit_tests/CallCredentials3Test.php
deleted file mode 100644
index 8f5e109..0000000
--- a/src/php/tests/unit_tests/CallCredentials3Test.php
+++ /dev/null
@@ -1,135 +0,0 @@
-<?php
-/*
- *
- * Copyright 2015, Google Inc.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- * * Neither the name of Google Inc. nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- */
-
-class CallCredentials3Test extends PHPUnit_Framework_TestCase
-{
- public function setUp()
- {
- $this->credentials = Grpc\ChannelCredentials::createSsl(
- file_get_contents(dirname(__FILE__).'/../data/ca.pem'));
- $server_credentials = Grpc\ServerCredentials::createSsl(
- null,
- file_get_contents(dirname(__FILE__).'/../data/server1.key'),
- file_get_contents(dirname(__FILE__).'/../data/server1.pem'));
- $this->server = new Grpc\Server();
- $this->port = $this->server->addSecureHttp2Port('0.0.0.0:0',
- $server_credentials);
- $this->server->start();
- $this->host_override = 'foo.test.google.fr';
- $this->channel = new Grpc\Channel(
- 'localhost:'.$this->port,
- [
- 'grpc.ssl_target_name_override' => $this->host_override,
- 'grpc.default_authority' => $this->host_override,
- 'credentials' => $this->credentials,
- ]
- );
- }
-
- public function tearDown()
- {
- unset($this->channel);
- unset($this->server);
- }
-
- public function callbackFunc($context)
- {
- $this->assertTrue(is_string($context->service_url));
- $this->assertTrue(is_string($context->method_name));
-
- return ['k1' => ['v1'], 'k2' => ['v2']];
- }
-
- public function testCreateFromPlugin()
- {
- $deadline = Grpc\Timeval::infFuture();
- $status_text = 'xyz';
- $call = new Grpc\Call($this->channel,
- '/abc/dummy_method',
- $deadline,
- $this->host_override);
-
- $call_credentials = Grpc\CallCredentials::createFromPlugin(
- [$this, 'callbackFunc']);
- $call->setCredentials($call_credentials);
-
- $event = $call->startBatch([
- Grpc\OP_SEND_INITIAL_METADATA => [],
- Grpc\OP_SEND_CLOSE_FROM_CLIENT => true,
- ]);
-
- $this->assertTrue($event->send_metadata);
- $this->assertTrue($event->send_close);
-
- $event = $this->server->requestCall();
-
- $this->assertTrue(is_array($event->metadata));
- $metadata = $event->metadata;
- $this->assertTrue(array_key_exists('k1', $metadata));
- $this->assertTrue(array_key_exists('k2', $metadata));
- $this->assertSame($metadata['k1'], ['v1']);
- $this->assertSame($metadata['k2'], ['v2']);
-
- $this->assertSame('/abc/dummy_method', $event->method);
- $server_call = $event->call;
-
- $event = $server_call->startBatch([
- Grpc\OP_SEND_INITIAL_METADATA => [],
- Grpc\OP_SEND_STATUS_FROM_SERVER => [
- 'metadata' => [],
- 'code' => Grpc\STATUS_OK,
- 'details' => $status_text,
- ],
- Grpc\OP_RECV_CLOSE_ON_SERVER => true,
- ]);
-
- $this->assertTrue($event->send_metadata);
- $this->assertTrue($event->send_status);
- $this->assertFalse($event->cancelled);
-
- $event = $call->startBatch([
- Grpc\OP_RECV_INITIAL_METADATA => true,
- Grpc\OP_RECV_STATUS_ON_CLIENT => true,
- ]);
-
- $this->assertSame([], $event->metadata);
- $status = $event->status;
- $this->assertSame([], $status->metadata);
- $this->assertSame(Grpc\STATUS_OK, $status->code);
- $this->assertSame($status_text, $status->details);
-
- unset($call);
- unset($server_call);
- }
-}
diff --git a/src/php/tests/unit_tests/CallTest.php b/src/php/tests/unit_tests/CallTest.php
index d736f51..1205f0c 100644
--- a/src/php/tests/unit_tests/CallTest.php
+++ b/src/php/tests/unit_tests/CallTest.php
@@ -113,7 +113,7 @@
/**
* @expectedException InvalidArgumentException
*/
- public function testInvalidMetadataKey()
+ public function testInvalidStartBatchKey()
{
$batch = [
'invalid' => ['key1' => 'value1'],
@@ -124,6 +124,28 @@
/**
* @expectedException InvalidArgumentException
*/
+ public function testInvalidMetadataStrKey()
+ {
+ $batch = [
+ Grpc\OP_SEND_INITIAL_METADATA => ['Key' => ['value1', 'value2']],
+ ];
+ $result = $this->call->startBatch($batch);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testInvalidMetadataIntKey()
+ {
+ $batch = [
+ Grpc\OP_SEND_INITIAL_METADATA => [1 => ['value1', 'value2']],
+ ];
+ $result = $this->call->startBatch($batch);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
public function testInvalidMetadataInnerValue()
{
$batch = [
diff --git a/templates/package.xml.template b/templates/package.xml.template
index 65fef18..32ed3b6 100644
--- a/templates/package.xml.template
+++ b/templates/package.xml.template
@@ -24,7 +24,7 @@
</stability>
<license>BSD</license>
<notes>
- - TBD
+ - Reject metadata keys which are not legal #7881
</notes>
<contents>
<dir baseinstalldir="/" name="/">
@@ -291,7 +291,7 @@
<date>2016-08-22</date>
<license>BSD</license>
<notes>
- - TBD
+ - Reject metadata keys which are not legal #7881
</notes>
</release>
</changelog>