diff --git a/src/Loader/FlatteningLoader.php b/src/Loader/FlatteningLoader.php index d47e239..dd3f9df 100644 --- a/src/Loader/FlatteningLoader.php +++ b/src/Loader/FlatteningLoader.php @@ -25,6 +25,7 @@ public function __construct( */ public function __invoke(string $location): string { + $location = self::normalizeLocation($location); $currentDoc = Document::fromXmlString( non_empty_string()->assert(($this->loader)($location)) ); @@ -32,4 +33,24 @@ public function __invoke(string $location): string return (new Flattener())($location, $currentDoc, $context); } + + /** + * Ensures the base location used for resolving imports is absolute. + * Why: league/uri >= 7.6 throws when resolving a relative reference against a non-absolute base. + * + * @throws UnloadableWsdlException + */ + private static function normalizeLocation(string $location): string + { + if (preg_match('#^[a-z][a-z0-9+.\-]*:#i', $location) === 1) { + return $location; + } + + $absolute = realpath($location); + if ($absolute === false) { + throw UnloadableWsdlException::fromLocation($location); + } + + return $absolute; + } } diff --git a/tests/Unit/Loader/FlatteningLoaderTest.php b/tests/Unit/Loader/FlatteningLoaderTest.php index ddcf093..2e64470 100644 --- a/tests/Unit/Loader/FlatteningLoaderTest.php +++ b/tests/Unit/Loader/FlatteningLoaderTest.php @@ -5,8 +5,11 @@ use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; +use Psl\Ref; +use Soap\Wsdl\Exception\UnloadableWsdlException; use Soap\Wsdl\Loader\FlatteningLoader; use Soap\Wsdl\Loader\StreamWrapperLoader; +use Soap\Wsdl\Loader\WsdlLoader; use VeeWee\Xml\Dom\Document; use function VeeWee\Xml\Dom\Configurator\comparable; @@ -28,6 +31,70 @@ public function test_it_can_load_flattened_imports(string $wsdl, Document $expec static::assertSame($expected->toXmlString(), $flattened->toXmlString()); } + public function test_it_resolves_imports_when_given_a_relative_path(): void + { + $cwd = getcwd(); + chdir(FIXTURE_DIR.'/flattening'); + try { + $result = ($this->loader)('single-xsd.wsdl'); + } finally { + chdir($cwd); + } + + $flattened = Document::fromXmlString($result, comparable()); + $expected = Document::fromXmlFile(FIXTURE_DIR.'/flattening/result/single-xsd-result.wsdl', comparable()); + + static::assertSame($expected->toXmlString(), $flattened->toXmlString()); + } + + public function test_it_leaves_uri_scheme_locations_untouched(): void + { + $result = ($this->loader)('file://'.FIXTURE_DIR.'/flattening/single-xsd.wsdl'); + $flattened = Document::fromXmlString($result, comparable()); + $expected = Document::fromXmlFile(FIXTURE_DIR.'/flattening/result/single-xsd-result.wsdl', comparable()); + + static::assertSame($expected->toXmlString(), $flattened->toXmlString()); + } + + public function test_it_throws_when_given_a_non_existing_relative_path(): void + { + $this->expectException(UnloadableWsdlException::class); + ($this->loader)('does-not-exist.wsdl'); + } + + #[DataProvider('provideSchemeLocations')] + public function test_it_forwards_uri_scheme_locations_unchanged(string $location): void + { + /** @var Ref $captured */ + $captured = new Ref(null); + $capturingLoader = new class($captured) implements WsdlLoader { + public function __construct(private Ref $captured) + { + } + public function __invoke(string $location): string + { + $this->captured->value = $location; + return ''; + } + }; + + (new FlatteningLoader($capturingLoader))($location); + + static::assertSame($location, $captured->value); + } + + public static function provideSchemeLocations(): iterable + { + yield 'http' => ['http://example.com/service.wsdl']; + yield 'https' => ['https://example.com/service.wsdl']; + yield 'file' => ['file:///tmp/service.wsdl']; + yield 'ftp' => ['ftp://example.com/service.wsdl']; + yield 'scheme-with-plus' => ['svn+ssh://example.com/service.wsdl']; + yield 'scheme-with-dot' => ['x.y://example.com/service.wsdl']; + yield 'scheme-with-dash' => ['x-y://example.com/service.wsdl']; + yield 'mixed-case-scheme' => ['HTTP://example.com/service.wsdl']; + } + public static function provideTestCases() { //