Remove whitespaces from SoapClient request in PHP - php

I'm creating a SOAP request via PHP native SoapClient class. When I dump the request using SoapClient::__getLastRequest I'll get:
<?xml version="1.0" encoding="UTF-8"?>\n
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="urn:microsoft-dynamics-schemas/page/itemb2cws"><SOAP-ENV:Body><ns1:ReadMultiple><ns1:filter/><ns1:setSize>100</ns1:setSize></ns1:ReadMultiple></SOAP-ENV:Body></SOAP-ENV:Envelope>
Is there any way, how to remove the line feed or any other whitespace between XML elements?
Our client is using some old version of ERP Microsoft Dynamics Nav and the server falls when gets whitespace in request.

Solved
Via https://stackoverflow.com/questions/14363147/php-soapclient-malformed-xml#:~:text=override%20the%20SoapClient::__doRequest
Just extend native PHP SoapClient and override the SoapClient::__doRequest() method.
Example:
<?php
declare(strict_types=1);
namespace App\Soap;
use SoapClient;
class Client extends SoapClient
{
public function __doRequest($request, $location, $action, $version, $one_way = 0)
{
$request = $this->filter($request);
parent::__doRequest($request, $location, $action, $version, $one_way);
}
private function filter(string $request): string
{
return preg_replace( '#\R+#', '', $request);
}
}
And you're done.

Related

Soap: How to deal with non XML 1.0 characters

I am having some problems with a SoapServer sending me some responses with some invalid XML 1.0 chars like U+001F throwing a SoapFault "looks like we got no XML document".
My current code is:
class SoapClientNG extends \SoapClient {
public function __doRequest($request, $location, $action, $version, $one_way = 0) {
$xml = parent::__doRequest($request, $location, $action, $version, $one_way);
$response = str_ireplace('', '', $xml);
return $response;
}
}
Is there a simple way to convert all non 1.0 chars to nothing or even a way to enable SoapClient to accepts xml 1.1?

SOAP - PHP is possible to change 'ns1' tag name?

I need send a XML with this format:
<soapenv:Body>
<loc:amount>
<loc:user>user_name</loc:user>
<loc:charge>
<description>description_text</description>
</loc:charge>
</loc:amount>
I have generate the XML body with soapVar, but I cant change "ns1" tag.
Source:
$var = '<loc:amount> <loc:user>user_name</loc:user> <loc:charge> <description>description_text</description> </loc:charge> </loc:amount>';
$params[0] = new SoapVar($var,XSD_ANYXML,'amount','http://mydomain.cm');
And the header result is this:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://mydomain.cm" xmlns:ns2="http://header_domain.cm">
I need this result:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:loc="http://mydomain.cm" xmlns:ns2="http://header_domain.cm">
Is possible to change 'ns1' tag to 'loc'??
Thanks
Namespace prefix names are insignificant; it is only through their binding to a namespace value that they derive meaning. No conformant XML processor will care about the specific namespace prefix names; you should not either.
I've such an issue with UPnP Devices, which use "u" as default namespace-prefix and I need to change it from ns1. As I also found no better solution, I'll post my solution here.
class UpnpSoapClient extends SoapClient {
public function __doRequest($request, $location, $action, $version, $oneWay = 0)
{
// Some fucking UPnP Devices need the fucking u as namespace-prefix ... WTF?
if (preg_match('/xmlns:ns1=/', $request)) {
$request = preg_replace('/xmlns:ns1=/', 'xmlns:u=', $request, 1);
$request = preg_replace('/ns1:/', 'u:', $request);
}
return parent::__doRequest($request, $location, $action, $version);
}
}

Sending Raw XML via PHP SoapClient request

