Récupération du contenu du fichier binaire en utilisant Javascript, encodage base64 et décodage inverse en utilisant Python

J'essaie de télécharger un fichier binaire en utilisant XMLHttpRequest (en utilisant un Webkit récent) et base64-Encoder son contenu en utilisant cette fonction simple:

function getBinary(file){
    var xhr = new XMLHttpRequest();  
    xhr.open("GET", file, false);  
    xhr.overrideMimeType("text/plain; charset=x-user-defined");  
    xhr.send(null);
    return xhr.responseText;
}

function base64encode(binary) {
    return btoa(unescape(encodeURIComponent(binary)));
}

var binary = getBinary('http://some.tld/sample.pdf');
var base64encoded = base64encode(binary);

Comme une note de côté, tout ce qui précède est des choses JavaScript standard, y compris btoa() et encodeURIComponent(): https://developer.mozilla.org/en/DOM/window.btoa

Cela fonctionne assez bien, et je peux même décoder le contenu base64 en utilisant Javascript:

function base64decode(base64) {
    return decodeURIComponent(escape(atob(base64)));
}

var decodedBinary = base64decode(base64encoded);
decodedBinary === binary // true

Maintenant, je veux décoder le contenu codé en base64 en utilisant Python qui consommez une chaîne JSON pour obtenir la valeur de chaîne base64encoded. Naïvement c'est ce que je fais:

import urllib
import base64
# ... retrieving of base64 encoded string through JSON
base64 = "77+9UE5HDQ……………oaCgA="
source_contents = urllib.unquote(base64.b64decode(base64))
destination_file = open(destination, 'wb')
destination_file.write(source_contents)
destination_file.close()

Mais le fichier résultant est invalide, on dirait que l'opération est messaed avec UTF-8, encodage ou quelque chose qui n'est pas encore clair pour moi.

Si j'essaie de décoder le contenu UTF-8 avant de le mettre dans le fichier de destination, une erreur est générée:

import urllib
import base64
# ... retrieving of base64 encoded string through JSON
base64 = "77+9UE5HDQ……………oaCgA="
source_contents = urllib.unquote(base64.b64decode(base64)).decode('utf-8')
destination_file = open(destination, 'wb')
destination_file.write(source_contents)
destination_file.close()

$ python test.py
// ...
UnicodeEncodeError: 'ascii' codec can't encode character u'ufffd' in position 0: ordinal not in range(128)

En guise de remarque, voici une capture d'écran de deux représentations textuelles d'un même fichier; à gauche: l'original; à droite: celui créé à partir de la chaîne décodée en base64: http://cl.ly/0U3G34110z3c132O2e2x

Existe-t-il une astuce connue pour contourner ces problèmes d'encodage lors de la tentative de recréation du fichier? Comment y arriveriez-vous vous-même?

Toute aide ou indice très apprécié:)

36
demandé sur NiKo 2011-09-10 13:55:11

1 réponses

Donc je réponds à moi - même-et désolé pour cela-mais je pense que cela pourrait être utile pour quelqu'un aussi perdu que moi;)

Vous devez donc utiliser ArrayBuffer et définir la propriété responseType de votre instance d'objet XMLHttpRequest sur arraybuffer pour récupérer un tableau natif d'octets, qui peut être converti en base64 en utilisant la fonction pratique suivante (trouvée , l'auteur peut être béni ici):

function base64ArrayBuffer(arrayBuffer) {
  var base64    = ''
  var encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'

  var bytes         = new Uint8Array(arrayBuffer)
  var byteLength    = bytes.byteLength
  var byteRemainder = byteLength % 3
  var mainLength    = byteLength - byteRemainder

  var a, b, c, d
  var chunk

  // Main loop deals with bytes in chunks of 3
  for (var i = 0; i < mainLength; i = i + 3) {
    // Combine the three bytes into a single integer
    chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2]

    // Use bitmasks to extract 6-bit segments from the triplet
    a = (chunk & 16515072) >> 18 // 16515072 = (2^6 - 1) << 18
    b = (chunk & 258048)   >> 12 // 258048   = (2^6 - 1) << 12
    c = (chunk & 4032)     >>  6 // 4032     = (2^6 - 1) << 6
    d = chunk & 63               // 63       = 2^6 - 1

    // Convert the raw binary segments to the appropriate ASCII encoding
    base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d]
  }

  // Deal with the remaining bytes and padding
  if (byteRemainder == 1) {
    chunk = bytes[mainLength]

    a = (chunk & 252) >> 2 // 252 = (2^6 - 1) << 2

    // Set the 4 least significant bits to zero
    b = (chunk & 3)   << 4 // 3   = 2^2 - 1

    base64 += encodings[a] + encodings[b] + '=='
  } else if (byteRemainder == 2) {
    chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1]

    a = (chunk & 64512) >> 10 // 64512 = (2^6 - 1) << 10
    b = (chunk & 1008)  >>  4 // 1008  = (2^6 - 1) << 4

    // Set the 2 least significant bits to zero
    c = (chunk & 15)    <<  2 // 15    = 2^4 - 1

    base64 += encodings[a] + encodings[b] + encodings[c] + '='
  }

  return base64
}

Voici donc un code de travail:

var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://some.tld/favicon.png', false);
xhr.responseType = 'arraybuffer';
xhr.onload = function(e) {
    console.log(base64ArrayBuffer(e.currentTarget.response));
};
xhr.send();

Cela va enregistrer un valide chaîne codée en base64 représentant le contenu du fichier binaire.

Edit: pour les anciens navigateurs n'ayant pas accès à ArrayBuffer et ayant btoa() échouant sur les caractères d'encodage, voici une autre façon d'obtenir une version codée en base64 de n'importe quel binaire:

function getBinary(file){
    var xhr = new XMLHttpRequest();
    xhr.open("GET", file, false);
    xhr.overrideMimeType("text/plain; charset=x-user-defined");
    xhr.send(null);
    return xhr.responseText;
}

function base64Encode(str) {
    var CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    var out = "", i = 0, len = str.length, c1, c2, c3;
    while (i < len) {
        c1 = str.charCodeAt(i++) & 0xff;
        if (i == len) {
            out += CHARS.charAt(c1 >> 2);
            out += CHARS.charAt((c1 & 0x3) << 4);
            out += "==";
            break;
        }
        c2 = str.charCodeAt(i++);
        if (i == len) {
            out += CHARS.charAt(c1 >> 2);
            out += CHARS.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4));
            out += CHARS.charAt((c2 & 0xF) << 2);
            out += "=";
            break;
        }
        c3 = str.charCodeAt(i++);
        out += CHARS.charAt(c1 >> 2);
        out += CHARS.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4));
        out += CHARS.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6));
        out += CHARS.charAt(c3 & 0x3F);
    }
    return out;
}

console.log(base64Encode(getBinary('http://www.google.fr/images/srpr/logo3w.png')));

J'espère que cela aide les autres comme il l'a fait pour moi.

75
répondu NiKo 2011-09-12 09:56:12