From e6859a91a4e277e33db00621435612f2999d0854 Mon Sep 17 00:00:00 2001 From: Maks3w Date: Tue, 8 Sep 2015 19:26:33 +0200 Subject: [PATCH 1/4] ContentType test refactor as data providers --- test/Header/ContentTypeTest.php | 172 +++++++++++++++----------------- 1 file changed, 83 insertions(+), 89 deletions(-) diff --git a/test/Header/ContentTypeTest.php b/test/Header/ContentTypeTest.php index 2b6230a8..3f8cb6ec 100644 --- a/test/Header/ContentTypeTest.php +++ b/test/Header/ContentTypeTest.php @@ -10,37 +10,19 @@ namespace ZendTest\Mail\Header; use Zend\Mail\Header\ContentType; +use Zend\Mail\Header\Exception\InvalidArgumentException; +use Zend\Mail\Header\HeaderInterface; /** * @group Zend_Mail */ class ContentTypeTest extends \PHPUnit_Framework_TestCase { - public function testContentTypeFromStringCreatesValidContentTypeHeader() + public function testImplementsHeaderInterface() { - $contentTypeHeader = ContentType::fromString('Content-Type: xxx/yyy'); - $this->assertInstanceOf('Zend\Mail\Header\HeaderInterface', $contentTypeHeader); - $this->assertInstanceOf('Zend\Mail\Header\ContentType', $contentTypeHeader); - } - - public function testContentTypeGetFieldNameReturnsHeaderName() - { - $contentTypeHeader = new ContentType(); - $this->assertEquals('Content-Type', $contentTypeHeader->getFieldName()); - } + $header = new ContentType(); - public function testContentTypeGetFieldValueReturnsProperValue() - { - $contentTypeHeader = new ContentType(); - $contentTypeHeader->setType('foo/bar'); - $this->assertEquals('foo/bar', $contentTypeHeader->getFieldValue()); - } - - public function testContentTypeToStringReturnsHeaderFormattedString() - { - $contentTypeHeader = new ContentType(); - $contentTypeHeader->setType('foo/bar'); - $this->assertEquals("Content-Type: foo/bar", $contentTypeHeader->toString()); + $this->assertInstanceOf(HeaderInterface::class, $header); } /** @@ -55,26 +37,6 @@ public function testTrailingSemiColonFromString() $this->assertEquals(['boundary' => 'Apple-Mail=_1B852F10-F9C6-463D-AADD-CD503A5428DD'], $params); } - public function testProvidingParametersIntroducesHeaderFolding() - { - $header = new ContentType(); - $header->setType('application/x-unit-test'); - $header->addParameter('charset', 'us-ascii'); - $string = $header->toString(); - - $this->assertContains("Content-Type: application/x-unit-test;", $string); - $this->assertContains(";\r\n charset=\"us-ascii\"", $string); - } - - public function testExtractsExtraInformationFromContentType() - { - $contentTypeHeader = ContentType::fromString( - 'Content-Type: multipart/alternative; boundary="Apple-Mail=_1B852F10-F9C6-463D-AADD-CD503A5428DD"' - ); - $params = $contentTypeHeader->getParameters(); - $this->assertEquals($params, ['boundary' => 'Apple-Mail=_1B852F10-F9C6-463D-AADD-CD503A5428DD']); - } - public function testExtractsExtraInformationWithoutBeingConfusedByTrailingSemicolon() { $header = ContentType::fromString('Content-Type: application/pdf;name="foo.pdf";'); @@ -82,53 +44,46 @@ public function testExtractsExtraInformationWithoutBeingConfusedByTrailingSemico } /** - * @group #2728 - * - * Tests setting different MIME types + * @dataProvider setTypeProvider */ - public function testSetContentType() + public function testFromString($type, $parameters, $fieldValue, $expectedToString) { - $header = new ContentType(); - - $header->setType('application/vnd.ms-excel'); - $this->assertEquals('Content-Type: application/vnd.ms-excel', $header->toString()); - - $header->setType('application/rss+xml'); - $this->assertEquals('Content-Type: application/rss+xml', $header->toString()); - - $header->setType('video/mp4'); - $this->assertEquals('Content-Type: video/mp4', $header->toString()); - - $header->setType('message/rfc822'); - $this->assertEquals('Content-Type: message/rfc822', $header->toString()); + $header = ContentType::fromString($expectedToString); + + $this->assertInstanceOf(ContentType::class, $header); + $this->assertEquals('Content-Type', $header->getFieldName(), 'getFieldName() value not match'); + $this->assertEquals($type, $header->getType(), 'getType() value not match'); + $this->assertEquals($fieldValue, $header->getFieldValue(), 'getFieldValue() value not match'); + $this->assertEquals($parameters, $header->getParameters(), 'getParameters() value not match'); + $this->assertEquals($expectedToString, $header->toString(), 'toString() value not match'); } /** - * @group ZF2015-04 + * @dataProvider setTypeProvider */ - public function testFromStringRaisesExceptionForInvalidName() + public function testSetType($type, $parameters, $fieldValue, $expectedToString) { - $this->setExpectedException('Zend\Mail\Header\Exception\InvalidArgumentException', 'header name'); - $header = ContentType::fromString('Content-Type' . chr(32) . ': text/html'); - } + $header = new ContentType(); - public function headerLines() - { - return [ - 'newline' => ["Content-Type: text/html;\nlevel=1"], - 'cr-lf' => ["Content-Type: text/html\r\n;level=1",], - 'multiline' => ["Content-Type: text/html;\r\nlevel=1\r\nq=0.1"], - ]; + $header->setType($type); + foreach ($parameters as $name => $value) { + $header->addParameter($name, $value); + } + + $this->assertEquals('Content-Type', $header->getFieldName(), 'getFieldName() value not match'); + $this->assertEquals($type, $header->getType(), 'getType() value not match'); + $this->assertEquals($fieldValue, $header->getFieldValue(), 'getFieldValue() value not match'); + $this->assertEquals($parameters, $header->getParameters(), 'getParameters() value not match'); + $this->assertEquals($expectedToString, $header->toString(), 'toString() value not match'); } /** - * @dataProvider headerLines - * @group ZF2015-04 + * @dataProvider invalidHeaderLinesProvider */ - public function testFromStringRaisesExceptionForNonFoldingMultilineValues($headerLine) + public function testFromStringThrowException($headerLine, $expectedException, $exceptionMessage) { - $this->setExpectedException('Zend\Mail\Header\Exception\InvalidArgumentException', 'header value'); - $header = ContentType::fromString($headerLine); + $this->setExpectedException($expectedException, $exceptionMessage); + ContentType::fromString($headerLine); } /** @@ -142,24 +97,63 @@ public function testFromStringHandlesContinuations() } /** - * @group ZF2015-04 + * @dataProvider invalidParametersProvider */ - public function testAddParameterRaisesInvalidArgumentExceptionForInvalidParameterName() + public function testAddParameterThrowException($paramName, $paramValue, $expectedException, $exceptionMessage) { $header = new ContentType(); $header->setType('text/html'); - $this->setExpectedException('Zend\Mail\Header\Exception\InvalidArgumentException', 'parameter name'); - $header->addParameter("b\r\na\rr\n", "baz"); + + $this->setExpectedException($expectedException, $exceptionMessage); + $header->addParameter($paramName, $paramValue); } - /** - * @group ZF2015-04 - */ - public function testAddParameterRaisesInvalidArgumentExceptionForInvalidParameterValue() + public function setTypeProvider() { - $header = new ContentType(); - $header->setType('text/html'); - $this->setExpectedException('Zend\Mail\Header\Exception\InvalidArgumentException', 'parameter value'); - $header->addParameter('foo', "\nbar\r\nbaz\r"); + $foldingHeaderLine = "Content-Type: foo/baz;\r\n charset=\"us-ascii\""; + $foldingFieldValue = "foo/baz;\r\n charset=\"us-ascii\""; + + // @codingStandardsIgnoreStart + return [ + // Description => [$type, $parameters, $fieldValue, toString()] + // @group #2728 + 'foo/a.b-c' => ['foo/a.b-c', [], 'foo/a.b-c', 'Content-Type: foo/a.b-c'], + 'foo/a+b' => ['foo/a+b' , [], 'foo/a+b' , 'Content-Type: foo/a+b'], + 'foo/baz' => ['foo/baz' , [], 'foo/baz' , 'Content-Type: foo/baz'], + 'parameter use header folding' => ['foo/baz' , ['charset' => 'us-ascii'], $foldingFieldValue, $foldingHeaderLine], + ]; + // @codingStandardsIgnoreEnd + } + + public function invalidParametersProvider() + { + $invalidArgumentException = InvalidArgumentException::class; + + // @codingStandardsIgnoreStart + return [ + // Description => [param name, param value, expected exception, exception message contain] + + // @group ZF2015-04 + 'invalid name' => ["b\r\na\rr\n", 'baz', $invalidArgumentException, 'parameter name'], + 'invalid value' => ['foo', "\nbar\r\nbaz\r", $invalidArgumentException, 'parameter value'], + ]; + // @codingStandardsIgnoreEnd + } + + public function invalidHeaderLinesProvider() + { + $invalidArgumentException = InvalidArgumentException::class; + + // @codingStandardsIgnoreStart + return [ + // Description => [header line, expected exception, exception message contain] + + // @group ZF2015-04 + 'invalid name' => ['Content-Type' . chr(32) . ': text/html', $invalidArgumentException, 'header name'], + 'newline' => ["Content-Type: text/html;\nlevel=1", $invalidArgumentException, 'header value'], + 'cr-lf' => ["Content-Type: text/html\r\n;level=1", $invalidArgumentException, 'header value'], + 'multiline' => ["Content-Type: text/html;\r\nlevel=1\r\nq=0.1", $invalidArgumentException, 'header value'], + ]; + // @codingStandardsIgnoreEnd } } From 9539895128dc4ada209cb7b3c4ccb205fd288d8a Mon Sep 17 00:00:00 2001 From: Maks3w Date: Tue, 8 Sep 2015 19:59:40 +0200 Subject: [PATCH 2/4] [ContentType] Add test for parameters with encoded values --- test/Header/ContentTypeTest.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/Header/ContentTypeTest.php b/test/Header/ContentTypeTest.php index 3f8cb6ec..43f6a7a5 100644 --- a/test/Header/ContentTypeTest.php +++ b/test/Header/ContentTypeTest.php @@ -113,6 +113,9 @@ public function setTypeProvider() $foldingHeaderLine = "Content-Type: foo/baz;\r\n charset=\"us-ascii\""; $foldingFieldValue = "foo/baz;\r\n charset=\"us-ascii\""; + $encodedHeaderLine = "Content-Type: foo/baz;\r\n name=\"=?UTF-8?Q?=C3=93?=\""; + $encodedFieldValue = "foo/baz;\r\n name=\"Ó\""; + // @codingStandardsIgnoreStart return [ // Description => [$type, $parameters, $fieldValue, toString()] @@ -121,6 +124,7 @@ public function setTypeProvider() 'foo/a+b' => ['foo/a+b' , [], 'foo/a+b' , 'Content-Type: foo/a+b'], 'foo/baz' => ['foo/baz' , [], 'foo/baz' , 'Content-Type: foo/baz'], 'parameter use header folding' => ['foo/baz' , ['charset' => 'us-ascii'], $foldingFieldValue, $foldingHeaderLine], + 'encoded characters' => ['foo/baz' , ['name' => 'Ó'], $encodedFieldValue, $encodedHeaderLine], ]; // @codingStandardsIgnoreEnd } From 701a575264fb881fa2d02a4cc28efabcc5f2436c Mon Sep 17 00:00:00 2001 From: Maks3w Date: Tue, 8 Sep 2015 20:04:03 +0200 Subject: [PATCH 3/4] [ContentType] Fix parameters with encoded values --- src/Header/ContentType.php | 29 +++++++++++++++++++++++------ test/Header/ContentTypeTest.php | 3 ++- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/Header/ContentType.php b/src/Header/ContentType.php index 45852c05..cf4caebc 100644 --- a/src/Header/ContentType.php +++ b/src/Header/ContentType.php @@ -10,14 +10,22 @@ namespace Zend\Mail\Header; use Zend\Mail\Headers; +use Zend\Mime\Mime; -class ContentType implements HeaderInterface +class ContentType implements UnstructuredInterface { /** * @var string */ protected $type; + /** + * Header encoding + * + * @var string + */ + protected $encoding = 'ASCII'; + /** * @var array */ @@ -66,6 +74,13 @@ public function getFieldValue($format = HeaderInterface::FORMAT_RAW) $values = [$prepared]; foreach ($this->parameters as $attribute => $value) { + if (HeaderInterface::FORMAT_ENCODED === $format && + !Mime::isPrintable($value)) { + $this->encoding = 'UTF-8'; + $value = HeaderWrap::wrap($value, $this); + $this->encoding = 'ASCII'; + } + $values[] = sprintf('%s="%s"', $attribute, $value); } @@ -74,18 +89,18 @@ public function getFieldValue($format = HeaderInterface::FORMAT_RAW) public function setEncoding($encoding) { - // This header must be always in US-ASCII + $this->encoding = $encoding; return $this; } public function getEncoding() { - return 'ASCII'; + return $this->encoding; } public function toString() { - return 'Content-Type: ' . $this->getFieldValue(); + return 'Content-Type: ' . $this->getFieldValue(HeaderInterface::FORMAT_ENCODED); } /** @@ -135,8 +150,10 @@ public function addParameter($name, $value) if (! HeaderValue::isValid($name)) { throw new Exception\InvalidArgumentException('Invalid content-type parameter name detected'); } - if (! HeaderValue::isValid($value)) { - throw new Exception\InvalidArgumentException('Invalid content-type parameter value detected'); + if (! HeaderWrap::canBeEncoded($value)) { + throw new Exception\InvalidArgumentException( + 'Parameter value must be composed of printable US-ASCII or UTF-8 characters.' + ); } $this->parameters[$name] = $value; diff --git a/test/Header/ContentTypeTest.php b/test/Header/ContentTypeTest.php index 43f6a7a5..24824557 100644 --- a/test/Header/ContentTypeTest.php +++ b/test/Header/ContentTypeTest.php @@ -12,6 +12,7 @@ use Zend\Mail\Header\ContentType; use Zend\Mail\Header\Exception\InvalidArgumentException; use Zend\Mail\Header\HeaderInterface; +use Zend\Mail\Header\UnstructuredInterface; /** * @group Zend_Mail @@ -22,6 +23,7 @@ public function testImplementsHeaderInterface() { $header = new ContentType(); + $this->assertInstanceOf(UnstructuredInterface::class, $header); $this->assertInstanceOf(HeaderInterface::class, $header); } @@ -139,7 +141,6 @@ public function invalidParametersProvider() // @group ZF2015-04 'invalid name' => ["b\r\na\rr\n", 'baz', $invalidArgumentException, 'parameter name'], - 'invalid value' => ['foo', "\nbar\r\nbaz\r", $invalidArgumentException, 'parameter value'], ]; // @codingStandardsIgnoreEnd } From 54412043ba97ff322ea82e60f75cd7a4bb517b24 Mon Sep 17 00:00:00 2001 From: Maks3w Date: Wed, 9 Sep 2015 16:58:58 +0200 Subject: [PATCH 4/4] CS --- src/Header/ContentType.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Header/ContentType.php b/src/Header/ContentType.php index cf4caebc..eb520d5b 100644 --- a/src/Header/ContentType.php +++ b/src/Header/ContentType.php @@ -74,8 +74,7 @@ public function getFieldValue($format = HeaderInterface::FORMAT_RAW) $values = [$prepared]; foreach ($this->parameters as $attribute => $value) { - if (HeaderInterface::FORMAT_ENCODED === $format && - !Mime::isPrintable($value)) { + if (HeaderInterface::FORMAT_ENCODED === $format && !Mime::isPrintable($value)) { $this->encoding = 'UTF-8'; $value = HeaderWrap::wrap($value, $this); $this->encoding = 'ASCII';