I am trying to simply send RAW xml to a webservice via PHP and SoapClient. The problem is when I encode my XML it changes the order of elements in the XML that is converted to an associative array.
// Initialize the Soap Client:
$this->_transactionServicesClient = new SoapClient($soapWSDLUrl);
How or what would be the best way to send the following XML as a string to my SoapClient?
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://www.micros.com/pos/les/TransactionServices">
<SOAP-ENV:Body>
<ns1:PostTransaction>
<ns1:REQ>
<ns1:RequestHeader>
<ns1:InterfaceVersion>3.0.7</ns1:InterfaceVersion>
<ns1:ClientName>TRANS_SERVICES</ns1:ClientName>
</ns1:RequestHeader>
<ns1:CheckDetailEntries>
<ns1:MenuItem>
<ns1:ReferenceEntry>Pizza4</ns1:ReferenceEntry>
<ns1:Count>1</ns1:Count>
<ns1:Price>10.00</ns1:Price>
<ns1:ItemNumber>112001</ns1:ItemNumber>
<ns1:PriceLevel>1</ns1:PriceLevel>
<ns1:Seat xsi:nil="true"/>
</ns1:MenuItem>
</ns1:CheckDetailEntries>
<ns1:CheckHeaderRequest>
<ns1:CheckId>03:21:05.050505</ns1:CheckId>
<ns1:GuestCount>1</ns1:GuestCount>
<ns1:GuestInformation>
<ns1:ID>001</ns1:ID>
<ns1:FirstName>xxx</ns1:FirstName>
<ns1:LastName>xxx</ns1:LastName>
<ns1:Address1>xxx Rd</ns1:Address1>
<ns1:Address2>xx</ns1:Address2>
<ns1:Address3>xx</ns1:Address3>
<ns1:PhoneNum>xx</ns1:PhoneNum>
<ns1:UserText1>None</ns1:UserText1>
<ns1:UserText2>None</ns1:UserText2>
<ns1:UserText3>None</ns1:UserText3>
<ns1:GUID></ns1:GUID></ns1:GuestInformation>
</ns1:CheckHeaderRequest>
<ns1:OrderTypeNumber>1</ns1:OrderTypeNumber>
</ns1:REQ>
</ns1:PostTransaction>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Update/Resolution: Here is the code I used to extend the SOAP Client and send my raw Soap Envelope: My answer below
Update/Resolution: Here is the code I used to extend the SOAP Client and send my raw Soap Envelope
Here is how I extended SoapClient:
<?php
class MySoapClient extends SoapClient {
function __construct($wsdl, $options) {
parent::__construct($wsdl, $options);
$this->server = new SoapServer($wsdl, $options);
}
public function __doRequest($request, $location, $action, $version)
{
$result = parent::__doRequest($request, $location, $action, $version);
return $result;
}
function __myDoRequest($array,$op) {
$request = $array;
$location = 'http://xxxxx:xxxx/TransactionServices/TransactionServices6.asmx';
$action = 'http://www.micros.com/pos/les/TransactionServices/'.$op;
$version = '1';
$result =$this->__doRequest($request, $location, $action, $version);
return $result;
}
}
// To invoke my new custom method with my Soap Envelope already prepared.
$soapClient = new MySoapClient("http://xxxx:xxxx/TransactionServices/TransactionServices6.asmx?WSDL", array("trace" => 1));
$PostTransaction = $soapClient->__myDoRequest($orderRequest,$op);
?>
Also posted on pastie.org: http://pastie.org/3687935 before I turned this into the answer.
For testing purposes, you can subclass SoapClient and override the __doRequest method - it receives the generated XML and you can pre-process it.
But better find what's going wrong with the XML encoding. You can use SoapVar and SoapParam instances to specify the exact way given object has to be represented. Those saved my life when namespaces had to be given

PHP SOAP client that understands multi-part messages?

