6

I am testing this webservice by sending a SOAP request through SoapUi.

I currently have this PHP array:

array(7) {
  ["name"]=>
  string(9) "John Doe"
  ["date"]=>
  string(23) "2021-11-30 00:00:00.000"
  ["job"]=>
  string(31) "developer"
  ["where_from"]=>
  string(15) "france"
  ["address"]=>
  array(3) {
    ["country"]=>
    string(15) "france"
    ["city"]=>
    string(10) "paris"
    ["vat_number"]=>
    string(1) "123456"
  }
  ["items"]=>
  array(1) {
    [0]=>
    array(2) {
      ["cook"]=>
      string(7) "spoon"
      ["clean"]=>
      string(14) "vacuum"
    }
  }
}

I am trying to convert it to XML with:

function convertToXml($data, $name='root', &$doc=null, &$node=null){
    if ($doc==null){
        $doc = new DOMDocument('1.0','UTF-8');
        $doc->formatOutput = TRUE;
        $node = $doc;
    }

    if (is_array($data)){
        foreach($data as $var=>$val){
            if (is_numeric($var)){
                convertToXml($val, $name, $doc, $node);
            }else{
                if (!isset($child)){
                    $child = $doc->createElement($name);
                    $node->appendChild($child);
                }

                convertToXml($val, $var, $doc, $child);
            }
        }
    }else{
        $child = $doc->createElement($name);
        $node->appendChild($child);
        $textNode = $doc->createTextNode($data);
        $child->appendChild($textNode);
}

    if ($doc==$node) return $doc->saveXML();
}

However, I am getting this response in SOAPUI:

<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
   <SOAP-ENV:Body>
      <SOAP-ENV:Fault>
         <faultcode xsi:type="xsd:string">SOAP-ENV:Server</faultcode>
         <faultactor xsi:type="xsd:string"/>
         <faultstring xsi:type="xsd:string">unable to serialize result</faultstring>
         <detail xsi:type="xsd:string"/>
      </SOAP-ENV:Fault>
   </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

I have tried with a smaller array and it works, but with this one it is not working as expected.

Can anyone help?

------------------------ UPDATE--------------------------------

Response I expect to get:

<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="http://ex.pt/soap/WebServices">
   <SOAP-ENV:Body>
      <ns1:Person xmlns:ns1="https://ex.pt/webservices">
         
         <data xsi:type="tns:getPersonInfo">
            <name xsi:type="xsd:string">John</name>
            <surname xsi:type="xsd:string">Doe</surname>
            <job xsi:type="xsd:string">developer</job>
            <from xsi:type="xsd:string">france</from>
            <address xsi:type="tns:getAddress">
               <country xsi:type="xsd:string">france</country>
               <city xsi:type="xsd:string">paris</city>
               <post_code xsi:type="xsd:string">12345</post_code>
            </address>
            <items xsi:type="tns:getItems">
               <item xsi:type="xsd:string">
                  <name xsi:type="xsd:string">pillow</name>
                  <material xsi:type="xsd:string">cotton</material>
               </item>
               .... other items
            </items>
         </data>
      </ns1:Person>
   </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

How it is retrieving the response:

<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="http://ex.pt/soap/WebServices">
       <SOAP-ENV:Body>
          <ns1:Person xmlns:ns1="https://ex.pt/webservices">
             
             <data xsi:type="tns:getPersonInfo">
                <name xsi:type="xsd:string">John</name>
                <surname xsi:type="xsd:string">Doe</surname>
                <job xsi:type="xsd:string">developer</job>
                <from xsi:type="xsd:string">france</from>
                <address xsi:type="tns:getAddress">
                   <country xsi:type="xsd:string">france</country>
                   <city xsi:type="xsd:string">paris</city>
                   <post_code xsi:type="xsd:string">12345</post_code>
                </address>
                <items xsi:type="tns:getItems"/>
             </data>
          </ns1:Person>
       </SOAP-ENV:Body>
    </SOAP-ENV:Envelope>

xml schema for "items"

<part name="items" type="tns:getItems"/>


<xsd:complexType name="getItems">
  <xsd:complexContent>
     <xsd:restriction base="SOAP-ENC:Array">
        <xsd:attribute ref="SOAP-ENC:arrayType wsdl:arrayType="tns:ItemInfo[]"/>
     </xsd:restriction>
  </xsd:complexContent>
</xsd:complexType>

<xsd:complexType name="ItemInfo">
   <xsd:all>
     <xsd:element name="name" type="xsd:string"/>
     <xsd:element name="material" type="xsd:string"/>
  </xsd:all>
