diff --git a/ChangeLog.md b/ChangeLog.md index 2ae31e9..76affaa 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -4,9 +4,8 @@ ChangeLog 3.0.0 (2014-09-23) ------------------ -* No changes from 2.1.0-alpha1, but doing a major version bump due to the - api breaks. - +* `getHeaders()` now returns header values as an array, just like psr/http. +* Added `hasHeader()`. 2.1.0-alpha1 (2014-09-15) ------------------------- @@ -16,7 +15,7 @@ ChangeLog * This means that `setHeaders()` does not wipe out every existing header anymore. * We also support multiple headers with the same name. -* Use `Request::getHeadersAsArray()` and `Response::getHeadersAsArray()` to +* Use `Request::getHeaderAsArray()` and `Response::getHeaderAsArray()` to get a hold off multiple headers with the same name. * If you use `getHeader()`, and there's more than 1 header with that name, we concatenate all these with a comma. diff --git a/lib/Auth/AWS.php b/lib/Auth/AWS.php index 5f2c3f9..d4b8489 100644 --- a/lib/Auth/AWS.php +++ b/lib/Auth/AWS.php @@ -192,7 +192,7 @@ protected function getAmzHeaders() { $headers = $this->request->getHeaders(); foreach($headers as $headerName => $headerValue) { if (strpos(strtolower($headerName),'x-amz-')===0) { - $amzHeaders[strtolower($headerName)] = str_replace( ["\r\n"], [' '],$headerValue) . "\n"; + $amzHeaders[strtolower($headerName)] = str_replace( ["\r\n"], [' '],$headerValue[0]) . "\n"; } } ksort($amzHeaders); diff --git a/lib/Client.php b/lib/Client.php index 993cdda..1ff5b69 100644 --- a/lib/Client.php +++ b/lib/Client.php @@ -415,9 +415,11 @@ protected function createCurlSettingsArray(RequestInterface $request) { } $nHeaders = []; - foreach($request->getHeaders() as $key=>$value) { + foreach($request->getHeaders() as $key=>$values) { - $nHeaders[] = $key . ': ' . $value; + foreach($values as $value) { + $nHeaders[] = $key . ': ' . $value; + } } $settings[CURLOPT_HTTPHEADER] = $nHeaders; diff --git a/lib/Message.php b/lib/Message.php index 9abb668..bdc4bc5 100644 --- a/lib/Message.php +++ b/lib/Message.php @@ -106,8 +106,7 @@ function setBody($body) { /** * Returns all the HTTP headers as an array. * - * Any HTTP headers with more than one value will be concatenated with - * comma (,). + * Every header is returned as an array, with one or more values. * * @return array */ @@ -115,12 +114,24 @@ function getHeaders() { $result = []; foreach($this->headers as $headerInfo) { - $result[$headerInfo[0]] = implode(',', $headerInfo[1]); + $result[$headerInfo[0]] = $headerInfo[1]; } return $result; } + /** + * Will return true or false, depending on if a http header exists. + * + * @param string $name + * @return bool + */ + function hasHeader($name) { + + return isset($this->headers[strtolower($name)]); + + } + /** * Returns a specific HTTP header, based on it's name. * diff --git a/lib/MessageDecoratorTrait.php b/lib/MessageDecoratorTrait.php index 24b946f..474ac94 100644 --- a/lib/MessageDecoratorTrait.php +++ b/lib/MessageDecoratorTrait.php @@ -80,8 +80,7 @@ function setBody($body) { /** * Returns all the HTTP headers as an array. * - * Any HTTP headers with more than one value will be concatenated with - * comma (,). + * Every header is returned as an array, with one or more values. * * @return array */ @@ -91,6 +90,18 @@ function getHeaders() { } + /** + * Will return true or false, depending on if a http header exists. + * + * @param string $name + * @return bool + */ + function hasHeader($name) { + + return $this->inner->hasHeader($name); + + } + /** * Returns a specific HTTP header, based on it's name. * diff --git a/lib/MessageInterface.php b/lib/MessageInterface.php index 0540ab1..3e44cca 100644 --- a/lib/MessageInterface.php +++ b/lib/MessageInterface.php @@ -52,13 +52,20 @@ function setBody($body); /** * Returns all the HTTP headers as an array. * - * Any HTTP headers with more than one value will be concatenated with - * comma (,). + * Every header is returned as an array, with one or more values. * * @return array */ function getHeaders(); + /** + * Will return true or false, depending on if a http header exists. + * + * @param string $name + * @return bool + */ + function hasHeader($name); + /** * Returns a specific HTTP header, based on it's name. * diff --git a/lib/Request.php b/lib/Request.php index 1c71199..409e489 100644 --- a/lib/Request.php +++ b/lib/Request.php @@ -295,7 +295,9 @@ function __toString() { $str = $this->getMethod() . ' ' . $this->getUrl() . ' HTTP/' . $this->getHTTPVersion() . "\r\n"; foreach($this->getHeaders() as $key=>$value) { - $str.= $key . ": " . $value . "\r\n"; + foreach($value as $v) { + $str.= $key . ": " . $v . "\r\n"; + } } $str.="\r\n"; $str.=$this->getBodyAsString(); diff --git a/lib/Response.php b/lib/Response.php index c8c0dfb..6b5ba3d 100644 --- a/lib/Response.php +++ b/lib/Response.php @@ -180,7 +180,9 @@ function __toString() { $str = 'HTTP/' . $this->httpVersion . ' ' . $this->getStatus() . ' ' . $this->getStatusText() . "\r\n"; foreach($this->getHeaders() as $key=>$value) { - $str.= $key . ": " . $value . "\r\n"; + foreach($value as $v) { + $str.= $key . ": " . $v . "\r\n"; + } } $str.="\r\n"; $str.=$this->getBodyAsString(); diff --git a/lib/Sapi.php b/lib/Sapi.php index 927de4c..a4963e3 100644 --- a/lib/Sapi.php +++ b/lib/Sapi.php @@ -58,7 +58,13 @@ static function sendResponse(ResponseInterface $response) { header('HTTP/' . $response->getHttpVersion() . ' ' . $response->getStatus() . ' ' . $response->getStatusText()); foreach($response->getHeaders() as $key=>$value) { - header($key . ': ' . $value); + foreach($value as $k=>$v) { + if ($k === 0) { + header($key . ': ' . $v); + } else { + header($key . ': ' . $v, false); + } + } } file_put_contents('php://output', $response->getBody()); diff --git a/tests/HTTP/Auth/DigestTest.php b/tests/HTTP/Auth/DigestTest.php index 78c93fe..2e7122f 100644 --- a/tests/HTTP/Auth/DigestTest.php +++ b/tests/HTTP/Auth/DigestTest.php @@ -106,6 +106,35 @@ public function testInvalidDigest2() { public function testDigestAuthInt() { + $this->auth->setQOP(Digest::QOP_AUTHINT); + list($nonce,$opaque) = $this->getServerTokens(Digest::QOP_AUTHINT); + + $username = 'admin'; + $password = 12345; + $nc = '00003'; + $cnonce = uniqid(); + + $digestHash = md5( + md5($username . ':' . self::REALM . ':' . $password) . ':' . + $nonce . ':' . + $nc . ':' . + $cnonce . ':' . + 'auth-int:' . + md5('POST' . ':' . '/' . ':' . md5('body')) + ); + + $this->request->setMethod('POST'); + $this->request->setHeader('Authorization','Digest username="'.$username.'", realm="' . self::REALM . '", nonce="' . $nonce . '", uri="/", response="' . $digestHash . '", opaque="' . $opaque . '", qop=auth-int,nc='.$nc.',cnonce="' . $cnonce . '"'); + $this->request->setBody('body'); + + $this->auth->init(); + + $this->assertTrue($this->auth->validateA1(md5($username . ':' . self::REALM . ':' . $password)),'Authentication is deemed invalid through validateA1'); + + } + + public function testDigestAuthBoth() { + $this->auth->setQOP(Digest::QOP_AUTHINT | Digest::QOP_AUTH); list($nonce,$opaque) = $this->getServerTokens(Digest::QOP_AUTHINT| Digest::QOP_AUTH); @@ -133,6 +162,7 @@ public function testDigestAuthInt() { } + private function getServerTokens($qop = Digest::QOP_AUTH) { $this->auth->requireLogin(); diff --git a/tests/HTTP/ClientTest.php b/tests/HTTP/ClientTest.php index b1adf2a..1ffcb51 100644 --- a/tests/HTTP/ClientTest.php +++ b/tests/HTTP/ClientTest.php @@ -249,7 +249,7 @@ function testParseCurlResult() { $this->assertEquals(Client::STATUS_SUCCESS, $result['status']); $this->assertEquals(200, $result['http_code']); $this->assertEquals(200, $result['response']->getStatus()); - $this->assertEquals(['Header1' => 'Val1'], $result['response']->getHeaders()); + $this->assertEquals(['Header1' => ['Val1']], $result['response']->getHeaders()); $this->assertEquals('Foo', $result['response']->getBodyAsString()); } @@ -299,7 +299,7 @@ function testDoRequest() { }); $response = $client->doRequest($request); $this->assertEquals(200, $response->getStatus()); - $this->assertEquals(['Header1' => 'Val1'], $response->getHeaders()); + $this->assertEquals(['Header1' => ['Val1']], $response->getHeaders()); $this->assertEquals('Foo', $response->getBodyAsString()); } diff --git a/tests/HTTP/MessageDecoratorTest.php b/tests/HTTP/MessageDecoratorTest.php index a7833fb..64dd4dc 100644 --- a/tests/HTTP/MessageDecoratorTest.php +++ b/tests/HTTP/MessageDecoratorTest.php @@ -32,27 +32,31 @@ function testHeaders() { 'a' => 'b', ]); - $this->assertEquals(['a' => 'b'], $this->inner->getHeaders()); - $this->assertEquals(['a' => 'b'], $this->outer->getHeaders()); + $this->assertEquals(['a' => ['b']], $this->inner->getHeaders()); + $this->assertEquals(['a' => ['b']], $this->outer->getHeaders()); $this->outer->setHeaders([ 'c' => 'd', ]); - $this->assertEquals(['a' => 'b', 'c' => 'd'], $this->inner->getHeaders()); - $this->assertEquals(['a' => 'b', 'c' => 'd'], $this->outer->getHeaders()); + $this->assertEquals(['a' => ['b'], 'c' => ['d']], $this->inner->getHeaders()); + $this->assertEquals(['a' => ['b'], 'c' => ['d']], $this->outer->getHeaders()); $this->outer->addHeaders([ 'e' => 'f', ]); - $this->assertEquals(['a' => 'b', 'c' => 'd', 'e' => 'f'], $this->inner->getHeaders()); - $this->assertEquals(['a' => 'b', 'c' => 'd', 'e' => 'f'], $this->outer->getHeaders()); + $this->assertEquals(['a' => ['b'], 'c' => ['d'], 'e' => ['f']], $this->inner->getHeaders()); + $this->assertEquals(['a' => ['b'], 'c' => ['d'], 'e' => ['f']], $this->outer->getHeaders()); } function testHeader() { + $this->assertFalse($this->outer->hasHeader('a')); + $this->assertFalse($this->inner->hasHeader('a')); $this->outer->setHeader('a', 'c'); + $this->assertTrue($this->outer->hasHeader('a')); + $this->assertTrue($this->inner->hasHeader('a')); $this->assertEquals('c', $this->inner->getHeader('A')); $this->assertEquals('c', $this->outer->getHeader('A')); diff --git a/tests/HTTP/MessageTest.php b/tests/HTTP/MessageTest.php index 62377d1..64bcd26 100644 --- a/tests/HTTP/MessageTest.php +++ b/tests/HTTP/MessageTest.php @@ -85,8 +85,8 @@ function testSetHeaders() { $message = new MessageMock(); $headers = [ - 'X-Foo' => '1', - 'X-Bar' => '2', + 'X-Foo' => ['1'], + 'X-Bar' => ['2'], ]; $message->setHeaders($headers); @@ -98,8 +98,8 @@ function testSetHeaders() { ]); $expected = [ - 'X-Foo' => '3,4', - 'X-Bar' => '5', + 'X-Foo' => ['3','4'], + 'X-Bar' => ['5'], ]; $this->assertEquals($expected, $message->getHeaders()); @@ -111,8 +111,8 @@ function testAddHeaders() { $message = new MessageMock(); $headers = [ - 'X-Foo' => '1', - 'X-Bar' => '2', + 'X-Foo' => ['1'], + 'X-Bar' => ['2'], ]; $message->addHeaders($headers); @@ -124,8 +124,8 @@ function testAddHeaders() { ]); $expected = [ - 'X-Foo' => '1,3,4', - 'X-Bar' => '2,5', + 'X-Foo' => ['1','3','4'], + 'X-Bar' => ['2','5'], ]; $this->assertEquals($expected, $message->getHeaders()); @@ -182,6 +182,16 @@ function testMultipleHeaders() { } + function testHasHeaders() { + + $message = new MessageMock(); + + $this->assertFalse($message->hasHeader('X-Foo')); + $message->setHeader('X-Foo', 'Bar'); + $this->assertTrue($message->hasHeader('X-Foo')); + + } + } class MessageMock extends Message { } diff --git a/tests/HTTP/RequestTest.php b/tests/HTTP/RequestTest.php index e377615..422000b 100644 --- a/tests/HTTP/RequestTest.php +++ b/tests/HTTP/RequestTest.php @@ -12,7 +12,7 @@ function testConstruct() { $this->assertEquals('GET', $request->getMethod()); $this->assertEquals('/foo', $request->getUrl()); $this->assertEquals(array( - 'User-Agent' => 'Evert', + 'User-Agent' => ['Evert'], ), $request->getHeaders()); } diff --git a/tests/HTTP/SapiTest.php b/tests/HTTP/SapiTest.php index 6e3aa3e..2d50aec 100644 --- a/tests/HTTP/SapiTest.php +++ b/tests/HTTP/SapiTest.php @@ -18,9 +18,9 @@ function testConstructFromServerArray() { $this->assertEquals('GET', $request->getMethod()); $this->assertEquals('/foo', $request->getUrl()); $this->assertEquals(array( - 'User-Agent' => 'Evert', - 'Content-Type' => 'text/xml', - 'Content-Length' => '400', + 'User-Agent' => ['Evert'], + 'Content-Type' => ['text/xml'], + 'Content-Length' => ['400'], ), $request->getHeaders()); $this->assertEquals('1.0', $request->getHttpVersion()); @@ -42,7 +42,7 @@ function testConstructPHPAuth() { $this->assertEquals('GET', $request->getMethod()); $this->assertEquals('/foo', $request->getUrl()); $this->assertEquals(array( - 'Authorization' => 'Basic ' . base64_encode('user:pass'), + 'Authorization' => ['Basic ' . base64_encode('user:pass')], ), $request->getHeaders()); } @@ -58,7 +58,7 @@ function testConstructPHPAuthDigest() { $this->assertEquals('GET', $request->getMethod()); $this->assertEquals('/foo', $request->getUrl()); $this->assertEquals(array( - 'Authorization' => 'Digest blabla', + 'Authorization' => ['Digest blabla'], ), $request->getHeaders()); } @@ -74,7 +74,7 @@ function testConstructRedirectAuth() { $this->assertEquals('GET', $request->getMethod()); $this->assertEquals('/foo', $request->getUrl()); $this->assertEquals(array( - 'Authorization' => 'Basic bla', + 'Authorization' => ['Basic bla'], ), $request->getHeaders()); } @@ -91,7 +91,10 @@ function testSend() { $this->markTestSkipped('XDebug needs to be installed for this test to run'); } - $response = new Response(204, ['Content-Type', 'text/xml']); + $response = new Response(204, ['Content-Type' => 'text/xml']); + + // Second Content-Type header. Normally this doesn't make sense. + $response->addHeader('Content-Type', 'application/xml'); $response->setBody('foo'); ob_start(); @@ -104,8 +107,8 @@ function testSend() { $this->assertEquals( [ - "0: Content-Type", - "1: text/xml", + "Content-Type: text/xml", + "Content-Type: application/xml", ], $headers ); diff --git a/tests/HTTP/UtilTest.php b/tests/HTTP/UtilTest.php index a55017e..201f5cd 100644 --- a/tests/HTTP/UtilTest.php +++ b/tests/HTTP/UtilTest.php @@ -157,6 +157,22 @@ function negotiateData() { ], 'audio/basic', ], + [ // Lower quality after + 'audio/pcm; q=0.2, audio/basic; q=0.1', + [ + 'audio/pcm', + 'audio/basic', + ], + 'audio/pcm', + ], + [ // Random parameter, should be ignored + 'audio/pcm; hello; q=0.2, audio/basic; q=0.1', + [ + 'audio/pcm', + 'audio/basic', + ], + 'audio/pcm', + ], [ // No whitepace after type, should pick the one that is the most specific. 'text/vcard;version=3.0, text/vcard', [