How do I access XHR responseBody (for binary data) from Javascript in IE?

I have a webpage that uses XMLHttpRequest to load a binary resource.

In Firefox and Gecko, I can use responseText to get bytes, even if the byte stream includes binary zeros. I may need to force mimetype with overrideMimeType() to make this happen. In IE, however, responseText does not work because it seems to end with the first zero. If you read 100,000 bytes, and byte 7 is binary zero, you can only access 7 bytes. IE XMLHttpRequest provides a responseBody property for accessing bytes. I have seen several posts suggesting that it is not possible to access this property in any meaningful way directly from Javascript. That sounds crazy to me.

xhr.responseBody is available from VBScript, so the obvious workaround is to define a method in VBScript on a web page and then call this method from Javascript. See jsdap for an example. EDIT: DO NOT USE THIS VBScript !!

 var IE_HACK = (/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent)); // no no no! Don't do this! if (IE_HACK) document.write('<script type="text/vbscript">\n\ Function BinaryToArray(Binary)\n\ Dim i\n\ ReDim byteArray(LenB(Binary))\n\ For i = 1 To LenB(Binary)\n\ byteArray(i-1) = AscB(MidB(Binary, i, 1))\n\ Next\n\ BinaryToArray = byteArray\n\ End Function\n\ </script>'); var xml = (window.XMLHttpRequest) ? new XMLHttpRequest() // Mozilla/Safari/IE7+ : (window.ActiveXObject) ? new ActiveXObject("MSXML2.XMLHTTP") // IE6 : null; // Commodore 64? xml.open("GET", url, true); if (xml.overrideMimeType) { xml.overrideMimeType('text/plain; charset=x-user-defined'); } else { xml.setRequestHeader('Accept-Charset', 'x-user-defined'); } xml.onreadystatechange = function() { if (xml.readyState == 4) { if (!binary) { callback(xml.responseText); } else if (IE_HACK) { // call a VBScript method to copy every single byte callback(BinaryToArray(xml.responseBody).toArray()); } else { callback(getBuffer(xml.responseText)); } } }; xml.send(''); 

It's true? The best way? copy every byte? For a large binary stream that will not be very efficient.