</xsd:complexType>
    
taylor.2317
  • 582
  • 1
  • 9
  • 23
  • You should var_export and not var_dump your array. – lukas.j Dec 05 '21 at 20:59
  • You're also open to [XXE Injection](https://documentation.help/InfoSec-cn/8d5649b4-8523-4cbf-b4bb-8424f871f7b2.htm) from what you're showing. – Jaquarh Dec 05 '21 at 21:04
  • How so @Jaquarh? –  Dec 05 '21 at 21:33
  • Everything is written in the article I linked, just use `libxml_disable_entity_loader(true);` to disable RCE in XML loading. – Jaquarh Dec 05 '21 at 21:36
  • Ok, thanks, got it. What about the real question here do you have any idea why is this happening? @Jaquarh –  Dec 05 '21 at 21:44
  • @Jaquarh Note that this vulnerability was mitigated in PHP 8.0, since as of that version the entity loader in libxml is [disabled by default](https://www.php.net/manual/en/migration80.other-changes.php#migration80.other-changes.extensions.libxml). – rickdenhaan Dec 05 '21 at 23:48
  • Yes, I'm tracking this however the OP doesn't mention PHP version, thus I advised to use it and linked the article as a further reading. @rickdenhaan – Jaquarh Dec 05 '21 at 23:50
  • So what does the resulting XML document look like? Does it look as you expected? You said another XML document was successful, how does it compare? How are you sending the XML to the SOAP server? What is the server attempting to do that causes this error? – miken32 Dec 09 '21 at 20:21
  • @ack31 I think that you forgot to include code of -> **this service** which you mention ) Btw, your xml is generating a bit incorrectly. If `items` are plural then structure should be next `spoonvacuum`, but instead xml generating function which was provided generate a list of items right inside `items` element -> `spoonvacuum`. Still, it would be better if you provide code which will explain how xml is parsed on service side. – Sergey Ligus Dec 09 '21 at 22:20
  • Maybe too obvious, but why not use `$xml = new SimpleXMLElement(''); array_walk_recursive($array, [$xml, 'addChild']); $myXMLasString = $xml->asXML();` – Danny Ebbers Dec 10 '21 at 16:10
  • 2
    @ack31 better if you add expected output of soap xml so some one can help you to solve – John Lobo Dec 11 '21 at 07:22
  • @JohnLobo Added it to my question –  Dec 11 '21 at 14:45
  • @ack31 can you show **wsdl** schema for **items**? Before sending something you need to know what type does it expect. – Sergey Ligus Dec 11 '21 at 18:36
  • @SergeyLigus Updated the question, I don't know if it is what you are asking for –  Dec 11 '21 at 22:13

4 Answers4

5

Not sure this is an elegant way to do but it might help you to get some idea to get a soap response as you mentioned.First you have to build array like below

$data = [
        'SOAP-ENV:Body' => [
            'ns1:Person' => [
                'info' => [
                    "name" => ['_attributes' => ['xsi:type' => 'xsd:string'], '_value' => "John Doe"],
                    "surname" => ['_attributes' => ['xsi:type' => 'xsd:string'], '_value' => "Doe"],
                    "job" => ['_attributes' => ['xsi:type' => 'xsd:string'], '_value' => "developer"],
                    "from" =>['_attributes' => ['xsi:type' => 'xsd:string'], '_value' =>  "france"],
                    "address" => [
                        "country" => ['_attributes' => ['xsi:type' => 'xsd:string'], '_value' => "france"],
                        "city" => ['_attributes' => ['xsi:type' => 'xsd:string'], '_value' => "paris"],
                        "post_code" => ['_attributes' => ['xsi:type' => 'xsd:string'], '_value' => "123456"],
                        '_attributes' => ['xsi:type' => 'tns:getAddress']
                    ],
                    "items" => [
                        '__custom:item:1' => [
                            "name" => ['_attributes' => ['xsi:type' => 'xsd:string'], '_value' => "spoon"],
                            "material" => ['_attributes' => ['xsi:type' => 'xsd:string'], '_value' => "vacuum"],
                        ],
                        '_attributes' => ['xsi:type' => 'tns:getItems']
                    ],
                    '_attributes' => ['xsi:type' => 'tns:getPersonInfo',]
                ],
                '_attributes' => ['xmlns:ns1' => 'https://ex.pt/webservices',]
            ],
        ]

    ];

