读取并对二进制文件进行base64编码

9
我正在尝试用JavaScript从文件系统读取二进制文件,然后对其进行base64编码。我使用FileReader API来读取数据,并在这里找到了base64编码器。
我已经编写的代码似乎接近工作状态,但问题是生成的base64数据不正确。以下是我的代码:
function saveResource() {
    var file = $(".resourceFile")[0].files[0];

    var reader = new FileReader();
    reader.onload = function(evt) {
        var fileData = evt.target.result;
        var bytes = new Uint8Array(fileData);
        var binaryText = '';

        for (var index = 0; index < bytes.byteLength; index++) {
            binaryText += String.fromCharCode( bytes[index] );
        }

        console.log(Base64.encode(binaryText));

    };
    reader.readAsArrayBuffer(file);
};

这是我正在测试的文件(它是一个100x100的蓝色正方形):

enter image description here

根据在线base64编解码器,该文件应该进行编码:

/9j/4AAQSkZJRgABAgAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCABkAGQDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDxyiiiv3E8wKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//Z

这是一个编码相关的文本,包含了HTML标签。请将其翻译为中文,并保留HTML标签。

...但是我从JavaScript中得到的是:

这是一个经过base64编码的字符串,解码后为一段HTML代码,其中包含了JavaScript代码和CSS样式表的内容。

如果我不得不猜测,我会说这个问题与二进制数据中的不可打印字符有关(如果我编码一个纯文本文档,那就没问题了)。但是如何解决这个问题才是最好的方法呢?
编辑
看起来这可能是base64库本身的问题(或者如果不是,则是将Uint8Array解包成字符串以进行库调用的方式),如果我改用浏览器的btoa()函数,并直接传递Uint8Array binaryText,那么就没问题了。太遗憾这个函数并不存在于所有浏览器中。
2个回答

8

谷歌来帮忙了。我找到了以下代码,它将输入数据作为一个普通的“字节数组”(介于0和255之间的数字;如果直接将Uint8Array传递给它也可以正常工作),并将其添加到我正在使用的库中:

//note:  it is assumed that the Base64 object has already been defined
//License:  Apache 2.0
Base64.byteToCharMap_ = null;
Base64.charToByteMap_ = null;
Base64.byteToCharMapWebSafe_ = null;
Base64.charToByteMapWebSafe_ = null;
Base64.ENCODED_VALS_BASE =
    'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +
    'abcdefghijklmnopqrstuvwxyz' +
    '0123456789';

/**
 * Our default alphabet. Value 64 (=) is special; it means "nothing."
 * @type {string}
 */
Base64.ENCODED_VALS = Base64.ENCODED_VALS_BASE + '+/=';
Base64.ENCODED_VALS_WEBSAFE = Base64.ENCODED_VALS_BASE + '-_.';

/**
 * Base64-encode an array of bytes.
 *
 * @param {Array.<number>|Uint8Array} input An array of bytes (numbers with
 *     value in [0, 255]) to encode.
 * @param {boolean=} opt_webSafe Boolean indicating we should use the
 *     alternative alphabet.
 * @return {string} The base64 encoded string.
 */
Base64.encodeByteArray = function(input, opt_webSafe) {
  Base64.init_();

  var byteToCharMap = opt_webSafe ?
                      Base64.byteToCharMapWebSafe_ :
                      Base64.byteToCharMap_;

  var output = [];

  for (var i = 0; i < input.length; i += 3) {
    var byte1 = input[i];
    var haveByte2 = i + 1 < input.length;
    var byte2 = haveByte2 ? input[i + 1] : 0;
    var haveByte3 = i + 2 < input.length;
    var byte3 = haveByte3 ? input[i + 2] : 0;

    var outByte1 = byte1 >> 2;
    var outByte2 = ((byte1 & 0x03) << 4) | (byte2 >> 4);
    var outByte3 = ((byte2 & 0x0F) << 2) | (byte3 >> 6);
    var outByte4 = byte3 & 0x3F;

    if (!haveByte3) {
      outByte4 = 64;

      if (!haveByte2) {
        outByte3 = 64;
      }
    }

    output.push(byteToCharMap[outByte1],
                byteToCharMap[outByte2],
                byteToCharMap[outByte3],
                byteToCharMap[outByte4]);
  }

  return output.join('');
};

/**
 * Lazy static initialization function. Called before
 * accessing any of the static map variables.
 * @private
 */
Base64.init_ = function() {
  if (!Base64.byteToCharMap_) {
    Base64.byteToCharMap_ = {};
    Base64.charToByteMap_ = {};
    Base64.byteToCharMapWebSafe_ = {};
    Base64.charToByteMapWebSafe_ = {};

    // We want quick mappings back and forth, so we precompute two maps.
    for (var i = 0; i < Base64.ENCODED_VALS.length; i++) {
      Base64.byteToCharMap_[i] =
          Base64.ENCODED_VALS.charAt(i);
      Base64.charToByteMap_[Base64.byteToCharMap_[i]] = i;
      Base64.byteToCharMapWebSafe_[i] =
          Base64.ENCODED_VALS_WEBSAFE.charAt(i);
      Base64.charToByteMapWebSafe_[
          Base64.byteToCharMapWebSafe_[i]] = i;
    }
  }
};

上述函数所在的完整库代码在此提供,但其非修改形式似乎依赖于其他许多库。上面稍作改动的版本适用于那些只需要快速解决此问题的人。


这正是我正在寻找的 - 有没有可能发布解码部分? - Graham
1
我没有自己的解码部分,因为在我的情况下(解码是在服务器端完成的),我不需要它。然而,谷歌的原始源代码在这里可用:http://docs.closure-library.googlecode.com/git/closure_goog_crypt_base64.js.source.html - aroth
是的,我看过了,但它有你在编码部分中删除的依赖项,所以我希望能找到类似的东西,不过还是谢谢你的回复。 - Graham
变量charToByteMap和charToByteMapWebSafe在您的代码中完全没有用处。它们从未被使用过。此外,函数init_()可以完全删除,因为如果您编写byteToCharMap[outByte1]或ENCODED_VALS.charAt(outByte1),它们是相同的。该代码可以轻松简化为一个不带外部变量的函数。 - Elmue
@Elmue - 完全正确。但这不是我的代码,是别人写的,我只是删除了他们对几个外部库的引用,以便它可以在没有它们的情况下运行。 - aroth

1
将二进制文件视为ArrayBuffer,这与任何字符编码无关。你的蓝色正方形(.jpg)有361个原生字节,表示0..255(十进制)的八位组,它们不是字符!
这意味着:使用ArrayBuffer将其编码为Base64,并使用众所周知的base64算法。
通过Perl回到原点,如上图所示显示蓝色正方形:
my $fh = IO::File->new;
$fh->open("d:/tmp/x.jpg", O_BINARY|O_CREAT|O_RDWR|O_TRUNC) or die $!;

$fh->print(decode_base64("/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBD
AQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCABkAGQDASIAAhEBAxEB/8QAFQABAQAA
AAAAAAAAAAAAAAAAAAf/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFgEBAQEAAAAAAAAAAAAAAAAAAAUH/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMR
AD8AjgDcUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//2Q==
"));


$fh->close;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接