There is also a possible technology using ADODB.Stream, which is the COM equivalent of MemoryStream. See here for an example. It does not require VBScript, but requires a separate COM object.

 if (typeof (ActiveXObject) != "undefined" && typeof (httpRequest.responseBody) != "undefined") { // Convert httpRequest.responseBody byte stream to shift_jis encoded string var stream = new ActiveXObject("ADODB.Stream"); stream.Type = 1; // adTypeBinary stream.Open (); stream.Write (httpRequest.responseBody); stream.Position = 0; stream.Type = 1; // adTypeBinary; stream.Read.... /// ???? what here } 

But this will not work well - ADODB.Stream is disabled on most computers these days.




In IE8 developer tools - the equivalent of Firebug IE - I can see that responseBody is an array of bytes, and I even see the bytes themselves. The data is right there. I don’t understand why I can’t get to him.

Is it possible for me to read it using responseText?

hints? (except for defining a VBScript method)

+24
javascript
Dec 17 '09 at 7:09
source share
7 answers

Yes, the answer I came up with to read binary data through XHR in IE is to use VBScript injection. At first it was unpleasant for me, but I see it as another browser dependent bit. (Regular XHR and responseText work fine in other browsers, you may have to force the mime type with XMLHttpRequest.overrideMimeType() . This is not available on IE).

This is how I got what works like responseText in IE, even for binary data. First add VBScript as a one-time thing, for example:

 if(/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent)) { var IEBinaryToArray_ByteStr_Script = "<!-- IEBinaryToArray_ByteStr -->\r\n"+ "<script type='text/vbscript' language='VBScript'>\r\n"+ "Function IEBinaryToArray_ByteStr(Binary)\r\n"+ " IEBinaryToArray_ByteStr = CStr(Binary)\r\n"+ "End Function\r\n"+ "Function IEBinaryToArray_ByteStr_Last(Binary)\r\n"+ " Dim lastIndex\r\n"+ " lastIndex = LenB(Binary)\r\n"+ " if lastIndex mod 2 Then\r\n"+ " IEBinaryToArray_ByteStr_Last = Chr( AscB( MidB( Binary, lastIndex, 1 ) ) )\r\n"+ " Else\r\n"+ " IEBinaryToArray_ByteStr_Last = "+'""'+"\r\n"+ " End If\r\n"+ "End Function\r\n"+ "</script>\r\n"; // inject VBScript document.write(IEBinaryToArray_ByteStr_Script); } 

The JS class that I use to read binary files provides one interesting readCharAt(i) method that reads a character (bytes, really) in the i-th index. Here's how I set it up:

 // see doc on http://msdn.microsoft.com/en-us/library/ms535874(VS.85).aspx function getXMLHttpRequest() { if (window.XMLHttpRequest) { return new window.XMLHttpRequest; } else { try { return new ActiveXObject("MSXML2.XMLHTTP"); } catch(ex) { return null; } } } // this fn is invoked if IE function IeBinFileReaderImpl(fileURL){ this.req = getXMLHttpRequest(); this.req.open("GET", fileURL, true); this.req.setRequestHeader("Accept-Charset", "x-user-defined"); // my helper to convert from responseBody to a "responseText" like thing var convertResponseBodyToText = function (binary) { var byteMapping = {}; for ( var i = 0; i < 256; i++ ) { for ( var j = 0; j < 256; j++ ) { byteMapping[ String.fromCharCode( i + j * 256 ) ] = String.fromCharCode(i) + String.fromCharCode(j); } } // call into VBScript utility fns var rawBytes = IEBinaryToArray_ByteStr(binary); var lastChr = IEBinaryToArray_ByteStr_Last(binary); return rawBytes.replace(/[\s\S]/g, function( match ) { return byteMapping[match]; }) + lastChr; }; this.req.onreadystatechange = function(event){ if (that.req.readyState == 4) { that.status = "Status: " + that.req.status; //that.httpStatus = that.req.status; if (that.req.status == 200) { // this doesn't work //fileContents = that.req.responseBody.toArray(); // this doesn't work //fileContents = new VBArray(that.req.responseBody).toArray(); // this works... var fileContents = convertResponseBodyToText(that.req.responseBody); fileSize = fileContents.length-1; if(that.fileSize < 0) throwException(_exception.FileLoadFailed); that.readByteAt = function(i){ return fileContents.charCodeAt(i) & 0xff; }; } if (typeof callback == "function"){ callback(that);} } }; this.req.send(); } // this fn is invoked if non IE function NormalBinFileReaderImpl(fileURL){ this.req = new XMLHttpRequest(); this.req.open('GET', fileURL, true); this.req.onreadystatechange = function(aEvt) { if (that.req.readyState == 4) { if(that.req.status == 200){ var fileContents = that.req.responseText; fileSize = fileContents.length; that.readByteAt = function(i){ return fileContents.charCodeAt(i) & 0xff; } if (typeof callback == "function"){ callback(that);} } else throwException(_exception.FileLoadFailed); } }; //XHR binary charset opt by Marcus Granado 2006 [http://mgran.blogspot.com] this.req.overrideMimeType('text/plain; charset=x-user-defined'); this.req.send(null); } 

conversion code was provided by Miskun.

Very fast, works great.

I used this method to read and extract zip files from Javascript, as well as to a class that reads and displays EPUB files in Javascript. Very reasonable performance. About half a second for a 500 KB file.

+14
Dec 18 '09 at 3:49
source share

XMLHttpRequest.responseBody - A VBArray object containing raw bytes. You can convert these objects to standard arrays using the toArray() function:

 var data = xhr.responseBody.toArray(); 
+11
Dec 02 2018-10-12T00:
source share

I would suggest two other (quick) options:

  • First, you can use ADODB.Recordset to convert an array of bytes to a string. I would suggest that this object is more common than ADODB.Stream, which is often disabled for security reasons. This option is VERY fast, less than 30 ms for a 500 KB file.

  • Secondly, if the Recordset component is not available, there is a trick to access byte array data from Javascript . Send your xhr.responseBody to VBScript, pass it through any VBScript string string such as CStr (takes no time), and return it to JS. You will get a strange string with bytes concatenated into 16-bit Unicode (in reverse order). Then you can quickly convert this string to usable bytes through a regular expression with a dictionary replacement. Takes about 1 s for 500 KB.

For comparison, byte-by-loop conversion takes several minutes for the same file of 500 KB, so no problem :) Below the code that I used, you can paste it into your header. Then call the ieGetBytes function with your xhr.responseBody.

 <!--[if IE]> <script type="text/vbscript"> 'Best case scenario when the ADODB.Recordset object exists 'We will do the existence test in Javascript (see after) 'Extremely fast, about 25ms for a 500kB file Function ieGetBytesADO(byteArray) Dim recordset Set recordset = CreateObject("ADODB.Recordset") With recordset .Fields.Append "temp", 201, LenB(byteArray) .Open .AddNew .Fields("temp").AppendChunk byteArray .Update End With ieGetBytesADO = recordset("temp") recordset.Close Set recordset = Nothing End Function 'Trick to return a Javascript-readable string from a VBScript byte array 'Yet the string is not usable as such by Javascript, since the bytes 'are merged into 16-bit unicode characters. Last character missing if odd length. Function ieRawBytes(byteArray) ieRawBytes = CStr(byteArray) End Function 'Careful the last character is missing in case of odd file length 'We Will call the ieLastByte function (below) from Javascript 'Cannot merge directly within ieRawBytes as the final byte would be duplicated Function ieLastChr(byteArray) Dim lastIndex lastIndex = LenB(byteArray) if lastIndex mod 2 Then ieLastChr = Chr( AscB( MidB( byteArray, lastIndex, 1 ) ) ) Else ieLastChr = "" End If End Function </script> <script type="text/javascript"> try { // best case scenario, the ADODB.Recordset object exists // we can use the VBScript ieGetBytes function to transform a byte array into a string var ieRecordset = new ActiveXObject('ADODB.Recordset'); var ieGetBytes = function( byteArray ) { return ieGetBytesADO(byteArray); } ieRecordset = null; } catch(err) { // no ADODB.Recordset object, we will do the conversion quickly through a regular expression // initializes for once and for all the translation dictionary to speed up our regexp replacement function var ieByteMapping = {}; for ( var i = 0; i < 256; i++ ) { for ( var j = 0; j < 256; j++ ) { ieByteMapping[ String.fromCharCode( i + j * 256 ) ] = String.fromCharCode(i) + String.fromCharCode(j); } } // since ADODB is not there, we replace the previous VBScript ieGetBytesADO function with a regExp-based function, // quite fast, about 1.3 seconds for 500kB (versus several minutes for byte-by-byte loops over the byte array) var ieGetBytes = function( byteArray ) { var rawBytes = ieRawBytes(byteArray), lastChr = ieLastChr(byteArray); return rawBytes.replace(/[\s\S]/g, function( match ) { return ieByteMapping[match]; }) + lastChr; } } </script> <![endif]--> 
