From 06ff1ab70771dd20c2a0940df3029a375ad0fe93 Mon Sep 17 00:00:00 2001 From: Andreas Schamberger Date: Sun, 2 Oct 2011 12:09:19 +0200 Subject: [PATCH] tests for WsdlDownloader and cs fixes --- src/BeSimple/SoapClient/WsdlDownloader.php | 193 ++++++++--------- .../SoapClient/Fixtures/type_include.xsd | 15 ++ .../xsdinclude/xsdinctest_absolute.xml | 9 + .../xsdinclude/xsdinctest_relative.xml | 9 + .../Tests/SoapClient/WsdlDownloaderTest.php | 205 ++++++++++++++++++ 5 files changed, 327 insertions(+), 104 deletions(-) create mode 100644 tests/BeSimple/Tests/SoapClient/Fixtures/type_include.xsd create mode 100644 tests/BeSimple/Tests/SoapClient/Fixtures/xsdinclude/xsdinctest_absolute.xml create mode 100644 tests/BeSimple/Tests/SoapClient/Fixtures/xsdinclude/xsdinctest_relative.xml create mode 100644 tests/BeSimple/Tests/SoapClient/WsdlDownloaderTest.php diff --git a/src/BeSimple/SoapClient/WsdlDownloader.php b/src/BeSimple/SoapClient/WsdlDownloader.php index b13fe6e..d80803b 100644 --- a/src/BeSimple/SoapClient/WsdlDownloader.php +++ b/src/BeSimple/SoapClient/WsdlDownloader.php @@ -8,7 +8,7 @@ * * This source file is subject to the MIT license that is bundled * with this source code in the file LICENSE. - * + * * @link https://github.com/BeSimple/BeSimpleSoapClient */ @@ -54,26 +54,28 @@ class WsdlDownloader /** * Constructor. + * + * @param array $options */ - public function __construct( $options ) + public function __construct(array $options = array()) { // get current WSDL caching config - $this->cacheEnabled = (bool)ini_get( 'soap.wsdl_cache_enabled' ); - if ( $this->cacheEnabled === true - && isset( $options['cache_wsdl'] ) - && $options['cache_wsdl'] === WSDL_CACHE_NONE - ) - { + $this->cacheEnabled = (bool)ini_get('soap.wsdl_cache_enabled'); + if ($this->cacheEnabled === true + && isset($options['cache_wsdl']) + && $options['cache_wsdl'] === WSDL_CACHE_NONE) { $this->cacheEnabled = false; } - $this->cacheDir = ini_get( 'soap.wsdl_cache_dir' ); - if ( !is_dir( $this->cacheDir ) ) - { + $this->cacheDir = ini_get('soap.wsdl_cache_dir'); + if (!is_dir($this->cacheDir)) { $this->cacheDir = sys_get_temp_dir(); } - $this->cacheDir = rtrim( $this->cacheDir, '/\\' ); - $this->cacheTtl = ini_get( 'soap.wsdl_cache_ttl' ); + $this->cacheDir = rtrim($this->cacheDir, '/\\'); + $this->cacheTtl = ini_get('soap.wsdl_cache_ttl'); $this->options = $options; + if (!isset($this->options['resolve_xsd_includes'])) { + $this->options['resolve_xsd_includes'] = true; + } } /** @@ -82,62 +84,44 @@ class WsdlDownloader * @param string $wsdl * @return string */ - public function download( $wsdl ) + public function download($wsdl) { // download and cache remote WSDL files or local ones where we want to // resolve remote XSD includes - $isRemoteFile = $this->isRemoteFile( $wsdl ); - if ( $isRemoteFile === true || $this->options['resolve_xsd_includes'] === true ) - { - $cacheFile = $this->cacheDir . DIRECTORY_SEPARATOR . 'wsdl_' . md5( $wsdl ) . '.cache'; - if ( $this->cacheEnabled === false - || !file_exists( $cacheFile ) - || ( filemtime( $cacheFile ) + $this->cacheTtl ) < time() - ) - { - if ( $isRemoteFile === true ) - { + $isRemoteFile = $this->isRemoteFile($wsdl); + if ($isRemoteFile === true || $this->options['resolve_xsd_includes'] === true) { + $cacheFile = $this->cacheDir . DIRECTORY_SEPARATOR . 'wsdl_' . md5($wsdl) . '.cache'; + if ($this->cacheEnabled === false + || !file_exists($cacheFile) + || (filemtime($cacheFile) + $this->cacheTtl) < time()) { + if ($isRemoteFile === true) { // new curl object for request - $curl = new Curl( $this->options ); + $curl = new Curl($this->options); // execute request - $responseSuccessfull = $curl->exec( $wsdl ); + $responseSuccessfull = $curl->exec($wsdl); // get content - if ( $responseSuccessfull === true ) - { + if ($responseSuccessfull === true) { $response = $curl->getResponseBody(); - if ( $this->options['resolve_xsd_includes'] === true ) - { - $this->resolveXsdIncludes( $response, $cacheFile, $wsdl ); - } - else - { - file_put_contents( $cacheFile, $response ); + if ($this->options['resolve_xsd_includes'] === true) { + $this->resolveXsdIncludes($response, $cacheFile, $wsdl); + } else { + file_put_contents($cacheFile, $response); } + } else { + throw new \ErrorException("SOAP-ERROR: Parsing WSDL: Couldn't load from '" . $wsdl ."'"); } - else - { - throw new \ErrorException( "SOAP-ERROR: Parsing WSDL: Couldn't load from '" . $wsdl ."'" ); - } - } - elseif ( file_exists( $wsdl ) ) - { - $response = file_get_contents( $wsdl ); - $this->resolveXsdIncludes( $response, $cacheFile ); - } - else - { - throw new \ErrorException( "SOAP-ERROR: Parsing WSDL: Couldn't load from '" . $wsdl ."'" ); + } elseif (file_exists($wsdl)) { + $response = file_get_contents($wsdl); + $this->resolveXsdIncludes($response, $cacheFile); + } else { + throw new \ErrorException("SOAP-ERROR: Parsing WSDL: Couldn't load from '" . $wsdl ."'"); } } return $cacheFile; - } - elseif ( file_exists( $wsdl ) ) - { - return realpath( $wsdl ); - } - else - { - throw new \ErrorException( "SOAP-ERROR: Parsing WSDL: Couldn't load from '" . $wsdl ."'" ); + } elseif (file_exists($wsdl)) { + return realpath($wsdl); + } else { + throw new \ErrorException("SOAP-ERROR: Parsing WSDL: Couldn't load from '" . $wsdl ."'"); } } @@ -147,14 +131,12 @@ class WsdlDownloader * @param string $file * @return boolean */ - private function isRemoteFile( $file ) + private function isRemoteFile($file) { $isRemoteFile = false; // @parse_url to suppress E_WARNING for invalid urls - if ( ( $url = @parse_url( $file ) ) !== false ) - { - if ( isset( $url['scheme'] ) && substr( $url['scheme'], 0, 4 ) == 'http' ) - { + if (($url = @parse_url($file)) !== false) { + if (isset($url['scheme']) && substr($url['scheme'], 0, 4) == 'http') { $isRemoteFile = true; } } @@ -169,33 +151,28 @@ class WsdlDownloader * @param unknown_type $parentIsRemote * @return string */ - private function resolveXsdIncludes( $xml, $cacheFile, $parentFile = null ) + private function resolveXsdIncludes($xml, $cacheFile, $parentFile = null) { $doc = new \DOMDocument(); - $doc->loadXML( $xml ); - $xpath = new \DOMXPath( $doc ); - $xpath->registerNamespace( Helper::PFX_XML_SCHEMA, Helper::NS_XML_SCHEMA ); + $doc->loadXML($xml); + $xpath = new \DOMXPath($doc); + $xpath->registerNamespace(Helper::PFX_XML_SCHEMA, Helper::NS_XML_SCHEMA); $query = './/' . Helper::PFX_XML_SCHEMA . ':include'; - $nodes = $xpath->query( $query ); - if ( $nodes->length > 0 ) - { - foreach ( $nodes as $node ) - { - $schemaLocation = $node->getAttribute( 'schemaLocation' ); - if ( $this->isRemoteFile( $schemaLocation ) ) - { - $schemaLocation = $this->download( $schemaLocation ); - $node->setAttribute( 'schemaLocation', $schemaLocation ); - } - elseif ( !is_null( $parentFile ) ) - { - $schemaLocation = $this->resolveRelativePathInUrl( $parentFile, $schemaLocation ); - $schemaLocation = $this->download( $schemaLocation ); - $node->setAttribute( 'schemaLocation', $schemaLocation ); + $nodes = $xpath->query($query); + if ($nodes->length > 0) { + foreach ($nodes as $node) { + $schemaLocation = $node->getAttribute('schemaLocation'); + if ($this->isRemoteFile($schemaLocation)) { + $schemaLocation = $this->download($schemaLocation); + $node->setAttribute('schemaLocation', $schemaLocation); + } elseif (!is_null($parentFile)) { + $schemaLocation = $this->resolveRelativePathInUrl($parentFile, $schemaLocation); + $schemaLocation = $this->download($schemaLocation); + $node->setAttribute('schemaLocation', $schemaLocation); } } } - $doc->save( $cacheFile ); + $doc->save($cacheFile); } /** @@ -205,36 +182,44 @@ class WsdlDownloader * @param string $relative * @return string */ - private function resolveRelativePathInUrl( $base, $relative ) + private function resolveRelativePathInUrl($base, $relative) { - $urlParts = parse_url( $base ); + $urlParts = parse_url($base); // combine base path with relative path - if ( strrpos( '/', $urlParts['path'] ) === ( strlen( $urlParts['path'] ) - 1 ) ) - { - $path = trim( $urlParts['path'] . $relative ); - } - else - { - $path = trim( dirname( $urlParts['path'] ) . '/' . $relative ); + if (isset($urlParts['path']) && strpos($relative, '/') === 0) { + // $relative is absolute path from domain (starts with /) + $path = $relative; + } elseif (isset($urlParts['path']) && strrpos($urlParts['path'], '/') === (strlen($urlParts['path']) )) { + // base path is directory + $path = $urlParts['path'] . $relative; + } elseif (isset($urlParts['path'])) { + // strip filename from base path + $path = substr($urlParts['path'], 0, strrpos($urlParts['path'], '/')) . '/' . $relative; + } else { + // no base path + $path = '/' . $relative; } // foo/./bar ==> foo/bar - $path = preg_replace( '~/\./~', '/', $path ); + $path = preg_replace('~/\./~', '/', $path); // remove double slashes - $path = preg_replace( '~/+~', '/', $path ); + $path = preg_replace('~/+~', '/', $path); // split path by '/' - $parts = explode( '/', $path ); + $parts = explode('/', $path); // resolve /../ - foreach ( $parts as $key => $part ) - { - if ( $part == ".." ) - { - if ( $key-1 >= 0 ) - { - unset( $parts[$key-1] ); + foreach ($parts as $key => $part) { + if ($part == "..") { + $keyToDelete = $key-1; + while ($keyToDelete > 0) { + if (isset($parts[$keyToDelete])) { + unset($parts[$keyToDelete]); + break; + } else { + $keyToDelete--; + } } - unset( $parts[$key] ); + unset($parts[$key]); } } - return $urlParts['scheme'] . '://' . $urlParts['host'] . implode( '/', $parts ); + return $urlParts['scheme'] . '://' . $urlParts['host'] . implode('/', $parts); } } \ No newline at end of file diff --git a/tests/BeSimple/Tests/SoapClient/Fixtures/type_include.xsd b/tests/BeSimple/Tests/SoapClient/Fixtures/type_include.xsd new file mode 100644 index 0000000..a41dd9a --- /dev/null +++ b/tests/BeSimple/Tests/SoapClient/Fixtures/type_include.xsd @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/BeSimple/Tests/SoapClient/Fixtures/xsdinclude/xsdinctest_absolute.xml b/tests/BeSimple/Tests/SoapClient/Fixtures/xsdinclude/xsdinctest_absolute.xml new file mode 100644 index 0000000..dc1b373 --- /dev/null +++ b/tests/BeSimple/Tests/SoapClient/Fixtures/xsdinclude/xsdinctest_absolute.xml @@ -0,0 +1,9 @@ + + + xsdinctest + + + + + + diff --git a/tests/BeSimple/Tests/SoapClient/Fixtures/xsdinclude/xsdinctest_relative.xml b/tests/BeSimple/Tests/SoapClient/Fixtures/xsdinclude/xsdinctest_relative.xml new file mode 100644 index 0000000..58cea74 --- /dev/null +++ b/tests/BeSimple/Tests/SoapClient/Fixtures/xsdinclude/xsdinctest_relative.xml @@ -0,0 +1,9 @@ + + + xsdinctest + + + + + + diff --git a/tests/BeSimple/Tests/SoapClient/WsdlDownloaderTest.php b/tests/BeSimple/Tests/SoapClient/WsdlDownloaderTest.php new file mode 100644 index 0000000..a07aa46 --- /dev/null +++ b/tests/BeSimple/Tests/SoapClient/WsdlDownloaderTest.php @@ -0,0 +1,205 @@ + + * (c) Francis Besset + * (c) Andreas Schamberger + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + * + * @link https://github.com/BeSimple/BeSimpleSoapClient + */ + +namespace BeSimple\SoapClient; + +use BeSimple\SoapClient\WsdlDownloader; + +class WsdlDownloaderTest extends \PHPUnit_Framework_TestCase +{ + protected $webserverProcessId; + + protected function startPhpWebserver() + { + if ('Windows' == substr(php_uname('s'), 0, 7 )) { + $powershellCommand = "\$app = start-process php.exe -ArgumentList '-S localhost:8000 -t ".__DIR__.DIRECTORY_SEPARATOR."Fixtures' -WindowStyle 'Hidden' -passthru; Echo \$app.Id;"; + $shellCommand = 'powershell -command "& {'.$powershellCommand.'}"'; + } else { + $shellCommand = "nohup php -S localhost:8000 -t ".__DIR__.DIRECTORY_SEPARATOR."Fixtures &"; + } + $output = array(); + exec($shellCommand, $output); + $this->webserverProcessId = $output[0]; // pid is in first element + } + + protected function stopPhpWebserver() + { + if (!is_null($this->webserverProcessId)) { + if ('Windows' == substr(php_uname('s'), 0, 7 )) { + exec('TASKKILL /F /PID ' . $this->webserverProcessId); + } else { + exec('kill ' . $this->webserverProcessId); + } + $this->webserverProcessId = null; + } + } + + public function testDownload() + { + $this->startPhpWebserver(); + + $options = array( + 'resolve_xsd_includes' => true, + ); + $wd = new WsdlDownloader($options); + + $cacheDir = ini_get('soap.wsdl_cache_dir'); + if (!is_dir($cacheDir)) { + $cacheDir = sys_get_temp_dir(); + $cacheDirForRegExp = preg_quote( $cacheDir ); + } + + $tests = array( + 'localWithAbsolutePath' => array( + 'source' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/xsdinclude/xsdinctest_absolute.xml', + 'assertRegExp' => '~.*'.$cacheDirForRegExp.'\\\wsdl_.*\.cache.*~', + ), + 'localWithRelativePath' => array( + 'source' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/xsdinclude/xsdinctest_relative.xml', + 'assertRegExp' => '~.*\.\./type_include\.xsd.*~', + ), + 'remoteWithAbsolutePath' => array( + 'source' => 'http://localhost:8000/xsdinclude/xsdinctest_absolute.xml', + 'assertRegExp' => '~.*'.$cacheDirForRegExp.'\\\wsdl_.*\.cache.*~', + ), + 'remoteWithAbsolutePath' => array( + 'source' => 'http://localhost:8000/xsdinclude/xsdinctest_relative.xml', + 'assertRegExp' => '~.*'.$cacheDirForRegExp.'\\\wsdl_.*\.cache.*~', + ), + ); + + foreach ($tests as $name => $values) { + $cacheFileName = $wd->download($values['source']); + $result = file_get_contents($cacheFileName); + $this->assertRegExp($values['assertRegExp'],$result,$name); + unlink($cacheFileName); + } + + $this->stopPhpWebserver(); + } + + public function testIsRemoteFile() + { + $wd = new WsdlDownloader(); + + $class = new \ReflectionClass($wd); + $method = $class->getMethod('isRemoteFile'); + $method->setAccessible(true); + + $this->assertEquals(true, $method->invoke($wd, 'http://www.php.net/')); + $this->assertEquals(true, $method->invoke($wd, 'http://localhost/')); + $this->assertEquals(true, $method->invoke($wd, 'http://mylocaldomain/')); + $this->assertEquals(true, $method->invoke($wd, 'http://www.php.net/dir/test.html')); + $this->assertEquals(true, $method->invoke($wd, 'http://localhost/dir/test.html')); + $this->assertEquals(true, $method->invoke($wd, 'http://mylocaldomain/dir/test.html')); + $this->assertEquals(true, $method->invoke($wd, 'https://www.php.net/')); + $this->assertEquals(true, $method->invoke($wd, 'https://localhost/')); + $this->assertEquals(true, $method->invoke($wd, 'https://mylocaldomain/')); + $this->assertEquals(true, $method->invoke($wd, 'https://www.php.net/dir/test.html')); + $this->assertEquals(true, $method->invoke($wd, 'https://localhost/dir/test.html')); + $this->assertEquals(true, $method->invoke($wd, 'https://mylocaldomain/dir/test.html')); + $this->assertEquals(false, $method->invoke($wd, 'c:/dir/test.html')); + $this->assertEquals(false, $method->invoke($wd, '/dir/test.html')); + $this->assertEquals(false, $method->invoke($wd, '../dir/test.html')); + } + + public function testResolveXsdIncludes() + { + $this->startPhpWebserver(); + + $options = array( + 'resolve_xsd_includes' => true, + ); + $wd = new WsdlDownloader($options); + + $class = new \ReflectionClass($wd); + $method = $class->getMethod('resolveXsdIncludes'); + $method->setAccessible(true); + + $cacheDir = ini_get('soap.wsdl_cache_dir'); + if (!is_dir($cacheDir)) { + $cacheDir = sys_get_temp_dir(); + $cacheDirForRegExp = preg_quote( $cacheDir ); + } + + $remoteUrlAbsolute = 'http://localhost:8000/xsdinclude/xsdinctest_absolute.xml'; + $remoteUrlRelative = 'http://localhost:8000/xsdinclude/xsdinctest_relative.xml'; + $tests = array( + 'localWithAbsolutePath' => array( + 'source' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/xsdinclude/xsdinctest_absolute.xml', + 'cacheFile' => $cacheDir.'/cache_local_absolute.xml', + 'remoteParentUrl' => null, + 'assertRegExp' => '~.*'.$cacheDirForRegExp.'\\\wsdl_.*\.cache.*~', + ), + 'localWithRelativePath' => array( + 'source' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/xsdinclude/xsdinctest_relative.xml', + 'cacheFile' => $cacheDir.'/cache_local_relative.xml', + 'remoteParentUrl' => null, + 'assertRegExp' => '~.*\.\./type_include\.xsd.*~', + ), + 'remoteWithAbsolutePath' => array( + 'source' => $remoteUrlAbsolute, + 'cacheFile' => $cacheDir.'/cache_remote_absolute.xml', + 'remoteParentUrl' => $remoteUrlAbsolute, + 'assertRegExp' => '~.*'.$cacheDirForRegExp.'\\\wsdl_.*\.cache.*~', + ), + 'remoteWithAbsolutePath' => array( + 'source' => $remoteUrlRelative, + 'cacheFile' => $cacheDir.'/cache_remote_relative.xml', + 'remoteParentUrl' => $remoteUrlRelative, + 'assertRegExp' => '~.*'.$cacheDirForRegExp.'\\\wsdl_.*\.cache.*~', + ), + ); + + foreach ($tests as $name => $values) { + $wsdl = file_get_contents( $values['source'] ); + $method->invoke($wd, $wsdl, $values['cacheFile'],$values['remoteParentUrl']); + $result = file_get_contents($values['cacheFile']); + $this->assertRegExp($values['assertRegExp'],$result,$name); + unlink($values['cacheFile']); + } + + $this->stopPhpWebserver(); + } + + public function testResolveRelativePathInUrl() + { + $wd = new WsdlDownloader(); + + $class = new \ReflectionClass($wd); + $method = $class->getMethod('resolveRelativePathInUrl'); + $method->setAccessible(true); + + $this->assertEquals('http://localhost/test', $method->invoke($wd, 'http://localhost/sub', '/test')); + $this->assertEquals('http://localhost/test', $method->invoke($wd, 'http://localhost/sub/', '/test')); + + $this->assertEquals('http://localhost/test', $method->invoke($wd, 'http://localhost', './test')); + $this->assertEquals('http://localhost/test', $method->invoke($wd, 'http://localhost/', './test')); + + $this->assertEquals('http://localhost/sub/test', $method->invoke($wd, 'http://localhost/sub/sub', './test')); + $this->assertEquals('http://localhost/sub/sub/test', $method->invoke($wd, 'http://localhost/sub/sub/', './test')); + + $this->assertEquals('http://localhost/test', $method->invoke($wd, 'http://localhost/sub/sub', '../test')); + $this->assertEquals('http://localhost/sub/test', $method->invoke($wd, 'http://localhost/sub/sub/', '../test')); + + $this->assertEquals('http://localhost/test', $method->invoke($wd, 'http://localhost/sub/sub/sub', '../../test')); + $this->assertEquals('http://localhost/sub/test', $method->invoke($wd, 'http://localhost/sub/sub/sub/', '../../test')); + + $this->assertEquals('http://localhost/test', $method->invoke($wd, 'http://localhost/sub/sub/sub/sub', '../../../test')); + $this->assertEquals('http://localhost/sub/test', $method->invoke($wd, 'http://localhost/sub/sub/sub/sub/', '../../../test')); + + $this->assertEquals('http://localhost/test', $method->invoke($wd, 'http://localhost/sub/sub/sub', '../../../test')); + $this->assertEquals('http://localhost/test', $method->invoke($wd, 'http://localhost/sub/sub/sub/', '../../../test')); + } +} \ No newline at end of file