use package array-to-xml ref:https://github.com/spatie/array-to-xml

    $response = ArrayToXml::convert($data, [
        'rootElementName' => 'SOAP-ENV:Envelope',
        '_attributes' => [
            'SOAP-ENV:encodingStyle' => 'http://schemas.xmlsoap.org/soap/encoding/',
            'xmlns:SOAP-ENV' => 'http://schemas.xmlsoap.org/soap/envelope/',
            'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
            'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
            'xmlns:SOAP-ENC' => 'http://schemas.xmlsoap.org/soap/encoding/',
            'xmlns:tns' => 'http://ex.pt/soap/WebServices'
        ],
    ], true, 'UTF-8');
John Lobo
  • 14,355
  • 2
  • 10
  • 20
2

Lets try slightly different approach to your problem. If we assume that you are sending correct xml structure to the service and get correct response from it(copied your example from post). Then we can reverse it to array look like with some extra php.

<?php

$xml =<<<XML
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="http://ex.pt/soap/WebServices">
   <SOAP-ENV:Body>
      <ns1:Person xmlns:ns1="https://ex.pt/webservices">
         <data xsi:type="tns:getPersonInfo">
            <name xsi:type="xsd:string">John</name>
            <surname xsi:type="xsd:string">Doe</surname>
            <job xsi:type="xsd:string">developer</job>
            <from xsi:type="xsd:string">france</from>
            <address xsi:type="tns:getAddress">
               <country xsi:type="xsd:string">france</country>
               <city xsi:type="xsd:string">paris</city>
               <post_code xsi:type="xsd:string">12345</post_code>
            </address>
            <items xsi:type="tns:getItems">
               <item xsi:type="tns:ItemInfo">
                  <name xsi:type="xsd:string">pillow_1</name>
                  <material xsi:type="xsd:string">cotton</material>
               </item>
               <item xsi:type="tns:ItemInfo">
                  <name xsi:type="xsd:string">pillow_2</name>
                  <material xsi:type="xsd:string">cotton_2</material>
               </item>
            </items>
         </data>
      </ns1:Person>
   </SOAP-ENV:Body>
</SOAP-ENV:Envelope>
XML;

$response = preg_replace("/(<\/?)(\w+):([^>]*>)/", "$1$2$3", $xml);
$xml = new SimpleXMLElement($response);
$body = $xml->xpath('//SOAP-ENV:Body')[0];
$array = json_decode(json_encode((array)$body), TRUE); 
print_r($array);

Output will be next

Array
(
    [ns1Person] => Array
        (
            [data] => Array
                (
                    [name] => John
                    [surname] => Doe
                    [job] => developer
                    [from] => france
                    [address] => Array
                        (
                            [country] => france
                            [city] => paris
                            [post_code] => 12345
                        )

                    [items] => Array
                        (
                            [item] => Array
                                (
                                    [0] => Array
                                        (
                                            [name] => pillow_1
                                            [material] => cotton
                                        )

                                    [1] => Array
                                        (
                                            [name] => pillow_2
                                            [material] => cotton_2
                                        )

                                )

                        )

                )

        )

)

Then pass this array to your xml generator and check for meaningful response. You can skip ns1Person and do as you did before.

Otherwise refer to previous answer and try to make some kind of envelope with correct body according to your wsdl schema.

One thing worth mentioning is that you are expecting wrong type here <item xsi:type="xsd:string">

<items xsi:type="tns:getItems">
    <item xsi:type="xsd:string">
        <name xsi:type="xsd:string">pillow</name>
        <material xsi:type="xsd:string">cotton</material>
    </item>
    .... other items
</items> 

item itself is an array according to your wsdl schema. Type example is in the code above.

just in case, code for parsing xml was taken from here

Actually it would be great if you could provide an example of your wsdl schema(it can be something close to what you did), so that people who have time can mock Client\Server comms and check if it works correctly.

Hope that something from what was stated before will help)

Sergey Ligus
  • 462
  • 3
  • 10
2

Updated and simpler answer

From sergey-ligus answer, you need to wrap your items elements in a child element item without changing your function. so your array would be something like:

$test = array(
    'name'  => 'John Doe',
    'date'  => '2021-11-30 00:00:00.000',
    'job'   => 'developer',
    'where_from'    => 'france',
    'address'   => array(
        'country'   => 'france',
        'city'  => 'paris',
        'vat_number'    => '123456',
    ),
    'items' => array(
        'item'  => array(
            array(
                'cook'  => 'spoon',
                'clean' => 'vacuum',
            ),
            array(
                'cook'  => 'spoon2',
                'clean' => 'vacuum2',
            ),
            array(
                'cook'  => 'spoon3',
                'clean' => 'vacuum3',
            ),
        ),
    ),
);

