diff --git a/src/BeSimple/SoapCommon/WsdlHandler.php b/src/BeSimple/SoapCommon/WsdlHandler.php new file mode 100644 index 0000000..cec21f5 --- /dev/null +++ b/src/BeSimple/SoapCommon/WsdlHandler.php @@ -0,0 +1,206 @@ + + * (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; + +/** + * This class loads the given WSDL file and allows to check MIME binding + * information. + * + * @author Andreas Schamberger + */ +class WsdlHandler +{ + /** + * Binding type 'input' . + */ + const BINDING_OPERATION_INPUT = 'input'; + + /** + * Binding type 'output' . + */ + const BINDING_OPERATION_OUTPUT = 'output'; + + /** + * WSDL 1.1 namespace. + */ + const NS_WSDL = 'http://schemas.xmlsoap.org/wsdl/'; + + /** + * WSDL MIME namespace. + */ + const NS_WSDL_MIME = 'http://schemas.xmlsoap.org/wsdl/mime/'; + + /** + * WSDL SOAP 1.1 namespace. + */ + const NS_WSDL_SOAP_1_1 = 'http://schemas.xmlsoap.org/wsdl/soap/'; + + /** + * WSDL SOAP 1.2 namespace. + */ + const NS_WSDL_SOAP_1_2 = 'http://schemas.xmlsoap.org/wsdl/soap12/'; + + /** + * WSDL file name. + * + * @var string + */ + private $wsdlFile; + + /** + * DOMDocument WSDL file. + * + * @var \DOMDocument + */ + private $domDocument; + + /** + * DOMXPath WSDL file. + * + * @var DOMXPath + */ + private $domXpath; + + /** + * Array of mime type information. + * + * @var array(string=>array(string=>array(string=>array(string)))) + */ + private $mimeTypes = array(); + + /** + * WSDL namespace of current WSDL file. + * + * @var string + */ + private $wsdlSoapNamespace; + + /** + * Constructor. + * + * @param string $wsdlFile WSDL file name + * @param string $soapVersion SOAP version constant + */ + public function __construct($wsdlFile, $soapVersion) + { + $this->wsdlFile = $wsdlFile; + if ($soapVersion == SOAP_1_1) { + $this->wsdlSoapNamespace = self::NS_WSDL_SOAP_1_1; + } else { + $this->wsdlSoapNamespace = self::NS_WSDL_SOAP_1_2; + } + $this->domDocument = new \DOMDocument('1.0', 'utf-8'); + $this->domDocument->load($this->wsdlFile); + $this->domXpath = new \DOMXPath($this->domDocument); + $this->domXpath->registerNamespace('wsdl', self::NS_WSDL); + $this->domXpath->registerNamespace('mime', self::NS_WSDL_MIME); + $this->domXpath->registerNamespace('soap', $this->wsdlSoapNamespace); + } + + /** + * Gets the mime type information from the WSDL file. + * + * @param string $soapAction Soap action to analyse + * @return array(string=>array(string=>array(string))) + */ + private function getMimeTypesForSoapAction($soapAction) + { + $query = '/wsdl:definitions/wsdl:binding/wsdl:operation/soap:operation[@soapAction="'.$soapAction.'"]/..'; + $nodes = $this->domXpath->query($query); + $mimeTypes = array(); + if (($wsdlOperation = $nodes->item(0)) !== null) { + //$wsdlOperationName = $wsdlOperation->getAttribute('name'); + foreach ($wsdlOperation->childNodes as $soapOperationChild) { + // wsdl:input or wsdl:output + if ($soapOperationChild->localName == 'input' || $soapOperationChild->localName == 'output') { + $operationType = $soapOperationChild->localName; + // mime:multipartRelated/mime:part + $mimeParts = $soapOperationChild->getElementsByTagNameNS(self::NS_WSDL_MIME, 'part'); + if ($mimeParts->length > 0) { + foreach ($mimeParts as $mimePart) { + foreach ($mimePart->childNodes as $child) { + switch ($child->localName) { + case 'body': + $parts = $child->getAttribute('parts'); + $parts = ($parts == '') ? '[body]' : $parts; + $mimeTypes[$operationType][$parts] = array('text/xml'); + break; + case 'content': + $part = $child->getAttribute('part'); + $part = ($part == '') ? null : $part; + $type = $child->getAttribute('type'); + $type = ($type == '') ? '*/*' : $type; + if (!isset($mimeTypes[$operationType][$part])) { + $mimeTypes[$operationType][$part] = array(); + } + $mimeTypes[$operationType][$part][] = $type; + break; + case 'mimeXml': + // this does not conform to the spec + $part = $child->getAttribute('part'); + $part = ($part == '') ? null : $part; + $mimeTypes[$operationType][$part] = array('text/xml'); + break; + } + } + } + } else { + $child = $soapOperationChild->getElementsByTagNameNS($this->wsdlSoapNamespace, 'body')->item(0); + if (!is_null($child)) { + $parts = $child->getAttribute('parts'); + $parts = ($parts == '') ? '[body]' : $parts; + $mimeTypes[$operationType][$parts] = array('text/xml'); + } + } + } + } + } + return $mimeTypes; + } + + /** + * Checks the mime type of the part for the given operation. + * + * @param string $soapAction Soap action + * @param string $operationType Operation type + * @param string $part Part name + * @param string $currentMimeType Current mime type + * @return boolean + */ + public function isValidMimeTypeType($soapAction, $operationType, $part, $currentMimeType) + { + // load data from WSDL + if (!isset($this->mimeTypes[$soapAction])) { + $this->mimeTypes[$soapAction] = $this->getMimeTypesForSoapAction($soapAction); + } + // part is valid as we do not have an explicit entry for current part + if (!isset($this->mimeTypes[$soapAction][$operationType][$part])) { + return true; + } + $mimeTypes = $this->mimeTypes[$soapAction][$operationType][$part]; + // wildcard or exact match + if (in_array('*/*', $mimeTypes) || in_array($currentMimeType, $mimeTypes)) { + return true; + // type/* match + } else { + list($ctype, $csubtype) = explode('/', $currentMimeType); + foreach ($mimeTypes as $mimeType) { + list($type, $subtype) = explode('/', $mimeType); + if ($subtype == '*' && $type == $ctype) { + return true; + } + } + } + return false; + } +} \ No newline at end of file diff --git a/tests/BeSimple/Tests/SoapCommon/Fixtures/WsdlMimeContent.wsdl b/tests/BeSimple/Tests/SoapCommon/Fixtures/WsdlMimeContent.wsdl new file mode 100644 index 0000000..30984b3 --- /dev/null +++ b/tests/BeSimple/Tests/SoapCommon/Fixtures/WsdlMimeContent.wsdl @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/BeSimple/Tests/SoapCommon/Fixtures/WsdlMimeContent2.wsdl b/tests/BeSimple/Tests/SoapCommon/Fixtures/WsdlMimeContent2.wsdl new file mode 100644 index 0000000..a6168da --- /dev/null +++ b/tests/BeSimple/Tests/SoapCommon/Fixtures/WsdlMimeContent2.wsdl @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/BeSimple/Tests/SoapCommon/Fixtures/WsdlMimeContent3.wsdl b/tests/BeSimple/Tests/SoapCommon/Fixtures/WsdlMimeContent3.wsdl new file mode 100644 index 0000000..c7e1d82 --- /dev/null +++ b/tests/BeSimple/Tests/SoapCommon/Fixtures/WsdlMimeContent3.wsdl @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/BeSimple/Tests/SoapCommon/WsdlHandlerTest.php b/tests/BeSimple/Tests/SoapCommon/WsdlHandlerTest.php new file mode 100644 index 0000000..7c10c92 --- /dev/null +++ b/tests/BeSimple/Tests/SoapCommon/WsdlHandlerTest.php @@ -0,0 +1,48 @@ + + * (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\Tests\SoapCommon\Soap; + +use BeSimple\SoapCommon\WsdlHandler; + +class WsdlHandlerTest extends \PHPUnit_Framework_TestCase +{ + public function testIsValidMimeTypeType() + { + $filename = __DIR__.DIRECTORY_SEPARATOR.'Fixtures/WsdlMimeContent.wsdl'; + $wh = new WsdlHandler($filename, SOAP_1_1); + + $this->assertTrue($wh->isValidMimeTypeType('http://example.com/soapaction', WsdlHandler::BINDING_OPERATION_INPUT, 'body', 'text/xml')); + $this->assertFalse($wh->isValidMimeTypeType('http://example.com/soapaction', WsdlHandler::BINDING_OPERATION_INPUT, 'body', 'image/gif')); + $this->assertTrue($wh->isValidMimeTypeType('http://example.com/soapaction', WsdlHandler::BINDING_OPERATION_INPUT, 'ClaimPhoto', 'image/jpeg')); + $this->assertFalse($wh->isValidMimeTypeType('http://example.com/soapaction', WsdlHandler::BINDING_OPERATION_INPUT, 'ClaimPhoto', 'image/gif')); + $this->assertFalse($wh->isValidMimeTypeType('http://example.com/soapaction', WsdlHandler::BINDING_OPERATION_INPUT, 'ClaimPhoto', 'text/xml')); + + $filename = __DIR__.DIRECTORY_SEPARATOR.'Fixtures/WsdlMimeContent2.wsdl'; + $wh = new WsdlHandler($filename, SOAP_1_1); + + $this->assertTrue($wh->isValidMimeTypeType('http://example.com/soapaction', WsdlHandler::BINDING_OPERATION_INPUT, 'body', 'text/xml')); + $this->assertFalse($wh->isValidMimeTypeType('http://example.com/soapaction', WsdlHandler::BINDING_OPERATION_INPUT, 'body', 'image/gif')); + $this->assertTrue($wh->isValidMimeTypeType('http://example.com/soapaction', WsdlHandler::BINDING_OPERATION_INPUT, 'ClaimPhoto', 'image/jpeg')); + $this->assertTrue($wh->isValidMimeTypeType('http://example.com/soapaction', WsdlHandler::BINDING_OPERATION_INPUT, 'ClaimPhoto', 'image/gif')); + $this->assertFalse($wh->isValidMimeTypeType('http://example.com/soapaction', WsdlHandler::BINDING_OPERATION_INPUT, 'ClaimPhoto', 'text/xml')); + + $filename = __DIR__.DIRECTORY_SEPARATOR.'Fixtures/WsdlMimeContent3.wsdl'; + $wh = new WsdlHandler($filename, SOAP_1_1); + + $this->assertTrue($wh->isValidMimeTypeType('http://example.com/soapaction', WsdlHandler::BINDING_OPERATION_INPUT, 'body', 'text/xml')); + $this->assertFalse($wh->isValidMimeTypeType('http://example.com/soapaction', WsdlHandler::BINDING_OPERATION_INPUT, 'body', 'image/gif')); + $this->assertTrue($wh->isValidMimeTypeType('http://example.com/soapaction', WsdlHandler::BINDING_OPERATION_INPUT, 'ClaimPhoto', 'image/jpeg')); + $this->assertTrue($wh->isValidMimeTypeType('http://example.com/soapaction', WsdlHandler::BINDING_OPERATION_INPUT, 'ClaimPhoto', 'image/gif')); + $this->assertTrue($wh->isValidMimeTypeType('http://example.com/soapaction', WsdlHandler::BINDING_OPERATION_INPUT, 'ClaimPhoto', 'text/xml')); + } +} \ No newline at end of file