clue/reactphp-soap

SoapFault caused by data uri transformation

Closed this issue · 8 comments

Here is my code that reproduces the problem:

$loop = \React\EventLoop\Factory::create();
$browser = new \React\Http\Browser($loop);
$wsdl = 'https://katastr.cuzk.cz/dokumentace/ws28/wsdp/ciselnik_v28.wsdl';

$browser->get($wsdl)->then(
	function (\Psr\Http\Message\ResponseInterface $response) use ($browser): void {
		$client = new \Clue\React\Soap\Client($browser, (string) $response->getBody(), ['exceptions' => true]);

		$client->soapCall('SeznamObci', [['kde' => ['nazevObce' => ['obsahuje' => 'Praha']]]])
			->then(
				function ($result): void {
					var_dump($result);
				},
				function (\Throwable $error): void {
					echo 'ERROR 1' . PHP_EOL;
					var_dump(get_class($error));
					var_dump($error->getMessage());
				}
			);
	}
)->then(
	null,
	function (\Throwable $error): void {
		echo 'ERROR 2' . PHP_EOL;
		var_dump(get_class($error));
		var_dump($error->getMessage());
	}
);

$loop->run();

It ends with this error:

ERROR 2
string(9) "SoapFault"
string(83) "SOAP-ERROR: Parsing Schema: can't import schema from 'data://text/ciselnik_v28.xsd'"

From what I found out the problem is somehow caused by using a data uri with the wsdl file's content's instead of the original uri.

If I hack \Clue\React\Soap\Client::__construct() by replacing the linked line with this:

$wsdl = 'https://katastr.cuzk.cz/dokumentace/ws28/wsdp/ciselnik_v28.wsdl';

then I get the expected error (I didn't provide authentication header):

ERROR 1
string(9) "SoapFault"
string(66) "Error on verifying message against security policy Error code:1000"

What is the purpose of transforming the original wsdl file into a data uri? Any idea why it fails in this case?

clue commented

@enumag Thanks for bringing this up, this is an interesting one!

It looks like problem boils down to the fact that this WSDL file includes external files:

…
    <wsdl:types>
        <xsd:schema targetNamespace="http://katastr.cuzk.cz/ciselnik/types/v2.8" elementFormDefault="qualified">
            <xsd:include schemaLocation="ciselnik_v28.xsd"/>
        </xsd:schema>
    </wsdl:types>
…

The Client provided by this class requires the WSDL file as a string because it means we can start using it right away instead of having to (asynchronously) load anything into memory. Internally, it uses a data URI to pass it to the SoapClient used internally solely because it requires a URL.

In order to be able to use the referenced WSDL, one would have to download ever referenced files and pass this in some combined data structure into the Client. One way to make it work would be using an XML parser to replace your external references with the inline contents of the references files.

What do you think about this?

Yeah, figured out the same in the meantime. I'm guessing that you pass data URI instead of normal URI to SoapClient because with the normal URI it would make a synchronous request, right? With data URI it doesn't need to make any request and therefore becomes non-blocking?

I'll try to pre-process the XML and replace the <xsd:include> tags with the content of the linked file.

clue commented

Yeah, figured out the same in the meantime. I'm guessing that you pass data URI instead of normal URI to SoapClient because with the normal URI it would make a synchronous request, right? With data URI it doesn't need to make any request and therefore becomes non-blocking?

Correct 👍

I'll try to pre-process the XML and replace the <xsd:include> tags with the content of the linked file.

Please share your results here, I'm sure this is something more people would be interested in! 👍 Perhaps we can also integrate this here in a future version 👍

Unfortunately I was unable to solve it. Really not sure how to embed the second xml into the primary one correctly. It's not as simple as just dumping all the elements in place of xsd:include - tried that and got into a series of weird errors (which were also OS localized so there is no point in posting them because they were not in english). I had to separate SOAP communication into a different process to avoid blocking.

clue commented

Unfortunately I was unable to solve it. Really not sure how to embed the second xml into the primary one correctly. It's not as simple as just dumping all the elements in place of xsd:include - tried that and got into a series of weird errors (which were also OS localized so there is no point in posting them because they were not in english).

I think a WSDL validator would be a good starting point in this case. WSDL files rarely change, so doing this manually following https://www.w3.org/TR/wsdl20-primer/#more-types-schema-import might also be an option 👍

I had to separate SOAP communication into a different process to avoid blocking.

Yeah, definitely an option if using an async implementation isn't possible: https://github.com/clue/reactphp-pq 👍

I think a WSDL validator would be a good starting point in this case. WSDL files rarely change, so doing this manually following w3.org/TR/wsdl20-primer/#more-types-schema-import might also be an option +1

Unfortunately they don't have anything about <xsd:include> there. I'll stick to separate process for now.

clue commented

Alright, I'll assume this is resolved and will close this for now, please feel free to report back otherwise 👍

It's not resolved. The fact is that this library is incompatible with certain WSDLs and there is no known fix. The library simply can't be used. I'm in fact using PHP's SoapClient directly in my separate process. I'm not asking you to go and find a solution, I just think the issue should stay open and perhaps a known issue about this should be added to readme as a warning.