Original answer

in teams of building your `XML` structure, you have two issues that you need to solve: 1. when your element is array, you should create `$child` node to hold array's elements. 2. in your code you are not handling the new node name for elements with numeric keys.

I assume that your original array should be something like this:

$test = array(
    'name'  => 'John Doe',
    'date'  => '2021-11-30 00:00:00.000',
    'job'   => 'developer',
    'where_from'    => 'france',
    'address'   => array(
        'country'   => 'france',
        'city'  => 'paris',
        'vat_number'    => '123456',
    ),
    'items' => array(
        array(
            'nodeName'  => 'item', // added for solution no. 2
            'cook'  => 'spoon',
            'clean' => 'vacuum',
        ),
        array(
            'nodeName'  => 'item', // added for solution no. 2
            'cook'  => 'spoon2',
            'clean' => 'vacuum2',
        ),
        array(
            'nodeName'  => 'item', // added for solution no. 2
            'cook'  => 'spoon3',
            'clean' => 'vacuum3',
        ),
    ),
);
You should fix your creation function like this:
function convertToXml($data, $name='root', &$doc=null, &$node=null){
    if ($doc==null){
        $doc = new DOMDocument('1.0','UTF-8');
        $doc->formatOutput = TRUE;
        $node = $doc;
    }

    if (is_array($data)){
        foreach($data as $var=>$val){
            // EDIT: create your "$child" node when you have an array $data
            if (!isset($child)){
                $child = $doc->createElement($name);
                $node->appendChild($child);
            }

            // EDIT: need solutions for numeric keys
            if (is_numeric($var)){
                // EDIT: first solution: assuming that the parent key contain "s" at the end, so we will remove it for the child node (items => item)
                $namex = substr($name, 0, -1);

                // EDIT: second solution: assuming that you have a 'nodeName' element in your array, so we will get its value and use it as child node name ($val['nodeName'] value) ONLY IN CASE $val IS AN ARRAY. you may need to use is_array($val) and isset($val['nodeName']) for this solution
                $namex = $val['nodeName'];
                unset( $val['nodeName'] );

                // EDIT: using the new node name "$namex" instead parent node name "$name"
                // EDIT: passing "$child" instead of "$node" to the function
                convertToXml($val, $namex, $doc, $child);
            }else{
                convertToXml($val, $var, $doc, $child);
            }
        }
    }else{
        $child = $doc->createElement($name);
        $node->appendChild($child);
        $textNode = $doc->createTextNode($data);
        $child->appendChild($textNode);
    }

    if ($doc==$node) return $doc->saveXML();
}
Walid Ajaj
  • 518
  • 4
  • 8
1

I used this simple function, credit to Koding Made Simple/Valli


<?php
// function to convert multi-dimensional array to xml
function Array2XML($obj, $array)
{
    foreach ($array as $key => $value)
    {
        if(is_numeric($key))
            $key = 'item' . $key;

        if (is_array($value))
        {
            $node = $obj->addChild($key);
            array2XML($node, $value);
        }
        else
        {
            $obj->addChild($key, htmlspecialchars($value));
        }
    }
}



// Sample Array    
// define php multi-dimensional array


$my_array = array (
    '0' => array (
        'id' => 'XYZ100',
        'personal' => array (
            'name' =>'Ashton Cox',
            'gender' => 'Male',
            'age' => 32,
            'address' => array (
                 'street' => '7 24th Street',
                 'city' => 'New York',
                 'state' => 'NY',
                 'zipcode' => '10038'
             )
        ),
        'profile' => array (
            'position' => 'Team Lead',
            'department' => 'Software'
        )
    ),
    '1' => array (
        'id' => 'XYZ121',
        'personal' => array (
            'name' => 'Rhona Davidson',
            'gender' => 'Female',
            'age' => 40,
            'address' => array (
                 'street' => 'S2 115th Street',
                 'city' => 'New York',
                 'state' => 'NY',
                 'zipcode' => '10100'
             )
        ),
        'profile' => array (
            'position' => 'Integration Specialist',
            'department' => 'Operations'
        )
    )
);


// create new instance of simplexml
$xml = new SimpleXMLElement('<root/>');

// function callback
array2XML($xml, $my_array);

// save as xml file
echo (($xml->asXML('data.xml')) ? 'Your XML file has been generated successfully!' : 'Error generating XML file!');
?>
Transformer
  • 6,963
  • 2
  • 26
  • 52