decode curl response gzip multipart attachment in PHP

  c#, curl, gzip, php, soap

I am trying to decode a binary zipped attachment received from a curl request, the attachment is an xml file but sent as binary by the API end point. Here is the full request I received:

--_=4883624417507473IBM4883624417507473MOKO
Content-Transfer-Encoding: 8bit
Content-ID: 30854c92-252a-4cb0-ae65-18ecf0de28d5
Content-Type: application/soap+xml; charset=UTF-8

<?xml version="1.0" encoding="utf-8"?><soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope"><soapenv:Header xmlns:wsa="http://www.w3.org/2005/08/addressing"><ns2:Messaging xmlns:ns2="http://docs.oasis-open.org/ebxml-msg/ebms/v3.0/ns/core/200704/" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" soapenv:mustUnderstand="true" wsu:Id="soapheader-1">
<rest of xml elements have been removed!>
</soapenv:Header><soapenv:Body xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="soapbody"></soapenv:Body></soapenv:Envelope>
--_=4883624417507473IBM4883624417507473MOKO
Content-Disposition: attachment; filename=Part1
Content-Transfer-Encoding: binary
Content-ID: <Attachment1>
Content-Type: application/gzip

‹íWÝOÛ0Ÿ´ÿáÔ—¾9-* ªP‰Ñn«¥*Ý´    ¡É8Wj-±#Ûé‡Ðþ÷ÙI!n  LŒ!ÄÛ}ùî|?ß%öf(,’XèýÚÔ˜´úB‘K9#4˜LD€Î†4¶HD
­uÂcºàI–œZ¹âfIe„žRR…A¥.Ì£ô
&Ú£-éÎ&§ŸFä[email protected]úƒÏ'¤ž*wÖ©j¸a°çt*•!]ÔLñÔYtÆŠ
p­-ß„ÎC­'YlÏÞ2$Ëë’,)ÒÚPÁô#{>á¨:6Õ{õ¥Ú#­ûÀ/ÿ+²õ„‘†Æ ²äÈ W!Êò¼€0S,d×Uã®
ó?•ERE4¨´G{¤_ŽÂOT*½#cWÛ
‰'ðiýŠ‘k=rƒjÌRª[email protected]{6EöûºGA·ýìÐM(_t·ÝÎKu›R!0†yTà÷ŽÜcÛ}©¦{MȽ‰qÙl<t™ÀEŠÌXÄÐ9×ïýu7Jÿ£]0p„LªèWcžp›t%Ëg×ݯíì6öZÛ­Övs¯v£Økì׆?{ßãR<^¦¹xÔsÒÆÔ¢â;«ÐÝö–Š¶yÀbáÎÞ·Ü©/£®î¾Æ¯ØòiÈÁø$º?ŽÍµÿxgÁ+þ³ítbw
z‰ñ„4ùGlDoèÆH5“ÂØK™)ÐrbæT!¤JÎxdßÛ*òíçîÄ]4ùP»•ÆèÀú
̺ûÃ^R†P_3ŸÏ‰52HégÂÔaJÝZ'lg&£Ò©JîÀ×Æuv*í~qß$©áºÓÛPý—믘6Ìm–yåܪÇÅ¥-»rù^ç}¶*ÆùÆ}ÎTzs{ÝræU¯,o^x¯}v«lg¯ñŠ ÷7ÿšÅši1€‚ü¨J7”ëŠ
V¯x‚lvR)è|üðo?M/
--_=4883624417507473IBM4883624417507473MOKO--

I have been searching and trying different things but couldn’t decode the attachment, I used the following to get the attachment part only:

preg_match('/(?<xml><.*??xml version=.*>)/', $response, $match);
$xml = $match['xml'];
$offset = strpos($response, $xml) + strlen($xml . PHP_EOL);
$attach = substr($response, $offset);

I have a working C#.net code connecting to the same API as below:

