From a8bc834077ab85f952293815b5b3def21c583c77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Bechyn=C4=9B?= Date: Mon, 12 Jun 2017 15:14:28 +0200 Subject: [PATCH] Mime/PartHeaders now handle both Content-ID and Content-id according to W3 specs --- src/BeSimple/SoapCommon/Mime/MultiPart.php | 31 +------- src/BeSimple/SoapCommon/Mime/Part.php | 12 +-- src/BeSimple/SoapCommon/Mime/PartHeader.php | 73 +++++++++++++------ tests/BeSimple/SoapServer/SoapServerTest.php | 29 ++++++++ ...dLowerCaseHeaders.request.mimepart.message | 62 ++++++++++++++++ 5 files changed, 145 insertions(+), 62 deletions(-) create mode 100644 tests/Fixtures/Message/Request/dummyServiceMethodWithAttachmentsAndLowerCaseHeaders.request.mimepart.message diff --git a/src/BeSimple/SoapCommon/Mime/MultiPart.php b/src/BeSimple/SoapCommon/Mime/MultiPart.php index 21b2e17..500264f 100644 --- a/src/BeSimple/SoapCommon/Mime/MultiPart.php +++ b/src/BeSimple/SoapCommon/Mime/MultiPart.php @@ -31,21 +31,17 @@ class MultiPart extends PartHeader { /** * Content-ID of main part. - * * @var string */ protected $mainPartContentId; /** * Mime parts. - * * @var \BeSimple\SoapCommon\Mime\Part[] */ - protected $parts = []; + protected $parts; /** - * Construct new mime object. - * * @param string $boundary */ public function __construct($boundary = null) @@ -63,7 +59,6 @@ class MultiPart extends PartHeader * Get mime message of this object (without headers). * * @param boolean $withHeaders Returned mime message contains headers - * * @return string */ public function getMimeMessage($withHeaders = false) @@ -79,30 +74,6 @@ class MultiPart extends PartHeader return $message; } - /** - * Get string array with MIME headers for usage in HTTP header (with CURL). - * Only 'Content-Type' and 'Content-Description' headers are returned. - * - * @return string[] - */ - public function getHeadersForHttp() - { - $allowedHeaders = [ - 'Content-Type', - 'Content-Description', - ]; - $headers = []; - foreach ($this->headers as $fieldName => $value) { - if (in_array($fieldName, $allowedHeaders)) { - $fieldValue = $this->generateHeaderFieldValue($value); - // for http only ISO-8859-1 - $headers[] = $fieldName . ': '. iconv('utf-8', 'ISO-8859-1//TRANSLIT', $fieldValue); - } - } - - return $headers; - } - /** * Add new part to MIME message. * diff --git a/src/BeSimple/SoapCommon/Mime/Part.php b/src/BeSimple/SoapCommon/Mime/Part.php index 787e346..b041116 100644 --- a/src/BeSimple/SoapCommon/Mime/Part.php +++ b/src/BeSimple/SoapCommon/Mime/Part.php @@ -68,9 +68,7 @@ class Part extends PartHeader } /** - * __toString. - * - * @return mixed + * @return string */ public function __toString() { @@ -98,11 +96,7 @@ class Part extends PartHeader } /** - * Set mime content. - * - * @param mixed $content Content to set - * - * @return void + * @param string $content */ public function setContent($content) { @@ -111,7 +105,6 @@ class Part extends PartHeader /** * Get complete mime message of this object. - * * @return string */ public function getMessagePart() @@ -121,7 +114,6 @@ class Part extends PartHeader /** * Generate body. - * * @return string */ protected function generateBody() diff --git a/src/BeSimple/SoapCommon/Mime/PartHeader.php b/src/BeSimple/SoapCommon/Mime/PartHeader.php index b9453aa..ed26459 100644 --- a/src/BeSimple/SoapCommon/Mime/PartHeader.php +++ b/src/BeSimple/SoapCommon/Mime/PartHeader.php @@ -19,7 +19,10 @@ namespace BeSimple\SoapCommon\Mime; */ abstract class PartHeader { - protected $headers = []; + /** @var string[] array of headers with lower-cased keys */ + private $headers; + /** @var string[] array of lower-cased keys and their original variants */ + private $headersOriginalKeys; /** * Add a new header to the mime part. @@ -32,19 +35,21 @@ abstract class PartHeader */ public function setHeader($name, $value, $subValue = null) { - if (isset($this->headers[$name]) && !is_null($subValue)) { - if (!is_array($this->headers[$name])) { - $this->headers[$name] = [ - '@' => $this->headers[$name], + $lowerCaseName = mb_strtolower($name); + $this->headersOriginalKeys[$lowerCaseName] = $name; + if (isset($this->headers[$lowerCaseName]) && !is_null($subValue)) { + if (!is_array($this->headers[$lowerCaseName])) { + $this->headers[$lowerCaseName] = [ + '@' => $this->headers[$lowerCaseName], $value => $subValue, ]; } else { - $this->headers[$name][$value] = $subValue; + $this->headers[$lowerCaseName][$value] = $subValue; } - } elseif (isset($this->headers[$name]) && is_array($this->headers[$name]) && isset($this->headers[$name]['@'])) { - $this->headers[$name]['@'] = $value; + } elseif (isset($this->headers[$lowerCaseName]) && is_array($this->headers[$lowerCaseName]) && isset($this->headers[$lowerCaseName]['@'])) { + $this->headers[$lowerCaseName]['@'] = $value; } else { - $this->headers[$name] = $value; + $this->headers[$lowerCaseName] = $value; } } @@ -58,17 +63,18 @@ abstract class PartHeader */ public function getHeader($name, $subValue = null) { - if (isset($this->headers[$name])) { + $lowerCaseName = mb_strtolower($name); + if (isset($this->headers[$lowerCaseName])) { if (!is_null($subValue)) { - if (is_array($this->headers[$name]) && isset($this->headers[$name][$subValue])) { - return $this->headers[$name][$subValue]; + if (is_array($this->headers[$lowerCaseName]) && isset($this->headers[$lowerCaseName][$subValue])) { + return $this->headers[$lowerCaseName][$subValue]; } else { return null; } - } elseif (is_array($this->headers[$name]) && isset($this->headers[$name]['@'])) { - return $this->headers[$name]['@']; + } elseif (is_array($this->headers[$lowerCaseName]) && isset($this->headers[$lowerCaseName]['@'])) { + return $this->headers[$lowerCaseName]['@']; } else { - return $this->headers[$name]; + return $this->headers[$lowerCaseName]; } } @@ -80,6 +86,30 @@ abstract class PartHeader return $this->headers; } + /** + * Get string array with MIME headers for usage in HTTP header (with CURL). + * Only 'Content-Type' and 'Content-Description' headers are returned. + * + * @return string[] + */ + public function getHeadersForHttp() + { + $allowedHeadersLowerCase = [ + 'content-type', + 'content-description', + ]; + $headers = []; + foreach ($this->headers as $fieldName => $value) { + if (in_array($fieldName, $allowedHeadersLowerCase)) { + $fieldValue = $this->generateHeaderFieldValue($value); + // for http only ISO-8859-1 + $headers[] = $this->headersOriginalKeys[$fieldName] . ': '. iconv('utf-8', 'ISO-8859-1//TRANSLIT', $fieldValue); + } + } + + return $headers; + } + /** * Generate headers. * @@ -90,7 +120,7 @@ abstract class PartHeader $headers = ''; foreach ($this->headers as $fieldName => $value) { $fieldValue = $this->generateHeaderFieldValue($value); - $headers .= $fieldName . ': ' . $fieldValue . "\n"; + $headers .= $this->headersOriginalKeys[$fieldName] . ': ' . $fieldValue . "\n"; } return $headers; @@ -99,19 +129,18 @@ abstract class PartHeader /** * Generates a header field value from the given value paramater. * - * @param array(string=>string)|string $value Header value - * + * @param string[]|string $value Header value * @return string */ protected function generateHeaderFieldValue($value) { $fieldValue = ''; - if (is_array($value)) { + if (is_array($value) === true) { if (isset($value['@'])) { $fieldValue .= $value['@']; } foreach ($value as $subName => $subValue) { - if ($subName != '@') { + if ($subName !== '@') { $fieldValue .= '; ' . $subName . '=' . $this->quoteValueString($subValue); } } @@ -134,8 +163,8 @@ abstract class PartHeader { if (preg_match('~[()<>@,;:\\"/\[\]?=]~', $string)) { return '"' . $string . '"'; - } else { - return $string; } + + return $string; } } diff --git a/tests/BeSimple/SoapServer/SoapServerTest.php b/tests/BeSimple/SoapServer/SoapServerTest.php index 10f4175..2a012cd 100644 --- a/tests/BeSimple/SoapServer/SoapServerTest.php +++ b/tests/BeSimple/SoapServer/SoapServerTest.php @@ -115,6 +115,35 @@ class SoapServerTest extends PHPUnit_Framework_TestCase self::assertCount(2, $response->getAttachments()); } + public function testHandleRequestWithSwaResponseAndLowerCaseHeaders() + { + $dummyService = new DummyService(); + $classMap = new ClassMap(); + foreach ($dummyService->getClassMap() as $type => $className) { + $classMap->add($type, $className); + } + $soapServerBuilder = new SoapServerBuilder(); + $soapServerOptions = SoapServerOptionsBuilder::createWithDefaults($dummyService); + $soapOptions = SoapOptionsBuilder::createSwaWithClassMap($dummyService->getWsdlPath(), $classMap); + $soapServer = $soapServerBuilder->build($soapServerOptions, $soapOptions); + + $request = $soapServer->createRequest( + $dummyService->getEndpoint(), + 'DummyService.dummyServiceMethodWithAttachments', + 'multipart/related; type="text/xml"; start=""; boundary="----=_Part_6_2094841787.1482231370463"', + file_get_contents(self::FIXTURES_DIR.'/Message/Request/dummyServiceMethodWithAttachmentsAndLowerCaseHeaders.request.mimepart.message') + ); + $response = $soapServer->handleRequest($request); + + file_put_contents(self::CACHE_DIR . '/SoapServerTestSwaResponseWithAttachmentsAndLowerCaseHeaders.xml', $response->getContent()); + + self::assertNotContains("\r\n", $response->getContent(), 'Response cannot contain CRLF line endings'); + self::assertContains('dummyServiceMethodWithAttachmentsResponse', $response->getContent()); + self::assertSame('DummyService.dummyServiceMethodWithAttachments', $response->getAction()); + self::assertTrue($response->hasAttachments(), 'Response should contain attachments'); + self::assertCount(2, $response->getAttachments()); + } + public function getSoapServerBuilder() { return new SoapServerBuilder(); diff --git a/tests/Fixtures/Message/Request/dummyServiceMethodWithAttachmentsAndLowerCaseHeaders.request.mimepart.message b/tests/Fixtures/Message/Request/dummyServiceMethodWithAttachmentsAndLowerCaseHeaders.request.mimepart.message new file mode 100644 index 0000000..c16cd3d --- /dev/null +++ b/tests/Fixtures/Message/Request/dummyServiceMethodWithAttachmentsAndLowerCaseHeaders.request.mimepart.message @@ -0,0 +1,62 @@ + +------=_Part_6_2094841787.1482231370463 +Content-type: text/xml; charset=UTF-8 +Content-transfer-Encoding: 8bit +Content-id: + + + + + admin + + + + + + 3 + true + + + + +------=_Part_6_2094841787.1482231370463 +Content-type: text/html; charset=us-ascii; name=test-page.html +Content-transfer-Encoding: 7bit +Content-id: +Content-disposition: attachment; name="test-page.html"; filename="test-page.html" + + + + + + Test file page + + + + +

Hello World!

+ + + +------=_Part_6_2094841787.1482231370463 +Content-type: application/x-sh; name=testscript.sh +Content-transfer-Encoding: binary +Content-id: +Content-disposition: attachment; name="testscript.sh"; filename="testscript.sh" + +#!/bin/sh +### ====================================================================== ### +## ## +## Test Script ## +## ## +### ====================================================================== ### + +------=_Part_6_2094841787.1482231370463--