Comment puis-je accéder à XHR responsibody (pour les données binaires) à partir de Javascript dans IE?

j'ai une page web qui utilise XMLHttpRequest pour télécharger une ressource binaire.

dans Firefox et Gecko je peux utiliser responsabiletext pour obtenir les octets, même si le bytestream inclut des zéros binaires. J'ai peut-être besoin de forcer le type mime overrideMimeType() pour que ça arrive. Dans IE, cependant, responsabiletext ne fonctionne pas, parce qu'il semble se terminer au premier zéro. Si vous lisez 100 000 octets, et que l'octet 7 est un zéro binaire, vous ne pourrez accéder qu'à 7 octets. IE XMLHttpRequest expose un responseBody propriété pour accéder aux octets. J'ai vu quelques messages suggérant qu'il est impossible d'accéder à cette propriété d'une manière significative directement à partir de Javascript. Cela semble fou de moi.

xhr.responseBody accessible à partir de VBScript, donc la solution évidente est de définir une méthode en VBScript dans la page Web, puis d'appeler cette méthode à partir de Javascript. Voir jsdap pour un exemple. MODIFIER: NE PAS UTILISER CECI 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('');

Est-ce vraiment vrai? La meilleure façon de faire? la copie de chaque octet? Pour un grand flux binaire qui ne va pas être très efficace.

Il y a aussi un possible technique utilisant L'ADODB.Stream, qui est L'équivalent COM d'un MemoryStream. Voir ici pour un exemple. Il ne nécessite pas de VBScript, mais nécessite un objet COM séparé.

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
}

mais ça ne va pas bien marcher - ADODB.Le flux est désactivé sur la plupart des machines de nos jours.


dans les outils de développement IE8-L'équivalent IE de Firebug-je peux voir que le responsable est un tableau d'octets et je peux même voir les octets eux-mêmes. Les données sont . Je ne comprends pas pourquoi je ne peux pas l'obtenir.

Est-il possible pour moi de le lire avec responseText?

conseils? (autre que la définition d'une méthode VBScript)

24
demandé sur Cheeso 2009-12-17 10:09:52
la source

7 ответов

Oui, la réponse m'est venu pour la lecture de données binaires via XHR dans IE, est d'utiliser VBScript injection. C'était désagréable pour moi au début, mais, je le regarde comme juste un morceau de plus dépendant du navigateur de code. (Le XHR et le responsabiletext réguliers fonctionnent bien dans les autres navigateurs; vous pouvez avoir à forcer le type mime avec XMLHttpRequest.overrideMimeType(). Ce n'est pas disponible sur IE).

C'est comment j'ai obtenu une chose qui fonctionne comme responseText dans IE, même pour les données binaires. Tout d'abord, injecter quelques VBScript comme une chose une seule fois, comme ceci:

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);
}

la classe JS que j'utilise qui lit des fichiers binaires expose une seule méthode intéressante,readCharAt(i), qui lit le caractère (un octet, en fait) à l'index de l'I. C'est la façon dont je l'ai installé:

// 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);
}

code de conversion a été fourni par Miskun.

Très rapide, fonctionne très bien.

j'ai utilisé cette méthode pour lire et extraire des fichiers zip à partir de Javascript, et aussi dans une classe qui lit et affiche les fichiers EPUB en Javascript. Très raisonnable de la performance. Environ une demi-seconde pour un fichier de 500 ko.

14
répondu Cheeso 2016-09-02 21:53:15
la source

XMLHttpRequest.responseBody est un VBArray objet contenant les octets brutes. Vous pouvez convertir ces objets en tableaux standard en utilisant le toArray() fonction:

var data = xhr.responseBody.toArray();
11
répondu timrice 2015-06-03 12:00:13
la source

je suggère deux autres options (rapides):

  1. tout d'Abord, vous pouvez utiliser ADODB.Recordset pour convertir le tableau des octets en chaîne de caractères. Je suppose que cet objet est plus commun que ADODB.Stream, qui est souvent désactivé pour des raisons de sécurité. Cette option est très rapide, moins que 30 ms pour un fichier de 500kB.

  2. dans un Deuxième temps, si le jeu n'est pas accessible, il y a un astuce pour accéder au tableau d'octets les données de Javascript. Envoyez votre xhr.répondre à VBScript, passer par n'importe quelle fonction de chaîne de VBScript comme CStr (ne prend pas de temps), et le retourner à JS. Vous obtiendrez une chaîne bizarre avec des octets concaténés en unicode 16 bits (à l'inverse). Vous pouvez alors convertir cette chaîne rapidement en un bytestring Utilisable à travers un expression régulière avec remplacement basé sur le dictionnaire. Prend environ 1s pour 500kB.

à titre de comparaison, le la conversion byte-by-byte par des boucles prend plusieurs minutes pour ce même fichier de 500 ko, donc c'est un no-brainer :) ci-Dessous le code que j'ai été en utilisant, pour les insérer dans votre en-tête. Alors appelez la fonction ieGetBytes avec votre 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
répondu Louis LC 2010-06-16 06:27:02
la source

Merci beaucoup pour cette solution. la fonction BinaryToArray () dans VbScript fonctionne très bien pour moi.

soit dit en passant, j'ai besoin des données binaires pour les fournir à un Applet. (Ne me demandez pas pourquoi les Applets ne peuvent pas être utilisés pour télécharger des données binaires. Longue histoire courte.. authentification MS bizarre qui ne passe pas par des appels d'applets (URLConn). Son particulièrement étrange dans les cas où les utilisateurs sont derrière un proxy )

L'Applet a besoin d'un tableau d'octets à partir de ces données, donc voici ce que je fais pour obtenir c':

 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]);
    }

le tableau byte peut alors être converti en un bytearrayinputstream pour un traitement ultérieur.

1
répondu rk2010 2009-12-22 18:28:43
la source

j'essayais de télécharger un fichier et de le signer en utilisant CAPICOM.DLL. La seule façon de le faire était d'injecter une fonction VBScript qui effectue le téléchargement. C'est ma solution:

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
répondu Renato Crivano 2014-01-03 15:59:55
la source

je vous Remercie pour ce post.

j'ai trouvé ce lien utile:

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

Spécialement cette partie:

</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>

j'ai ajouté ceci à ma page htm. Puis j'appelle cette fonction à partir de mon javascript:

 responseText = BinaryToString(xhr.responseBody);

fonctionne sur IE8, IE9, IE10, FF & Chrome.

1
répondu George G 2014-02-26 06:24:19
la source

Vous pouvez aussi simplement faire un script proxy qui va à l'adresse que vous demandez & base64's it. Ensuite, vous avez juste à passer une chaîne de requête au script proxy qui lui dit l'adresse. Dans IE vous devez faire manuellement base64 dans JS cependant. Mais c'est une façon de faire si vous ne voulez pas utiliser VBScript.

j'ai utilisé ce pour mon émulateur GameBoy Color.

voici le script PHP qui fait la magie:

<?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 M Y H: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
répondu Grant Galitz 2015-04-22 10:00:38
la source

Autres questions sur