byte[] myData;
byte[] rv;
using (var webResponse = req.GetResponse())
{
   var responseHeaderstream = webResponse.Headers.ToByteArray();
   var responseStream = webResponse.GetResponseStream();
   myData = ReadFully(responseStream);
   responseStream.Dispose();
   rv = new byte[responseHeaderstream.Length + myData.Length];
   System.Buffer.BlockCopy(responseHeaderstream, 0, rv, 0, responseHeaderstream.Length);
   System.Buffer.BlockCopy(myData, 0, rv, responseHeaderstream.Length, myData.Length);                    
}

and then using the following code to loop and read any attachment(s) found then unzip any found attachments and the result is an XML file, this type of request should only have a single attachment:

Dim memstream As Stream = New MemoryStream(rv)
Dim entity As MimeMessage = MimeMessage.Load(memstream)
Dim attachments = New List(Of MimePart)()
Dim multiparts = New List(Of Multipart)()
Dim iter = New MimeIterator(entity)
While iter.MoveNext()
   Dim multipart = TryCast(iter.Parent, Multipart)
   Dim part = TryCast(iter.Current, MimePart)
   If multipart IsNot Nothing AndAlso part IsNot Nothing AndAlso part.IsAttachment Then
       multiparts.Add(multipart)
       attachments.Add(part)
   End If
End While

For i As Integer = 0 To attachments.Count - 1
    multiparts(i).Remove(attachments(i))
Next

For Each attachment In attachments
   Using memory = New MemoryStream()
       attachment.Content.DecodeTo(memory)
       Dim bytes = memory.ToArray()
       If attachment.ContentType.MimeType = "application/gzip" Then
          strAtchmnt = Unzip(bytes)
       Else
          strAtchmnt = Encoding.UTF8.GetString(bytes)
       End If
   End Using
Next

and here are the other functions needed to decode the attachment:

Public Shared Function Unzip(ByVal bytes As Byte()) As String
    Using msi = New MemoryStream(bytes)
        Using mso = New MemoryStream()
            Using gs = New GZipStream(msi, CompressionMode.Decompress)
                CopyTo(gs, mso)
            End Using
            Return Encoding.UTF8.GetString(mso.ToArray())
        End Using
    End Using
End Function
Public Shared Sub CopyTo(ByVal src As Stream, ByVal dest As Stream)
    Dim bytes As Byte() = New Byte(4095) {}
    Dim cnt As Integer
    cnt = -1
    While cnt <> 0
        cnt = src.Read(bytes, 0, bytes.Length)
        If cnt <> 0 Then dest.Write(bytes, 0, cnt)
    End While
End Sub

Any help is highly appreciated.
Here is the full curl request, the $pulreq is a signed xml document which there is no need to include here:

$guid = $this->guidv4();
$guidstring = "<" . $guid . "@ATODN-".substr(str_shuffle(MD5(microtime())), 0, 9).">";
$boundary = "_=Part_".dechex(time()).".". time();
$content_type_header = 'Content-Type: multipart/related; '
            .'type="application/xml"; '
            .'boundary="' . $boundary . '"; '
            .'start="'.$guidstring.'"; '
            .'start-info="application/soap+xml";';
$accept_header = 'Accept: multipart/related';
$transfer_encoding_header = 'Transfer-Encoding: chunked';
$headers = array($content_type_header, $accept_header, $transfer_encoding_header);
$postData  = "--" . $boundary . "rn"
            ."Content-Type: application/soap+xmlrn"
            ."Content-Transfer-Encoding: 8bitrn"
            ."Content-ID: ".$guidstring."rnrn"
            .$pulreq . "rn"
            ."--" .$boundary . "rn";
$curl = curl_init('https://xxxx/services/xxxx-async-pull');
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $postData);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 60);
curl_setopt($curl, CURLOPT_TIMEOUT, 60);
curl_setopt($curl, CURLOPT_HEADER, true);
curl_setopt($curl, CURLOPT_ENCODING,'');
curl_setopt($curl, CURLINFO_HEADER_OUT, true);

$response = curl_exec($curl);
if (curl_errno($curl)) {
    throw new Exception('cURL error:<br>' . curl_error($curl));
}
echo $response;

Source: Ask PHP

LEAVE A COMMENT