+3
Jun 16 '10 at 2:27
source share

Thanks so much for this solution. The BinaryToArray () function in VbScript works fine for me.

By the way, I need binary data to provide him with an applet. (Don’t ask me why applets cannot be used to download binary data. In short ... weird MS authentication that cannot go through applets (URLConn). It is especially strange when users are behind a proxy server)

An applet needs a byte array from this data, so here is what I do to get it:

  String[] results = result.toString().split(","); byte[] byteResults = new byte[results.length]; for (int i=0; i<results.length; i++){ byteResults[i] = (byte)Integer.parseInt(results[i]); } 

The byte array can then be converted to a bytearrayinputstream for further processing.

+1
Dec 22 '09 at 15:28
source share

You can also just make a proxy script that is sent to the address you request and base64. Then you just need to pass the query string to the proxy server script that tells it the address. In IE, you must manually make base64 in JS. But this is the way to go if you do not want to use VBScript.

I used this for my GameBoy Color emulator .

Here is a PHP script that does the magic:

 <?php //Binary Proxy if (isset($_GET['url'])) { try { $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, stripslashes($_GET['url'])); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); curl_setopt($curl, CURLOPT_POST, false); curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 30); $result = curl_exec($curl); curl_close($curl); if ($result !== false) { header('Content-Type: text/plain; charset=ASCII'); header('Expires: '.gmdate('D, d MYH:i:s \G\M\T', time() + (3600 * 24 * 7))); echo(base64_encode($result)); } else { header('HTTP/1.0 404 File Not Found'); } } catch (Exception $error) { } } ?> 
+1
Sep 29 '10 at 5:30
source share

I tried to download the file and signed it using CAPICOM.DLL. The only way I could do this was to enter a VBScript function that does the loading. This is my decision:

 if(/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent)) { var VBConteudo_Script = '<!-- VBConteudo -->\r\n'+ '<script type="text/vbscript">\r\n'+ 'Function VBConteudo(url)\r\n'+ ' Set objHTTP = CreateObject("MSXML2.XMLHTTP")\r\n'+ ' objHTTP.open "GET", url, False\r\n'+ ' objHTTP.send\r\n'+ ' If objHTTP.Status = 200 Then\r\n'+ ' VBConteudo = objHTTP.responseBody\r\n'+ ' End If\r\n'+ 'End Function\r\n'+ '\<\/script>\r\n'; // inject VBScript document.write(VBConteudo_Script); } 
+1
Jan 03 '14 at 11:40
source share

Thanks for this post.

I found this link useful:

http://www.codingforums.com/javascript-programming/47018-help-using-responsetext-property-microsofts-xmlhttp-activexobject-ie6.html

Specially this part:

 </script> <script language="VBScript"> Function BinaryToString(Binary) Dim I,S For I = 1 to LenB(Binary) S = S & Chr(AscB(MidB(Binary,I,1))) Next BinaryToString = S End Function </script> 

I added this to my htm page. Then I call this function from my javascript:

  responseText = BinaryToString(xhr.responseBody); 

Works on IE8, IE9, IE10, FF and Chrome.

+1
Feb 26 '14 at 2:10
source share



All Articles