Is there such a beastie? The simple SOAP client that ships with PHP does not understand multi-part messages. Thanks in advance.
The native PHP SoapClient class does not support multipart messages (and is strongly limited in all WS-* matters) and I also I think that neither the PHP written libraries NuSOAP nor Zend_Soap can deal with this sort of SOAP messages.
I can think of two solutions:
extend the SoapClient class and overwrite the SoapClient::__doRequest() method to get hold of the actual response string which you can then parse at your whim.
class MySoapClient extends SoapClient
{
public function __doRequest($request, $location, $action, $version, $one_way = 0)
{
$response = parent::__doRequest($request, $location, $action, $version, $one_way);
// parse $response, extract the multipart messages and so on
}
}
This could be somewhat tricky though - but worth a try.
use a more sophisticated SOAP client library for PHP. The first and only one that comes into my mind is WSO2 WSF/PHP which features SOAP MTOM, WS-Addressing, WS-Security, WS-SecurityPolicy, WS-Secure Conversation and WS-ReliableMessaging at the cost of having to install a native PHP extension.
Even though this answer has been given a lot here already, I have put together a general solutions, that keeps in mind, that the XML can come without the wrapper.
class SoapClientExtended extends SoapClient
{
/**
* Sends SOAP request using a predefined XML
*
* Overwrites the default method SoapClient::__doRequest() to make it work
* with multipart responses.
*
* #param string $request The XML content to send
* #param string $location The URL to request.
* #param string $action The SOAP action. [optional] default=''
* #param int $version The SOAP version. [optional] default=1
* #param int $one_way [optional] ( If one_way is set to 1, this method
* returns nothing. Use this where a response is
* not expected. )
*
* #return string The XML SOAP response.
*/
public function __doRequest(
$request, $location, $action, $version, $one_way = 0
) {
$result = parent::__doRequest($request, $location, $action, $version, $one_way);
$headers = $this->__getLastResponseHeaders();
// Do we have a multipart request?
if (preg_match('#^Content-Type:.*multipart\/.*#mi', $headers) !== 0) {
// Make all line breaks even.
$result = str_replace("\r\n", "\n", $result);
// Split between headers and content.
list(, $content) = preg_split("#\n\n#", $result);
// Split again for multipart boundary.
list($result, ) = preg_split("#\n--#", $content);
}
return $result;
}
}
This only works if you initialize the SoapClientExtended with the option trace => true.
Using S. Gehrig second idea worked just fine here.
In most cases, you have just a single message packed into a MIME MultiPart message. In those cases a "SoapFault exception: [Client] looks like we got no XML document" exception is thrown. Here the following class should do just fine:
class MySoapClient extends SoapClient
{
public function __doRequest($request, $location, $action, $version, $one_way = 0)
{
$response = parent::__doRequest($request, $location, $action, $version, $one_way);
// strip away everything but the xml.
$response = preg_replace('#^.*(<\?xml.*>)[^>]*$#s', '$1', $response);
return $response;
}
}
Follow the advice of rafinskipg from the PHP documentation:
Support for MTOM addign this code to your project:
<?php
class MySoapClient extends SoapClient
{
public function __doRequest($request, $location, $action, $version, $one_way = 0)
{
$response = parent::__doRequest($request, $location, $action, $version, $one_way);
// parse $response, extract the multipart messages and so on
//this part removes stuff
$start=strpos($response,'<?xml');
$end=strrpos($response,'>');
$response_string=substr($response,$start,$end-$start+1);
return($response_string);
}
}
?>
Then you can do this
<?php
new MySoapClient($wsdl_url);
?>
Just to add more light to previous suggested steps. You must be getting response in following format
--uuid:eca72cdf-4e96-4ba9-xxxxxxxxxx+id=108
Content-ID: <http://tempuri.org/0>
Content-Transfer-Encoding: 8bit
Content-Type: application/xop+xml;charset=utf-8;type="text/xml"
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body> content goes here </s:Body></s:Envelope>
--uuid:c19585dd-6a5a-4c08-xxxxxxxxx+id=108--
Just use following code (I am not that great with regex so using string functions)
public function __doRequest($request, $location, $action, $version, $one_way = 0)
{
$response = parent::__doRequest($request, $location, $action, $version, $one_way);
// strip away everything but the xml.
$response = stristr(stristr($response, "<s:"), "</s:Envelope>", true) . "</s:Envelope>";
return $response;
}
A little bit more simple IMHO
class SoapClientUnwrappedXml extends SoapClient
{
const SOAP_ENVELOPE_REGEXP = '/^<soap:Envelope[^>]*>(.*)<\/soap:Envelope>/m';
/**
* Sends SOAP request using a predefined XML.
*
* Overwrites the default method SoapClient::__doRequest() to make it work
* with multipart responses or prefixed/suffixed by uuids.
*
* #return string The XML Valid SOAP response.
*/
public function __doRequest($request, $location, $action, $version, $one_way = 0): string
{
$result = parent::__doRequest($request, $location, $action, $version, $one_way);
$headers = $this->__getLastResponseHeaders();
if (preg_match('#^Content-Type:.*multipart\/.*#mi', $headers) !== 0) {
preg_match_all(self::SOAP_ENVELOPE_REGEXP, $result, $resultSanitized, PREG_SET_ORDER, 0);
$result = $resultSanitized[0][0] ?? $result;
}
return $result;
}
}
The code just worked for me with the option "trace => true" too.
Thanks for sharing!

How can i create a soap header like this?

Doing some SOAP calls to a 3rd party application. They provide this soap header as an example of what the application expects. How can I create a SOAP header like this in PHP?
<SOAP-ENV:Header>
<NS1:Security xsi:type="NS2:Security" xmlns:NS1="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:NS2="urn:dkWSValueObjects">
<NS2:UsernameToken xsi:type="NS2:TUsernameToken">
<Username xsi:type="xsd:string">XXXX</Username>
<Password xsi:type="xsd:string">XXX</Password>
</NS2:UsernameToken>
</NS1:Security>
</SOAP-ENV:Header>
I do what i think is a correct call and keep getting in return that no headers were sent.
Here is a sample from my code.
class SOAPStruct
{
function __construct($user, $pass)
{
$this->Username = $user;
$this->Password = $pass;
}
}
$client = new SoapClient("http://www.example.com/service");
$auth = new SOAPStruct("username", "password");
$header = new SoapHeader("http://example.com/service", "TUsernameToken", $auth);
$client->__setSoapHeaders(array($header));
$client->__soapCall("GetSubscriptionGroupTypes", array(), NULL, $header)
And this is the SOAP header i get back. (its more but i stripped info away that might be sensitive)
<SOAP-ENV:Header>
<ns2:TUsernameToken>
<Username>username</Username>
<Password>password</Password>
</ns2:TUsernameToken>
</SOAP-ENV:Header>
SOAP header handling in PHP is actually not very flexible and I'd go as far as saying that especially the use two namespaces within the header will make it impossible to inject the header simply by using a SoapHeader-construct of some type.
I think the best way to handle this one is to shape the XML request yourself by overriding SoapClient::__doRequest() in a custom class that extends SoapClient.
class My_SoapClient extends SoapClient
{
public function __doRequest($request, $location, $action, $version, $one_way = 0)
{
$xmlRequest = new DOMDocument('1.0');
$xmlRequest->loadXML($request);
/*
* Do your processing using DOM
* e.g. insert security header and so on
*/
$request = $xmlRequest->saveXML();
return parent::__doRequest($request, $location, $action, $version, $one_way);
}
}
Please see SoapClient::__doRequest for further information and some examples.

Categories