From 84c37b1d24d690be50733818ad91bcd894f68d56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Bechyn=C4=9B?= Date: Tue, 8 Nov 2016 15:42:52 +0100 Subject: [PATCH] Soap server with attachments refactoring --- src/BeSimple/SoapClient/Curl.php | 5 +- src/BeSimple/SoapClient/MimeFilter.php | 54 ++-- src/BeSimple/SoapClient/SoapClient.php | 251 +++++++----------- src/BeSimple/SoapClient/WsdlDownloader.php | 48 +--- .../SoapCommon/AttachmentsHandler.php | 12 + .../Converter/MtomTypeConverter.php | 34 +-- .../Converter/SoapKernelAwareInterface.php | 32 --- .../SoapCommon/Converter/SwaTypeConverter.php | 20 -- src/BeSimple/SoapCommon/Mime/MultiPart.php | 83 +++--- src/BeSimple/SoapCommon/Mime/Parser.php | 135 ++++++---- src/BeSimple/SoapCommon/Mime/Part.php | 1 - src/BeSimple/SoapCommon/Mime/PartHeader.php | 26 +- src/BeSimple/SoapCommon/SoapMessage.php | 24 +- .../SoapCommon/SoapOptions/SoapOptions.php | 5 + .../SoapCommon/SoapRequestFactory.php | 4 +- .../AbstractStorage/AbstractStorage.php | 26 ++ .../RequestHandlerAttachmentsStorage.php | 25 ++ src/BeSimple/SoapServer/MimeFilter.php | 55 ++-- .../SoapOptions/SoapServerOptions.php | 18 ++ src/BeSimple/SoapServer/SoapResponse.php | 3 - .../SoapServer/SoapResponseFactory.php | 31 ++- src/BeSimple/SoapServer/SoapServer.php | 119 +++++++-- src/BeSimple/SoapServer/XmlMimeFilter.php | 2 +- 23 files changed, 511 insertions(+), 502 deletions(-) create mode 100644 src/BeSimple/SoapCommon/AttachmentsHandler.php delete mode 100644 src/BeSimple/SoapCommon/Converter/SoapKernelAwareInterface.php create mode 100644 src/BeSimple/SoapCommon/Storage/AbstractStorage/AbstractStorage.php create mode 100644 src/BeSimple/SoapCommon/Storage/RequestHandlerAttachmentsStorage.php diff --git a/src/BeSimple/SoapClient/Curl.php b/src/BeSimple/SoapClient/Curl.php index 7efee35..ef48e95 100644 --- a/src/BeSimple/SoapClient/Curl.php +++ b/src/BeSimple/SoapClient/Curl.php @@ -50,11 +50,10 @@ class Curl /** * Constructor. * - * @todo: do not use options as Array * @param array $options Options array from SoapClient constructor * @param int $followLocationMaxRedirects Redirection limit for Location header */ - public function __construct(array $options = array(), $followLocationMaxRedirects = 10) + public function __construct(array $options = [], $followLocationMaxRedirects = 10) { // set the default HTTP user agent if (!isset($options['user_agent'])) { @@ -126,7 +125,7 @@ class Curl /** * Execute HTTP request. - * Returns true if request was successfull. + * Returns true if request was successful. * * @param string $location HTTP location * @param string $request Request body diff --git a/src/BeSimple/SoapClient/MimeFilter.php b/src/BeSimple/SoapClient/MimeFilter.php index e28e706..898e48f 100644 --- a/src/BeSimple/SoapClient/MimeFilter.php +++ b/src/BeSimple/SoapClient/MimeFilter.php @@ -16,6 +16,7 @@ use BeSimple\SoapCommon\Helper; use BeSimple\SoapCommon\Mime\MultiPart as MimeMultiPart; use BeSimple\SoapCommon\Mime\Parser as MimeParser; use BeSimple\SoapCommon\Mime\Part as MimePart; +use BeSimple\SoapCommon\Mime\Part; use BeSimple\SoapCommon\SoapRequest; use BeSimple\SoapCommon\SoapRequestFilter; use BeSimple\SoapCommon\SoapResponse; @@ -31,31 +32,27 @@ class MimeFilter implements SoapRequestFilter, SoapResponseFilter public function filterRequest(SoapRequest $request, $attachmentType) { $attachmentsToSend = $request->getAttachments(); - - // build mime message if we have attachments if (count($attachmentsToSend) > 0) { - $multipart = new MimeMultiPart(); + $multipart = new MimeMultiPart('Part_' . rand(10, 15) . '_' . uniqid() . '.' . uniqid()); $soapPart = new MimePart($request->getContent(), 'text/xml', 'utf-8', MimePart::ENCODING_EIGHT_BIT); $soapVersion = $request->getVersion(); - // change content type headers for MTOM with SOAP 1.1 + if ($soapVersion == SOAP_1_1 && $attachmentType & Helper::ATTACHMENTS_TYPE_MTOM) { $multipart->setHeader('Content-Type', 'type', 'application/xop+xml'); $multipart->setHeader('Content-Type', 'start-info', 'text/xml'); $soapPart->setHeader('Content-Type', 'application/xop+xml'); $soapPart->setHeader('Content-Type', 'type', 'text/xml'); - } - // change content type headers for SOAP 1.2 - elseif ($soapVersion == SOAP_1_2) { + } elseif ($soapVersion == SOAP_1_2) { $multipart->setHeader('Content-Type', 'type', 'application/soap+xml'); $soapPart->setHeader('Content-Type', 'application/soap+xml'); } + $multipart->addPart($soapPart, true); foreach ($attachmentsToSend as $cid => $attachment) { $multipart->addPart($attachment, false); } $request->setContent($multipart->getMimeMessage()); - // TODO $headers = $multipart->getHeadersForHttp(); list(, $contentType) = explode(': ', $headers[0]); @@ -67,33 +64,26 @@ class MimeFilter implements SoapRequestFilter, SoapResponseFilter public function filterResponse(SoapResponse $response, $attachmentType) { - $attachmentsReceived = []; + $multiPartMessage = MimeParser::parseMimeMessage( + $response->getContent(), + ['Content-Type' => trim($response->getContentType())] + ); + $soapPart = $multiPartMessage->getMainPart(); + $attachments = $multiPartMessage->getAttachments(); - // check content type if it is a multipart mime message - $responseContentType = $response->getContentType(); - if (false !== stripos($responseContentType, 'multipart/related')) { - // parse mime message - $headers = array( - 'Content-Type' => trim($responseContentType), - ); - $multipart = MimeParser::parseMimeMessage($response->getContent(), $headers); - // get soap payload and update SoapResponse object - $soapPart = $multipart->getPart(); - // convert href -> myhref for external references as PHP throws exception in this case - // http://svn.php.net/viewvc/php/php-src/branches/PHP_5_4/ext/soap/php_encoding.c?view=markup#l3436 - $content = preg_replace('/href=(?!#)/', 'myhref=', $soapPart->getContent()); - $response->setContent($content); - $response->setContentType($soapPart->getHeader('Content-Type')); - // store attachments - $attachments = $multipart->getParts(false); - foreach ($attachments as $cid => $attachment) { - $attachmentsReceived[$cid] = $attachment; - } - } - if (count($attachmentsReceived) > 0) { - $response->setAttachments($attachmentsReceived); + $response->setContent($this->sanitizePhpExceptionOnHrefs($soapPart)); + $response->setContentType($soapPart->getHeader('Content-Type')); + if (count($attachments) > 0) { + $response->setAttachments($attachments); } return $response; } + + private function sanitizePhpExceptionOnHrefs(Part $soapPart) + { + // convert href -> myhref for external references as PHP throws exception in this case + // http://svn.php.net/viewvc/php/php-src/branches/PHP_5_4/ext/soap/php_encoding.c?view=markup#l3436 + return preg_replace('/href=(?!#)/', 'myhref=', $soapPart->getContent()); + } } diff --git a/src/BeSimple/SoapClient/SoapClient.php b/src/BeSimple/SoapClient/SoapClient.php index 2fde1be..c89eb25 100644 --- a/src/BeSimple/SoapClient/SoapClient.php +++ b/src/BeSimple/SoapClient/SoapClient.php @@ -12,9 +12,7 @@ namespace BeSimple\SoapClient; -use BeSimple\SoapCommon\Helper; -use BeSimple\SoapCommon\Converter\MtomTypeConverter; -use BeSimple\SoapCommon\Converter\SwaTypeConverter; +use BeSimple\SoapCommon\SoapKernel; use BeSimple\SoapCommon\SoapOptions\SoapOptions; use BeSimple\SoapCommon\SoapRequest; use BeSimple\SoapCommon\SoapRequestFactory; @@ -30,20 +28,10 @@ use BeSimple\SoapCommon\SoapRequestFactory; class SoapClient extends \SoapClient { protected $soapVersion; - - /** - * Tracing enabled? - * - * @var boolean - */ - protected $tracingEnabled = false; - - /** - * cURL instance. - * - * @var \BeSimple\SoapClient\Curl - */ - protected $curl = null; + protected $tracingEnabled; + protected $soapClientOptions; + protected $soapOptions; + protected $curl; /** * Last request headers. @@ -73,13 +61,6 @@ class SoapClient extends \SoapClient */ private $lastResponse = ''; - /** - * Soap kernel. - * - * @var \BeSimple\SoapClient\SoapKernel - */ - protected $soapKernel = null; - /** * Constructor. * @@ -88,44 +69,99 @@ class SoapClient extends \SoapClient */ public function __construct(SoapClientOptions $soapClientOptions, SoapOptions $soapOptions) { - $this->soapKernel = new SoapKernel(); $this->soapVersion = $soapOptions->getSoapVersion(); $this->tracingEnabled = $soapClientOptions->getTrace(); + $this->soapClientOptions = $soapClientOptions; + $this->soapOptions = $soapOptions; - // @todo: refactor SoapClient: do not use $options as array - $options = $this->configureMime($soapOptions->toArray()); - - // @todo: refactor SoapClient: do not use $options as array + // @todo: refactor SoapClient: do not use $options as array: refactor Curl $this->curl = new Curl($soapClientOptions->toArray()); - // @todo: refactor SoapClient: do not use $options as array - $wsdlFile = $this->loadWsdl($soapOptions->getWsdlFile(), $soapOptions->toArray()); + $wsdlFile = $this->loadWsdl( + $soapOptions->getWsdlFile(), + $soapOptions->getWsdlCacheType() + ); - parent::__construct($wsdlFile, $options); + parent::__construct( + $wsdlFile, + $soapClientOptions->toArray() + $soapOptions->toArray() + ); } + /** + * Custom request method to be able to modify the SOAP messages. + * $oneWay parameter is not used at the moment. + * + * @param string $request Request string + * @param string $location Location + * @param string $action SOAP action + * @param int $version SOAP version + * @param int $oneWay 0|1 + * + * @return string + */ + public function __doRequest($request, $location, $action, $version, $oneWay = 0) + { + $soapRequest = $this->createSoapRequest($location, $action, $version, $request); + $soapResponse = $this->getSoapResponseFromRequest($soapRequest); + + return $soapResponse->getContent(); + } + + private function createSoapRequest($location, $action, $version, $request) + { + $soapRequest = SoapRequestFactory::create($location, $action, $version, $request); + if ($this->soapOptions->hasAttachments()) { + $soapKernel = new SoapKernel(); + $soapRequest = $soapKernel->filterRequest( + $soapRequest, + $this->getAttachmentFilters(), + $this->soapOptions->getAttachmentType() + ); + } + + return $soapRequest; + } + + /** + * Runs the currently registered request filters on the request, performs + * the HTTP request and runs the response filters. + * + * @param SoapRequest $soapRequest SOAP request object + * + * @return SoapResponse + */ + private function getSoapResponseFromRequest(SoapRequest $soapRequest) + { + $soapResponse = $this->performHttpSoapRequest($soapRequest); + if ($this->soapOptions->hasAttachments()) { + $soapKernel = new SoapKernel(); + $soapKernel->filterResponse($soapResponse, $this->getAttachmentFilters(), $this->soapOptions->getAttachmentType()); + } + + return $soapResponse; + } /** * Perform HTTP request with cURL. * * @param SoapRequest $soapRequest SoapRequest object - * * @return SoapResponse */ - private function __doHttpRequest(SoapRequest $soapRequest) + private function performHttpSoapRequest(SoapRequest $soapRequest) { // HTTP headers $soapVersion = $soapRequest->getVersion(); $soapAction = $soapRequest->getAction(); if (SOAP_1_1 == $soapVersion) { - $headers = array( + $headers = [ 'Content-Type:' . $soapRequest->getContentType(), 'SOAPAction: "' . $soapAction . '"', - ); + ]; } else { - $headers = array( + $headers = [ 'Content-Type:' . $soapRequest->getContentType() . '; action="' . $soapAction . '"', - ); + ]; } $location = $soapRequest->getLocation(); @@ -135,8 +171,7 @@ class SoapClient extends \SoapClient $options = $this->filterRequestOptions($soapRequest); - // execute HTTP request with cURL - $responseSuccessfull = $this->curl->exec( + $responseSuccessful = $this->curl->exec( $location, $content, $headers, @@ -149,7 +184,7 @@ class SoapClient extends \SoapClient $this->lastRequest = $soapRequest->getContent(); } // in case of an error while making the http request throw a soapFault - if ($responseSuccessfull === false) { + if ($responseSuccessful === false) { // get error message from curl $faultstring = $this->curl->getErrorMessage(); throw new \SoapFault('HTTP', $faultstring); @@ -171,53 +206,6 @@ class SoapClient extends \SoapClient return $soapResponse; } - /** - * Custom request method to be able to modify the SOAP messages. - * $oneWay parameter is not used at the moment. - * - * @todo: refactor SoapClient: refactoring starts from here - * @param string $request Request string - * @param string $location Location - * @param string $action SOAP action - * @param int $version SOAP version - * @param int $oneWay 0|1 - * - * @return string - */ - public function __doRequest($request, $location, $action, $version, $oneWay = 0) - { - // wrap request data in SoapRequest object - $soapRequest = SoapRequestFactory::create($location, $action, $version, $request); - - // do actual SOAP request - $soapResponse = $this->__doRequest2($soapRequest); - - // return SOAP response to ext/soap - return $soapResponse->getContent(); - } - - /** - * Runs the currently registered request filters on the request, performs - * the HTTP request and runs the response filters. - * - * @param SoapRequest $soapRequest SOAP request object - * - * @return SoapResponse - */ - protected function __doRequest2(SoapRequest $soapRequest) - { - // run SoapKernel on SoapRequest - $this->soapKernel->filterRequest($soapRequest); - - // perform HTTP request with cURL - $soapResponse = $this->__doHttpRequest($soapRequest); - - // run SoapKernel on SoapResponse - $this->soapKernel->filterResponse($soapResponse); - - return $soapResponse; - } - /** * Filters HTTP headers which will be sent * @@ -240,7 +228,7 @@ class SoapClient extends \SoapClient */ protected function filterRequestOptions(SoapRequest $soapRequest) { - return array(); + return []; } /** @@ -284,81 +272,34 @@ class SoapClient extends \SoapClient } /** - * Get SoapKernel instance. - * - * @return \BeSimple\SoapClient\SoapKernel - */ - public function getSoapKernel() - { - return $this->soapKernel; - } - - private function configureMime(array $options) - { - // @todo: PBe: refactor same as SoapServer - if (Helper::ATTACHMENTS_TYPE_BASE64 !== $options['attachment_type']) { - // register mime filter in SoapKernel - $mimeFilter = new MimeFilter($options['attachment_type']); - $this->soapKernel->registerFilter($mimeFilter); - // configure type converter - if (Helper::ATTACHMENTS_TYPE_SWA === $options['attachment_type']) { - $converter = new SwaTypeConverter(); - $converter->setKernel($this->soapKernel); - } elseif (Helper::ATTACHMENTS_TYPE_MTOM === $options['attachment_type']) { - $xmlMimeFilter = new XmlMimeFilter($options['attachment_type']); - $this->soapKernel->registerFilter($xmlMimeFilter); - $converter = new MtomTypeConverter(); - $converter->setKernel($this->soapKernel); - } - // configure typemap - if (!isset($options['typemap'])) { - $options['typemap'] = array(); - } - $options['typemap'][] = array( - 'type_name' => $converter->getTypeName(), - 'type_ns' => $converter->getTypeNamespace(), - 'from_xml' => function($input) use ($converter) { - return $converter->convertXmlToPhp($input); - }, - 'to_xml' => function($input) use ($converter) { - return $converter->convertPhpToXml($input); - }, - ); - } - - return $options; - } - - /** - * Downloads WSDL files with cURL. Uses all SoapClient options for - * authentication. Uses the WSDL_CACHE_* constants and the 'soap.wsdl_*' - * ini settings. Does only file caching as SoapClient only supports a file - * name parameter. - * - * @param string $wsdl WSDL file - * @param array(string=>mixed) $options Options array + * @param string $wsdl + * @param int $wsdlCache + * @param bool $resolveRemoteIncludes * * @return string */ - protected function loadWsdl($wsdl, array $options) + private function loadWsdl($wsdl, $wsdlCache, $resolveRemoteIncludes = true) { - // option to resolve wsdl/xsd includes - $resolveRemoteIncludes = true; - if (isset($options['resolve_wsdl_remote_includes'])) { - $resolveRemoteIncludes = $options['resolve_wsdl_remote_includes']; - } - // option to enable cache - $wsdlCache = WSDL_CACHE_DISK; - if (isset($options['cache_wsdl'])) { - $wsdlCache = $options['cache_wsdl']; - } $wsdlDownloader = new WsdlDownloader($this->curl, $resolveRemoteIncludes, $wsdlCache); try { $cacheFileName = $wsdlDownloader->download($wsdl); } catch (\RuntimeException $e) { - throw new \SoapFault('WSDL', "SOAP-ERROR: Parsing WSDL: Couldn't load from '" . $wsdl . "' : failed to load external entity \"" . $wsdl . "\""); + throw new \SoapFault('WSDL', "SOAP-ERROR: Parsing WSDL: Couldn't load from '" . $wsdl . "'"); } return $cacheFileName; } -} \ No newline at end of file + + private function getAttachmentFilters() + { + $filters = []; + if ($this->soapOptions->getAttachmentType() !== SoapOptions::SOAP_ATTACHMENTS_TYPE_BASE64) { + $filters[] = new MimeFilter(); + } + if ($this->soapOptions->getAttachmentType() === SoapOptions::SOAP_ATTACHMENTS_TYPE_MTOM) { + $filters[] = new XmlMimeFilter(); + } + + return $filters; + } +} diff --git a/src/BeSimple/SoapClient/WsdlDownloader.php b/src/BeSimple/SoapClient/WsdlDownloader.php index 97a7d67..43253c2 100644 --- a/src/BeSimple/SoapClient/WsdlDownloader.php +++ b/src/BeSimple/SoapClient/WsdlDownloader.php @@ -25,54 +25,22 @@ use BeSimple\SoapCommon\Helper; */ class WsdlDownloader { - /** - * Cache enabled. - * - * @var bool - */ + protected $curl; + protected $resolveRemoteIncludes = true; protected $cacheEnabled; - - /** - * Cache dir. - * - * @var string - */ protected $cacheDir; - - /** - * Cache TTL. - * - * @var int - */ protected $cacheTtl; /** - * cURL instance for downloads. - * - * @var unknown_type + * @param Curl $curl + * @param int $cacheWsdl = Cache::TYPE_NONE|Cache::WSDL_CACHE_DISK|Cache::WSDL_CACHE_BOTH|Cache::WSDL_CACHE_MEMORY + * @param boolean $resolveRemoteIncludes */ - protected $curl; - - /** - * Resolve WSDl/XSD includes. - * - * @var boolean - */ - protected $resolveRemoteIncludes = true; - - /** - * Constructor. - * - * @param \BeSimple\SoapClient\Curl $curl Curl instance - * @param boolean $resolveRemoteIncludes WSDL/XSD include enabled? - * @param boolean $cacheWsdl Cache constant - */ - public function __construct(Curl $curl, $resolveRemoteIncludes = true, $cacheWsdl = Cache::TYPE_DISK) + public function __construct(Curl $curl, $cacheWsdl, $resolveRemoteIncludes = true) { - $this->curl = $curl; - $this->resolveRemoteIncludes = (Boolean) $resolveRemoteIncludes; + $this->curl = $curl; + $this->resolveRemoteIncludes = $resolveRemoteIncludes; - // get current WSDL caching config $this->cacheEnabled = $cacheWsdl === Cache::TYPE_NONE ? Cache::DISABLED : Cache::ENABLED == Cache::isEnabled(); $this->cacheDir = Cache::getDirectory(); $this->cacheTtl = Cache::getLifetime(); diff --git a/src/BeSimple/SoapCommon/AttachmentsHandler.php b/src/BeSimple/SoapCommon/AttachmentsHandler.php new file mode 100644 index 0000000..4de27b4 --- /dev/null +++ b/src/BeSimple/SoapCommon/AttachmentsHandler.php @@ -0,0 +1,12 @@ + inconsistent - adding storage, getting items - WTF APi? */ +interface AttachmentsHandler +{ + public function addAttachmentStorage(RequestHandlerAttachmentsStorage $requestHandlerAttachmentsStorage); + public function getAttachmentsFromStorage(); +} diff --git a/src/BeSimple/SoapCommon/Converter/MtomTypeConverter.php b/src/BeSimple/SoapCommon/Converter/MtomTypeConverter.php index 52ea961..f3bf76f 100644 --- a/src/BeSimple/SoapCommon/Converter/MtomTypeConverter.php +++ b/src/BeSimple/SoapCommon/Converter/MtomTypeConverter.php @@ -14,16 +14,13 @@ namespace BeSimple\SoapCommon\Converter; use BeSimple\SoapCommon\Helper; use BeSimple\SoapCommon\Mime\Part as MimePart; -use BeSimple\SoapCommon\SoapKernel; -use BeSimple\SoapCommon\Converter\SoapKernelAwareInterface; -use BeSimple\SoapCommon\Converter\TypeConverterInterface; /** * MTOM type converter. * * @author Andreas Schamberger */ -class MtomTypeConverter implements TypeConverterInterface, SoapKernelAwareInterface +class MtomTypeConverter implements TypeConverterInterface { /** * @var \BeSimple\SoapCommon\SoapKernel $soapKernel SoapKernel instance @@ -54,25 +51,6 @@ class MtomTypeConverter implements TypeConverterInterface, SoapKernelAwareInterf $doc = new \DOMDocument(); $doc->loadXML($data); - $includes = $doc->getElementsByTagNameNS(Helper::NS_XOP, 'Include'); - $include = $includes->item(0); - - // convert href -> myhref for external references as PHP throws exception in this case - // http://svn.php.net/viewvc/php/php-src/branches/PHP_5_4/ext/soap/php_encoding.c?view=markup#l3436 - $ref = $include->getAttribute('myhref'); - - if ('cid:' === substr($ref, 0, 4)) { - $contentId = urldecode(substr($ref, 4)); - - if (null !== ($part = $this->soapKernel->getAttachment($contentId))) { - - return $part->getContent(); - } else { - - return null; - } - } - return $data; } @@ -84,8 +62,6 @@ class MtomTypeConverter implements TypeConverterInterface, SoapKernelAwareInterf $part = new MimePart($data); $contentId = trim($part->getHeader('Content-ID'), '<>'); - $this->soapKernel->addAttachment($part); - $doc = new \DOMDocument(); $node = $doc->createElement($this->getTypeName()); $doc->appendChild($node); @@ -97,12 +73,4 @@ class MtomTypeConverter implements TypeConverterInterface, SoapKernelAwareInterf return $doc->saveXML(); } - - /** - * {@inheritDoc} - */ - public function setKernel(SoapKernel $soapKernel) - { - $this->soapKernel = $soapKernel; - } } diff --git a/src/BeSimple/SoapCommon/Converter/SoapKernelAwareInterface.php b/src/BeSimple/SoapCommon/Converter/SoapKernelAwareInterface.php deleted file mode 100644 index 655a258..0000000 --- a/src/BeSimple/SoapCommon/Converter/SoapKernelAwareInterface.php +++ /dev/null @@ -1,32 +0,0 @@ - - * (c) Francis Besset - * - * This source file is subject to the MIT license that is bundled - * with this source code in the file LICENSE. - */ - -namespace BeSimple\SoapCommon\Converter; - -use BeSimple\SoapCommon\SoapKernel; - -/** - * Internal type converter interface. - * - * @author Andreas Schamberger - */ -interface SoapKernelAwareInterface -{ - /** - * Set SoapKernel instance. - * - * @param \BeSimple\SoapCommon\SoapKernel $soapKernel SoapKernel instance - * - * @return void - */ - function setKernel(SoapKernel $soapKernel); -} \ No newline at end of file diff --git a/src/BeSimple/SoapCommon/Converter/SwaTypeConverter.php b/src/BeSimple/SoapCommon/Converter/SwaTypeConverter.php index 1d434f1..71d31d1 100644 --- a/src/BeSimple/SoapCommon/Converter/SwaTypeConverter.php +++ b/src/BeSimple/SoapCommon/Converter/SwaTypeConverter.php @@ -35,23 +35,6 @@ class SwaTypeConverter implements TypeConverterInterface $doc = new \DOMDocument(); $doc->loadXML($data); - // convert href -> myhref for external references as PHP throws exception in this case - // http://svn.php.net/viewvc/php/php-src/branches/PHP_5_4/ext/soap/php_encoding.c?view=markup#l3436 - $ref = $doc->documentElement->getAttribute('myhref'); - - if ('cid:' === substr($ref, 0, 4)) { - $contentId = urldecode(substr($ref, 4)); - - // @todo-critical: ci je nyni zodpovednost vygetovat attachmenty - if (null !== ($part = $this->soapKernel->getAttachment($contentId))) { - - return $part->getContent(); - } else { - - return null; - } - } - return $data; } @@ -63,9 +46,6 @@ class SwaTypeConverter implements TypeConverterInterface $part = new MimePart($data); $contentId = trim($part->getHeader('Content-ID'), '<>'); - // @todo-critical: ci je nyni zodpovednost nastrkat attachmenty - //$this->soapKernel->addAttachment($part); - return sprintf('<%s href="%s"/>', $this->getTypeName(), 'cid:' . $contentId); } } diff --git a/src/BeSimple/SoapCommon/Mime/MultiPart.php b/src/BeSimple/SoapCommon/Mime/MultiPart.php index 1b8ffa3..622f471 100644 --- a/src/BeSimple/SoapCommon/Mime/MultiPart.php +++ b/src/BeSimple/SoapCommon/Mime/MultiPart.php @@ -12,6 +12,7 @@ namespace BeSimple\SoapCommon\Mime; +use Exception; use BeSimple\SoapCommon\Helper; /** @@ -38,16 +39,14 @@ class MultiPart extends PartHeader /** * Mime parts. * - * @var array(\BeSimple\SoapCommon\Mime\Part) + * @var \BeSimple\SoapCommon\Mime\Part[] */ - protected $parts = array(); + protected $parts = []; /** * Construct new mime object. * - * @param string $boundary Boundary string - * - * @return void + * @param string $boundary */ public function __construct($boundary = null) { @@ -55,10 +54,9 @@ class MultiPart extends PartHeader $this->setHeader('Content-Type', 'multipart/related'); $this->setHeader('Content-Type', 'type', 'text/xml'); $this->setHeader('Content-Type', 'charset', 'utf-8'); - if (is_null($boundary)) { - $boundary = $this->generateBoundary(); + if ($boundary !== null) { + $this->setHeader('Content-Type', 'boundary', $boundary); } - $this->setHeader('Content-Type', 'boundary', $boundary); } /** @@ -73,10 +71,11 @@ class MultiPart extends PartHeader $message = ($withHeaders === true) ? $this->generateHeaders() : ""; // add parts foreach ($this->parts as $part) { - $message .= "\r\n" . '--' . $this->getHeader('Content-Type', 'boundary') . "\r\n"; + $message .= "\n" . '--' . $this->getHeader('Content-Type', 'boundary') . "\n"; $message .= $part->getMessagePart(); } - $message .= "\r\n" . '--' . $this->getHeader('Content-Type', 'boundary') . '--'; + $message .= "\n" . '--' . $this->getHeader('Content-Type', 'boundary') . '--'; + return $message; } @@ -84,22 +83,23 @@ class MultiPart extends PartHeader * Get string array with MIME headers for usage in HTTP header (with CURL). * Only 'Content-Type' and 'Content-Description' headers are returned. * - * @return arrray(string) + * @return string[] */ public function getHeadersForHttp() { - $allowed = array( + $allowedHeaders = [ 'Content-Type', 'Content-Description', - ); - $headers = array(); + ]; + $headers = []; foreach ($this->headers as $fieldName => $value) { - if (in_array($fieldName, $allowed)) { + 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; } @@ -122,44 +122,51 @@ class MultiPart extends PartHeader } /** - * Get part with given content id. If there is no content id given it - * returns the main part that is defined through the content-id start - * parameter. + * Get part with given content id. * * @param string $contentId Content id of desired part * - * @return \BeSimple\SoapCommon\Mime\Part|null + * @return \BeSimple\SoapCommon\Mime\Part */ - public function getPart($contentId = null) + public function getPart($contentId) { - if (is_null($contentId)) { - $contentId = $this->mainPartContentId; - } if (isset($this->parts[$contentId])) { return $this->parts[$contentId]; } - return null; + + throw new Exception('MimePart not found by ID: ' . $contentId); } /** - * Get all parts. + * Get main part. * - * @param boolean $includeMainPart Should main part be in result set - * - * @return array(\BeSimple\SoapCommon\Mime\Part) + * @return \BeSimple\SoapCommon\Mime\Part */ - public function getParts($includeMainPart = false) + public function getMainPart() { - if ($includeMainPart === true) { - $parts = $this->parts; - } else { - $parts = array(); - foreach ($this->parts as $cid => $part) { - if ($cid != $this->mainPartContentId) { - $parts[$cid] = $part; - } + foreach ($this->parts as $cid => $part) { + if ($cid === $this->mainPartContentId) { + return $part; } } + + throw new Exception('SoapRequest error: main part not found by Id: ' . $this->mainPartContentId); + } + + /** + * Get attachment parts. + * + * @return \BeSimple\SoapCommon\Mime\Part[] + */ + public function getAttachments() + { + $parts = []; + foreach ($this->parts as $cid => $part) { + if ($cid !== $this->mainPartContentId) { + $parts[$cid] = $part; + } + } + return $parts; } @@ -168,7 +175,7 @@ class MultiPart extends PartHeader * * @return string */ - protected function generateBoundary() + public function generateBoundary() { return 'urn:uuid:' . Helper::generateUUID(); } diff --git a/src/BeSimple/SoapCommon/Mime/Parser.php b/src/BeSimple/SoapCommon/Mime/Parser.php index c4a8c27..5f98b72 100644 --- a/src/BeSimple/SoapCommon/Mime/Parser.php +++ b/src/BeSimple/SoapCommon/Mime/Parser.php @@ -27,7 +27,7 @@ class Parser * * @return \BeSimple\SoapCommon\Mime\MultiPart */ - public static function parseMimeMessage($mimeMessage, array $headers = array()) + public static function parseMimeMessage($mimeMessage, array $headers = []) { $boundary = null; $start = null; @@ -37,7 +37,7 @@ class Parser // add given headers, e.g. coming from HTTP headers if (count($headers) > 0) { foreach ($headers as $name => $value) { - if ($name == 'Content-Type') { + if ($name === 'Content-Type') { self::parseContentTypeHeader($multipart, $name, $value); $boundary = $multipart->getHeader('Content-Type', 'boundary'); $start = $multipart->getHeader('Content-Type', 'start'); @@ -49,43 +49,57 @@ class Parser } $content = ''; $currentPart = $multipart; - $lines = preg_split("/(\r\n)/", $mimeMessage); - foreach ($lines as $line) { - // ignore http status code and POST * - if (substr($line, 0, 5) == 'HTTP/' || substr($line, 0, 4) == 'POST') { - continue; - } - if (isset($currentHeader)) { - if (isset($line[0]) && ($line[0] === ' ' || $line[0] === "\t")) { - $currentHeader .= $line; + $lines = preg_split("/(\r\n)|(\n)/", $mimeMessage); + if (self::hasBoundary($lines)) { + foreach ($lines as $line) { + // ignore http status code and POST * + if (substr($line, 0, 5) == 'HTTP/' || substr($line, 0, 4) == 'POST') { continue; } - if (strpos($currentHeader, ':') !== false) { - list($headerName, $headerValue) = explode(':', $currentHeader, 2); - $headerValue = iconv_mime_decode($headerValue, 0, 'utf-8'); - if (strpos($headerValue, ';') !== false) { - self::parseContentTypeHeader($currentPart, $headerName, $headerValue); - $boundary = $multipart->getHeader('Content-Type', 'boundary'); - $start = $multipart->getHeader('Content-Type', 'start'); - } else { - $currentPart->setHeader($headerName, trim($headerValue)); + if (isset($currentHeader)) { + if (isset($line[0]) && ($line[0] === ' ' || $line[0] === "\t")) { + $currentHeader .= $line; + continue; } + if (strpos($currentHeader, ':') !== false) { + list($headerName, $headerValue) = explode(':', $currentHeader, 2); + $headerValue = iconv_mime_decode($headerValue, 0, 'utf-8'); + if (strpos($headerValue, ';') !== false) { + self::parseContentTypeHeader($currentPart, $headerName, $headerValue); + $boundary = $multipart->getHeader('Content-Type', 'boundary'); + $start = $multipart->getHeader('Content-Type', 'start'); + } else { + $currentPart->setHeader($headerName, trim($headerValue)); + } + } + unset($currentHeader); } - unset($currentHeader); - } - if ($inHeader) { - if (trim($line) == '') { - $inHeader = false; + if ($inHeader) { + if (trim($line) == '') { + $inHeader = false; + continue; + } + $currentHeader = $line; continue; - } - $currentHeader = $line; - continue; - } else { - // check if we hit any of the boundaries - if (strlen($line) > 0 && $line[0] == "-") { - if (strcmp(trim($line), '--' . $boundary) === 0) { - if ($currentPart instanceof Part) { - $content = substr($content, 0, -2); + } else { + if (self::isBoundary($line)) { + if (strcmp(trim($line), '--' . $boundary) === 0) { + if ($currentPart instanceof Part) { + $content = substr($content, 0, -1); + self::decodeContent($currentPart, $content); + // check if there is a start parameter given, if not set first part + $isMain = (is_null($start) || $start == $currentPart->getHeader('Content-ID')) ? true : false; + if ($isMain === true) { + $start = $currentPart->getHeader('Content-ID'); + } + $multipart->addPart($currentPart, $isMain); + } + $currentPart = new Part(); + $hitFirstBoundary = true; + $inHeader = true; + $content = ''; + } elseif (strcmp(trim($line), '--' . $boundary . '--') === 0) { + $content = substr($content, 0, -1); self::decodeContent($currentPart, $content); // check if there is a start parameter given, if not set first part $isMain = (is_null($start) || $start == $currentPart->getHeader('Content-ID')) ? true : false; @@ -93,34 +107,24 @@ class Parser $start = $currentPart->getHeader('Content-ID'); } $multipart->addPart($currentPart, $isMain); + $content = ''; } - $currentPart = new Part(); - $hitFirstBoundary = true; - $inHeader = true; - $content = ''; - } elseif (strcmp(trim($line), '--' . $boundary . '--') === 0) { - $content = substr($content, 0, -2); - self::decodeContent($currentPart, $content); - // check if there is a start parameter given, if not set first part - $isMain = (is_null($start) || $start == $currentPart->getHeader('Content-ID')) ? true : false; - if ($isMain === true) { - $start = $currentPart->getHeader('Content-ID'); + } else { + if ($hitFirstBoundary === false) { + if (trim($line) !== '') { + $inHeader = true; + $currentHeader = $line; + continue; + } } - $multipart->addPart($currentPart, $isMain); - $content = ''; + $content .= $line . "\n"; } - } else { - if ($hitFirstBoundary === false) { - if (trim($line) != '') { - $inHeader = true; - $currentHeader = $line; - continue; - } - } - $content .= $line . "\r\n"; } } + } else { + $multipart->addPart(new Part($mimeMessage), true); } + return $multipart; } @@ -135,7 +139,6 @@ class Parser * @param string $headerName Header name * @param string $headerValue Header value * - * @return null */ private static function parseContentTypeHeader(PartHeader $part, $headerName, $headerValue) { @@ -168,7 +171,6 @@ class Parser * @param \BeSimple\SoapCommon\Mime\Part $part Part to add content * @param string $content Content to decode * - * @return null */ private static function decodeContent(Part $part, $content) { @@ -184,4 +186,21 @@ class Parser } $part->setContent($content); } + + private static function hasBoundary(array $lines) + { + foreach ($lines as $line) { + if (self::isBoundary($line)) { + + return true; + } + } + + return false; + } + + private static function isBoundary($line) + { + return strlen($line) > 0 && $line[0] === "-"; + } } \ No newline at end of file diff --git a/src/BeSimple/SoapCommon/Mime/Part.php b/src/BeSimple/SoapCommon/Mime/Part.php index 714f137..476c00d 100644 --- a/src/BeSimple/SoapCommon/Mime/Part.php +++ b/src/BeSimple/SoapCommon/Mime/Part.php @@ -69,7 +69,6 @@ class Part extends PartHeader * @param string $encoding Encoding * @param string $contentId Content id * - * @return void */ public function __construct($content = null, $contentType = 'application/octet-stream', $charset = null, $encoding = self::ENCODING_BINARY, $contentId = null) { diff --git a/src/BeSimple/SoapCommon/Mime/PartHeader.php b/src/BeSimple/SoapCommon/Mime/PartHeader.php index ff0dcfc..1b58fff 100644 --- a/src/BeSimple/SoapCommon/Mime/PartHeader.php +++ b/src/BeSimple/SoapCommon/Mime/PartHeader.php @@ -19,12 +19,7 @@ namespace BeSimple\SoapCommon\Mime; */ abstract class PartHeader { - /** - * Mime headers. - * - * @var array(string=>mixed|array(mixed)) - */ - protected $headers = array(); + protected $headers = []; /** * Add a new header to the mime part. @@ -39,10 +34,10 @@ abstract class PartHeader { if (isset($this->headers[$name]) && !is_null($subValue)) { if (!is_array($this->headers[$name])) { - $this->headers[$name] = array( + $this->headers[$name] = [ '@' => $this->headers[$name], $value => $subValue, - ); + ]; } else { $this->headers[$name][$value] = $subValue; } @@ -76,6 +71,7 @@ abstract class PartHeader return $this->headers[$name]; } } + return null; } @@ -86,19 +82,12 @@ abstract class PartHeader */ protected function generateHeaders() { - $charset = strtolower($this->getHeader('Content-Type', 'charset')); - $preferences = array( - 'scheme' => 'Q', - 'input-charset' => 'utf-8', - 'output-charset' => $charset, - ); $headers = ''; foreach ($this->headers as $fieldName => $value) { $fieldValue = $this->generateHeaderFieldValue($value); - // do not use proper encoding as Apache Axis does not understand this - // $headers .= iconv_mime_encode($field_name, $field_value, $preferences) . "\r\n"; - $headers .= $fieldName . ': ' . $fieldValue . "\r\n"; + $headers .= $fieldName . ': ' . $fieldValue . "\n"; } + return $headers; } @@ -124,6 +113,7 @@ abstract class PartHeader } else { $fieldValue .= $value; } + return $fieldValue; } @@ -143,4 +133,4 @@ abstract class PartHeader return $string; } } -} \ No newline at end of file +} diff --git a/src/BeSimple/SoapCommon/SoapMessage.php b/src/BeSimple/SoapCommon/SoapMessage.php index 1b91c1a..e2d42f1 100644 --- a/src/BeSimple/SoapCommon/SoapMessage.php +++ b/src/BeSimple/SoapCommon/SoapMessage.php @@ -45,12 +45,12 @@ abstract class SoapMessage /** * Content types for SOAP versions. * - * @var array(string=>string) + * @var array (string=>string) */ - static protected $versionToContentTypeMap = array( + static protected $versionToContentTypeMap = [ SOAP_1_1 => 'text/xml; charset=utf-8', SOAP_1_2 => 'application/soap+xml; charset=utf-8' - ); + ]; /** * SOAP action. @@ -64,7 +64,7 @@ abstract class SoapMessage * * @var array(\BeSimple\SoapCommon\Mime\Part) */ - protected $attachments = array(); + protected $attachments; /** * Message content (MIME Message or SOAP Envelope). @@ -111,8 +111,8 @@ abstract class SoapMessage */ public static function getContentTypeForVersion($version) { - if (!in_array($version, array(SOAP_1_1, SOAP_1_2))) { - throw new \InvalidArgumentException("The 'version' argument has to be either 'SOAP_1_1' or 'SOAP_1_2'!"); + if (!in_array($version, [SOAP_1_1, SOAP_1_2])) { + throw new \InvalidArgumentException('Invalid SOAP version: ' . $version); } return self::$versionToContentTypeMap[$version]; @@ -138,10 +138,15 @@ abstract class SoapMessage $this->action = $action; } + public function hasAttachments() + { + return $this->attachments !== null; + } + /** * Get attachments. * - * @return array(\BeSimple\SoapCommon\Mime\Part) + * @return \BeSimple\SoapCommon\Mime\Part[] */ public function getAttachments() { @@ -151,7 +156,7 @@ abstract class SoapMessage /** * Set SOAP action. * - * @param array(\BeSimple\SoapCommon\Mime\Part) $attachments Attachment array + * @param \BeSimple\SoapCommon\Mime\Part[] $attachments */ public function setAttachments(array $attachments) { @@ -165,10 +170,11 @@ abstract class SoapMessage */ public function getContent() { - if (null !== $this->contentDomDocument) { + if ($this->contentDomDocument !== null) { $this->content = $this->contentDomDocument->saveXML(); $this->contentDomDocument = null; } + return $this->content; } diff --git a/src/BeSimple/SoapCommon/SoapOptions/SoapOptions.php b/src/BeSimple/SoapCommon/SoapOptions/SoapOptions.php index 8d7cb13..f878b96 100644 --- a/src/BeSimple/SoapCommon/SoapOptions/SoapOptions.php +++ b/src/BeSimple/SoapCommon/SoapOptions/SoapOptions.php @@ -90,6 +90,11 @@ class SoapOptions return $this->wsdlCacheDir; } + public function isWsdlCached() + { + return $this->wsdlCacheType !== self::SOAP_CACHE_TYPE_NONE; + } + public function getWsdlCacheType() { return $this->wsdlCacheType; diff --git a/src/BeSimple/SoapCommon/SoapRequestFactory.php b/src/BeSimple/SoapCommon/SoapRequestFactory.php index a327965..8d07fae 100644 --- a/src/BeSimple/SoapCommon/SoapRequestFactory.php +++ b/src/BeSimple/SoapCommon/SoapRequestFactory.php @@ -10,11 +10,12 @@ class SoapRequestFactory * @param string $location Location * @param string $action SOAP action * @param string $version SOAP version + * @param string $contentType Content Type * @param string $content Content * * @return SoapRequest */ - public static function create($location, $action, $version, $content = null) + public static function create($location, $action, $version, $contentType, $content = null) { $request = new SoapRequest(); // $content is if unmodified from SoapClient not a php string type! @@ -22,7 +23,6 @@ class SoapRequestFactory $request->setLocation($location); $request->setAction($action); $request->setVersion($version); - $contentType = SoapMessage::getContentTypeForVersion($version); $request->setContentType($contentType); return $request; diff --git a/src/BeSimple/SoapCommon/Storage/AbstractStorage/AbstractStorage.php b/src/BeSimple/SoapCommon/Storage/AbstractStorage/AbstractStorage.php new file mode 100644 index 0000000..749d400 --- /dev/null +++ b/src/BeSimple/SoapCommon/Storage/AbstractStorage/AbstractStorage.php @@ -0,0 +1,26 @@ +items; + $this->resetItems(); + + return $items; + } + + protected function setItems(array $items) + { + $this->items = $items; + } + + private function resetItems() + { + $this->items = []; + } +} diff --git a/src/BeSimple/SoapCommon/Storage/RequestHandlerAttachmentsStorage.php b/src/BeSimple/SoapCommon/Storage/RequestHandlerAttachmentsStorage.php new file mode 100644 index 0000000..ea8996d --- /dev/null +++ b/src/BeSimple/SoapCommon/Storage/RequestHandlerAttachmentsStorage.php @@ -0,0 +1,25 @@ +getContent(), + ['Content-Type' => trim($request->getContentType())] + ); + $soapPart = $multiPartMessage->getMainPart(); + $attachments = $multiPartMessage->getAttachments(); - // check content type if it is a multipart mime message - $requestContentType = $request->getContentType(); - if (stripos($requestContentType, 'multipart/related') !== false) { - // parse mime message - $headers = [ - 'Content-Type' => trim($requestContentType), - ]; - $multipart = MimeParser::parseMimeMessage($request->getContent(), $headers); - // get soap payload and update SoapResponse object - $soapPart = $multipart->getPart(); - // convert href -> myhref for external references as PHP throws exception in this case - // http://svn.php.net/viewvc/php/php-src/branches/PHP_5_4/ext/soap/php_encoding.c?view=markup#l3436 - $content = preg_replace('/href=(?!#)/', 'myhref=', $soapPart->getContent()); - $request->setContent($content); - $request->setContentType($soapPart->getHeader('Content-Type')); - // store attachments - $attachments = $multipart->getParts(false); - foreach ($attachments as $cid => $attachment) { - $attachmentsReceived[$cid] = $attachment; - } - } - - if (count($attachmentsReceived) > 0) { - $request->setAttachments($attachmentsReceived); + $request->setContent($this->sanitizePhpExceptionOnHrefs($soapPart)); + $request->setContentType($soapPart->getHeader('Content-Type')); + if (count($attachments) > 0) { + $request->setAttachments($attachments); } return $request; @@ -65,28 +51,26 @@ class MimeFilter implements SoapRequestFilter, SoapResponseFilter { $attachmentsToSend = $response->getAttachments(); if (count($attachmentsToSend) > 0) { - $multipart = new MimeMultiPart(); + $multipart = new MimeMultiPart('Part_' . rand(10, 15) . '_' . uniqid() . '.' . uniqid()); $soapPart = new MimePart($response->getContent(), 'text/xml', 'utf-8', MimePart::ENCODING_EIGHT_BIT); $soapVersion = $response->getVersion(); - // change content type headers for MTOM with SOAP 1.1 - if ($soapVersion == SOAP_1_1 && $attachmentType & Helper::ATTACHMENTS_TYPE_MTOM) { + + if ($soapVersion === SOAP_1_1 && $attachmentType === Helper::ATTACHMENTS_TYPE_MTOM) { $multipart->setHeader('Content-Type', 'type', 'application/xop+xml'); $multipart->setHeader('Content-Type', 'start-info', 'text/xml'); $soapPart->setHeader('Content-Type', 'application/xop+xml'); $soapPart->setHeader('Content-Type', 'type', 'text/xml'); - } - // change content type headers for SOAP 1.2 - elseif ($soapVersion == SOAP_1_2) { + } elseif ($soapVersion === SOAP_1_2) { $multipart->setHeader('Content-Type', 'type', 'application/soap+xml'); $soapPart->setHeader('Content-Type', 'application/soap+xml'); } + $multipart->addPart($soapPart, true); foreach ($attachmentsToSend as $cid => $attachment) { $multipart->addPart($attachment, false); } $response->setContent($multipart->getMimeMessage()); - // TODO $headers = $multipart->getHeadersForHttp(); list(, $contentType) = explode(': ', $headers[0]); @@ -95,4 +79,11 @@ class MimeFilter implements SoapRequestFilter, SoapResponseFilter return $response; } + + private function sanitizePhpExceptionOnHrefs(Part $soapPart) + { + // convert href -> myhref for external references as PHP throws exception in this case + // http://svn.php.net/viewvc/php/php-src/branches/PHP_5_4/ext/soap/php_encoding.c?view=markup#l3436 + return preg_replace('/href=(?!#)/', 'myhref=', $soapPart->getContent()); + } } diff --git a/src/BeSimple/SoapServer/SoapOptions/SoapServerOptions.php b/src/BeSimple/SoapServer/SoapOptions/SoapServerOptions.php index 04b3457..08b39c1 100644 --- a/src/BeSimple/SoapServer/SoapOptions/SoapServerOptions.php +++ b/src/BeSimple/SoapServer/SoapOptions/SoapServerOptions.php @@ -2,6 +2,8 @@ namespace BeSimple\SoapServer\SoapOptions; +use Exception; + class SoapServerOptions { const SOAP_SERVER_PERSISTENCE_NONE = 0; @@ -43,6 +45,22 @@ class SoapServerOptions $this->persistence = $persistence; } + public function getHandler() + { + if ($this->hasHandlerObject()) { + + return $this->getHandlerObject(); + + } else if ($this->hasHandlerClass()) { + + return $this->getHandlerClass(); + + } else { + + throw new Exception('No HandlerClass or HandlerObject set'); + } + } + public function hasHandlerClass() { return $this->handlerClass !== null; diff --git a/src/BeSimple/SoapServer/SoapResponse.php b/src/BeSimple/SoapServer/SoapResponse.php index 51224ea..ed6e677 100644 --- a/src/BeSimple/SoapServer/SoapResponse.php +++ b/src/BeSimple/SoapServer/SoapResponse.php @@ -8,9 +8,6 @@ class SoapResponse extends CommonSoapResponse { public function getResponseContent() { - // set Content-Type header - header('Content-Type: ' . $this->getContentType()); - return $this->getContent(); } } diff --git a/src/BeSimple/SoapServer/SoapResponseFactory.php b/src/BeSimple/SoapServer/SoapResponseFactory.php index 99d4c2c..2e0e0cd 100644 --- a/src/BeSimple/SoapServer/SoapResponseFactory.php +++ b/src/BeSimple/SoapServer/SoapResponseFactory.php @@ -12,6 +12,8 @@ namespace BeSimple\SoapServer; +use BeSimple\SoapBundle\Soap\SoapAttachment; +use BeSimple\SoapCommon\Mime\Part; use BeSimple\SoapCommon\SoapMessage; class SoapResponseFactory @@ -23,7 +25,7 @@ class SoapResponseFactory * @param string $location Location * @param string $action SOAP action * @param string $version SOAP version - * @param array $attachments SOAP attachments + * @param SoapAttachment[] $attachments SOAP attachments * * @return SoapResponse */ @@ -37,6 +39,33 @@ class SoapResponseFactory $contentType = SoapMessage::getContentTypeForVersion($version); $response->setContentType($contentType); + if (count($attachments) > 0) { + $response->setAttachments( + self::createAttachmentParts($attachments) + ); + } + return $response; } + + /** + * @param SoapAttachment[] $attachments SOAP attachments + * @return Part[] + */ + private function createAttachmentParts(array $attachments = []) + { + $parts = []; + foreach ($attachments as $attachment) { + $part = new Part( + $attachment->getContent(), + 'application/pdf', + 'utf-8', + Part::ENCODING_BINARY, + $attachment->getId() + ); + $parts[] = $part; + } + + return $parts; + } } diff --git a/src/BeSimple/SoapServer/SoapServer.php b/src/BeSimple/SoapServer/SoapServer.php index 5a3f8ac..452581c 100644 --- a/src/BeSimple/SoapServer/SoapServer.php +++ b/src/BeSimple/SoapServer/SoapServer.php @@ -12,10 +12,13 @@ namespace BeSimple\SoapServer; +use BeSimple\SoapBundle\Soap\SoapAttachment; +use BeSimple\SoapCommon\AttachmentsHandler; use BeSimple\SoapCommon\SoapKernel; use BeSimple\SoapCommon\SoapOptions\SoapOptions; use BeSimple\SoapCommon\SoapRequest; use BeSimple\SoapCommon\SoapRequestFactory; +use BeSimple\SoapCommon\Storage\RequestHandlerAttachmentsStorage; use BeSimple\SoapServer\SoapOptions\SoapServerOptions; use BeSimple\SoapCommon\Converter\MtomTypeConverter; use BeSimple\SoapCommon\Converter\SwaTypeConverter; @@ -44,9 +47,6 @@ class SoapServer extends \SoapServer */ public function __construct(SoapServerOptions $soapServerOptions, SoapOptions $soapOptions) { - if ($soapOptions->hasAttachments()) { - $soapOptions = $this->configureTypeConverters($soapOptions); - } $this->soapVersion = $soapOptions->getSoapVersion(); $this->soapServerOptions = $soapServerOptions; $this->soapOptions = $soapOptions; @@ -60,6 +60,7 @@ class SoapServer extends \SoapServer /** * Custom handle method to be able to modify the SOAP messages. * + * @deprecated Please, use createRequest + handleRequest methods * @param string $requestUrl * @param string $soapAction * @param string $requestContent = null @@ -67,15 +68,9 @@ class SoapServer extends \SoapServer */ public function handle($requestUrl, $soapAction, $requestContent = null) { - try { - - return $this->getSoapResponse($requestUrl, $soapAction, $requestContent)->getResponseContent(); - - } catch (\SoapFault $fault) { - $this->fault($fault->faultcode, $fault->faultstring); - - return self::SOAP_SERVER_REQUEST_FAILED; - } + return $this->handleRequest( + $this->createRequest($requestUrl, $soapAction, $requestContent) + ); } /** @@ -83,20 +78,42 @@ class SoapServer extends \SoapServer * * @param string $requestUrl * @param string $soapAction + * @param string $requestContentType * @param string $requestContent = null - * @return SoapResponse + * @return SoapRequest */ - public function getSoapResponse($requestUrl, $soapAction, $requestContent = null) + public function createRequest($requestUrl, $soapAction, $requestContentType, $requestContent = null) { $soapRequest = SoapRequestFactory::create( $requestUrl, $soapAction, $this->soapVersion, + $requestContentType, $requestContent ); - $soapResponse = $this->handleSoapRequest($soapRequest); + $soapKernel = new SoapKernel(); + if ($this->soapOptions->hasAttachments()) { + $soapRequest = $soapKernel->filterRequest( + $soapRequest, + $this->getAttachmentFilters(), + $this->soapOptions->getAttachmentType() + ); + } - return $soapResponse; + return $soapRequest; + } + + public function handleRequest(SoapRequest $soapRequest) + { + try { + + return $this->handleSoapRequest($soapRequest); + + } catch (\SoapFault $fault) { + $this->fault($fault->faultcode, $fault->faultstring); + + return self::SOAP_SERVER_REQUEST_FAILED; + } } /** @@ -110,33 +127,87 @@ class SoapServer extends \SoapServer */ private function handleSoapRequest(SoapRequest $soapRequest) { - $soapKernel = new SoapKernel(); + /** @var AttachmentsHandler $handler */ + $handler = $this->soapServerOptions->getHandler(); + if ($this->soapOptions->hasAttachments()) { - $soapRequest = $soapKernel->filterRequest($soapRequest, $this->getFilters(), $this->soapOptions->getAttachmentType()); + $this->injectAttachmentStorage($handler, $soapRequest, $this->soapOptions->getAttachmentType()); } ob_start(); parent::handle($soapRequest->getContent()); - $response = ob_get_clean(); + $nativeSoapServerResponse = ob_get_clean(); + + $attachments = []; + if ($this->soapOptions->hasAttachments()) { + $attachments = $handler->getAttachmentsFromStorage(); + } // Remove headers added by SoapServer::handle() method header_remove('Content-Length'); header_remove('Content-Type'); - $soapResponse = SoapResponseFactory::create( - $response, + return $this->createResponse( $soapRequest->getLocation(), $soapRequest->getAction(), - $soapRequest->getVersion() + $soapRequest->getVersion(), + $nativeSoapServerResponse, + $attachments ); + } + /** + * @param string $requestLocation + * @param string $soapAction + * @param string $soapVersion + * @param string|null $responseContent + * @param SoapAttachment[] $attachments + * @return SoapResponse + */ + private function createResponse($requestLocation, $soapAction, $soapVersion, $responseContent = null, $attachments = []) + { + $soapResponse = SoapResponseFactory::create( + $responseContent, + $requestLocation, + $soapAction, + $soapVersion, + $attachments + ); + $soapKernel = new SoapKernel(); if ($this->soapOptions->hasAttachments()) { - $soapResponse = $soapKernel->filterResponse($soapResponse, $this->getFilters(), $this->soapOptions->getAttachmentType()); + $soapResponse = $soapKernel->filterResponse( + $soapResponse, + $this->getAttachmentFilters(), + $this->soapOptions->getAttachmentType() + ); } return $soapResponse; } + private function injectAttachmentStorage(AttachmentsHandler $handler, SoapRequest $soapRequest, $attachmentType) + { + $attachments = []; + if ($soapRequest->hasAttachments()) { + foreach ($soapRequest->getAttachments() as $attachment) { + $attachments[] = new SoapAttachment( + $attachment->getHeader('Content-Disposition', 'filename'), + $attachmentType, + $attachment->getContent() + ); + } + } + $handler->addAttachmentStorage(new RequestHandlerAttachmentsStorage($attachments)); + } + + /** + * Legacy code: TypeConverters should be resolved in SoapServer::__construct() + * To be removed if all tests pass + * + * @deprecated + * @param SoapOptions $soapOptions + * @return SoapOptions + */ private function configureTypeConverters(SoapOptions $soapOptions) { if ($soapOptions->getAttachmentType() !== SoapOptions::SOAP_ATTACHMENTS_TYPE_BASE64) { @@ -152,7 +223,7 @@ class SoapServer extends \SoapServer return $soapOptions; } - private function getFilters() + private function getAttachmentFilters() { $filters = []; if ($this->soapOptions->getAttachmentType() !== SoapOptions::SOAP_ATTACHMENTS_TYPE_BASE64) { diff --git a/src/BeSimple/SoapServer/XmlMimeFilter.php b/src/BeSimple/SoapServer/XmlMimeFilter.php index f8611b1..dd4ccd1 100644 --- a/src/BeSimple/SoapServer/XmlMimeFilter.php +++ b/src/BeSimple/SoapServer/XmlMimeFilter.php @@ -31,7 +31,7 @@ class XmlMimeFilter implements SoapResponseFilter { } - public function filterResponse(SoapResponse $response) + public function filterResponse(SoapResponse $response, $attachmentType) { $dom = $response->getContentDocument();