Issue
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äKo@úƒÏ'¤ž*\wÖ©j¸a°çt*•!]ÔLñÔYtÆŠ
p-ß„ÎC'YlÏÞ2$Ëë’,)ÒÚPÁô#{>á¨:6Õ{õ¥Ú#ûÀ/ÿ+²õ„‘†Æ ²äÈ W!Êò¼€0S,d×Uã®
ó?•ERE4¨´G{¤_ŽÂOT*½#c\WÛ
‰'ðiýŠ‘k=rƒjÌRª5F@3cOp{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 . "\r\n"
."Content-Type: application/soap+xml\r\n"
."Content-Transfer-Encoding: 8bit\r\n"
."Content-ID: ".$guidstring."\r\n\r\n"
.$pulreq . "\r\n"
."--" .$boundary . "\r\n";
$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;
Solution
It appears that the solution is really simple but didn't think about it before Once I extracted the decoded attachment, all I needed is:
$xml_string = gzdecode($decoded_attachment);
and the result is the expected XML attachment
Answered By - Alan