B window.atob a phân tích

Example

Decode a base-64 encoded string:

let text = "Hello World!";
let encoded = window.btoa(text);
let decoded = window.atob(encoded);

Try it Yourself »


Definition and Usage

The atob() method decodes a base-64 encoded string.

Note

The atob() method decodes a string that has been encoded by the btoa() method.

See Also:

The btoa() Method.


Syntax

Parameters

Parameter Description
encoded Required.
The string to be decoded.

Return Value

Type Description
A string The decoded string.

Browser Support

atob() is supported in all browsers:

Chrome IE Edge Firefox Safari Opera
Yes 10-11 Yes Yes Yes Yes

Vấn đề Unicode

Mặc dù JavaScript (ECMAScript) đã trưởng thành, sự mong manh của mã hóa Base64, ASCII và Unicode đã gây ra rất nhiều vấn đề đau đầu (phần lớn là trong lịch sử của câu hỏi này).

Hãy xem xét ví dụ sau:

const ok = "a";
console.log(ok.codePointAt(0).toString(16)); //   61: occupies < 1 byte

const notOK = "✓"
console.log(notOK.codePointAt(0).toString(16)); // 2713: occupies > 1 byte

console.log(btoa(ok));    // YQ==
console.log(btoa(notOK)); // error

Tại sao chúng ta gặp phải điều này?

Base64, theo thiết kế, mong đợi dữ liệu nhị phân làm đầu vào của nó. Về chuỗi JavaScript, điều này có nghĩa là các chuỗi trong đó mỗi ký tự chỉ chiếm một byte. Vì vậy, nếu bạn truyền một chuỗi vào btoa () chứa các ký tự chiếm nhiều hơn một byte, bạn sẽ gặp lỗi, vì đây không được coi là dữ liệu nhị phân.

Nguồn: MDN (2021)

Bài báo MDN ban đầu cũng đề cập đến bản chất bị hỏng của window.btoa.atob, từ đó đã được sửa chữa trong ECMAScript hiện đại. Bài báo MDN ban đầu, hiện đã chết giải thích:

"Vấn đề Unicode" Vì DOMStrings là các chuỗi được mã hóa 16 bit, trong hầu hết các trình duyệt gọi window.btoachuỗi Unicode sẽ gây ra Character Out Of Range exceptionnếu một ký tự vượt quá phạm vi của byte 8 bit (0x00 ~ 0xFF).


Giải pháp với khả năng tương tác nhị phân

(Tiếp tục cuộn để tìm giải pháp ASCII base64)

Nguồn: MDN (2021)

Giải pháp được đề xuất bởi MDN là thực sự mã hóa đến và từ một biểu diễn chuỗi nhị phân:

Mã hóa UTF8 ⇢ binary

// convert a Unicode string to a string in which
// each 16-bit unit occupies only one byte
function toBinary(string) {
  const codeUnits = new Uint16Array(string.length);
  for (let i = 0; i < codeUnits.length; i++) {
    codeUnits[i] = string.charCodeAt(i);
  }
  return btoa(String.fromCharCode(...new Uint8Array(codeUnits.buffer)));
}

// a string that contains characters occupying > 1 byte
let encoded = toBinary("✓ à la mode") // "EycgAOAAIABsAGEAIABtAG8AZABlAA=="

Giải mã nhị phân ⇢ UTF-8

function fromBinary(encoded) {
  binary = atob(encoded)
  const bytes = new Uint8Array(binary.length);
  for (let i = 0; i < bytes.length; i++) {
    bytes[i] = binary.charCodeAt(i);
  }
  return String.fromCharCode(...new Uint16Array(bytes.buffer));
}

// our previous Base64-encoded string
let decoded = fromBinary(encoded) // "✓ à la mode"

Trường hợp điều này không thành công một chút, là bạn sẽ nhận thấy chuỗi được mã hóa EycgAOAAIABsAGEAIABtAG8AZABlAA==không còn khớp với chuỗi của giải pháp trước đó 4pyTIMOgIGxhIG1vZGU=. Điều này là do nó là một chuỗi được mã hóa nhị phân, không phải là một chuỗi được mã hóa UTF-8. Nếu điều này không quan trọng với bạn (tức là bạn không chuyển đổi các chuỗi được biểu thị trong UTF-8 từ một hệ thống khác), thì bạn nên thực hiện. Tuy nhiên, nếu bạn muốn duy trì chức năng UTF-8, bạn nên sử dụng giải pháp được mô tả bên dưới.


Giải pháp với khả năng tương tác ASCII base64

Toàn bộ lịch sử của câu hỏi này chỉ ra có bao nhiêu cách khác nhau mà chúng tôi đã phải giải quyết các hệ thống mã hóa bị hỏng trong nhiều năm. Mặc dù bài báo MDN ban đầu không còn tồn tại, giải pháp này vẫn được cho là giải pháp tốt hơn và thực hiện rất tốt việc giải quyết "Vấn đề Unicode" trong khi vẫn duy trì các chuỗi base64 văn bản thuần túy mà bạn có thể giải mã trên base64decode.org .

Có hai phương pháp khả thi để giải quyết vấn đề này:

  • cách đầu tiên là thoát toàn bộ chuỗi (với UTF-8, xem encodeURIComponent) và sau đó mã hóa nó;
  • cách thứ hai là chuyển đổi UTF-16 DOMStringthành mảng ký tự UTF-8 và sau đó mã hóa nó.

Lưu ý về các giải pháp trước đây: bài viết MDN ban đầu đề xuất sử dụng unescapeescapeđể giải quyết Character Out Of Rangevấn đề ngoại lệ, nhưng chúng đã không được dùng nữa. Một số câu trả lời khác ở đây đã gợi ý giải quyết vấn đề này với decodeURIComponentencodeURIComponent, điều này đã được chứng minh là không đáng tin cậy và không thể đoán trước được. Bản cập nhật gần đây nhất cho câu trả lời này sử dụng các hàm JavaScript hiện đại để cải thiện tốc độ và hiện đại hóa mã.

Nếu bạn đang cố gắng tiết kiệm thời gian cho mình, bạn cũng có thể cân nhắc sử dụng thư viện:

  • js-base64 (NPM, tuyệt vời cho Node.js)
  • base64-js

Mã hóa UTF8 ⇢ base64

    function b64EncodeUnicode(str) {
        // first we use encodeURIComponent to get percent-encoded UTF-8,
        // then we convert the percent encodings into raw bytes which
        // can be fed into btoa.
        return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
            function toSolidBytes(match, p1) {
                return String.fromCharCode('0x' + p1);
        }));
    }
    
    b64EncodeUnicode('✓ à la mode'); // "4pyTIMOgIGxhIG1vZGU="
    b64EncodeUnicode('\n'); // "Cg=="

Giải mã base64 ⇢ UTF8

    function b64DecodeUnicode(str) {
        // Going backwards: from bytestream, to percent-encoding, to original string.
        return decodeURIComponent(atob(str).split('').map(function(c) {
            return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
        }).join(''));
    }
    
    b64DecodeUnicode('4pyTIMOgIGxhIG1vZGU='); // "✓ à la mode"
    b64DecodeUnicode('Cg=='); // "\n"

(Tại sao chúng ta cần làm điều này? Thêm ('00' + c.charCodeAt(0).toString(16)).slice(-2)0 vào các chuỗi ký tự đơn, ví dụ: khi c == \n, các kết c.charCodeAt(0).toString(16)quả trả về a, buộc aphải được biểu diễn dưới dạng 0a).


Hỗ trợ TypeScript

Đây là giải pháp tương tự với một số khả năng tương thích TypeScript bổ sung (thông qua @ MA-Maddin):

// Encoding UTF8 ⇢ base64

function b64EncodeUnicode(str) {
    return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function(match, p1) {
        return String.fromCharCode(parseInt(p1, 16))
    }))
}

// Decoding base64 ⇢ UTF8

function b64DecodeUnicode(str) {
    return decodeURIComponent(Array.prototype.map.call(atob(str), function(c) {
        return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
    }).join(''))
}

Giải pháp đầu tiên (không được dùng nữa)

Điều này đã được sử dụng escapeunescape(hiện không được dùng nữa, mặc dù điều này vẫn hoạt động trong tất cả các trình duyệt hiện đại):

function utf8_to_b64( str ) {
    return window.btoa(unescape(encodeURIComponent( str )));
}

function b64_to_utf8( str ) {
    return decodeURIComponent(escape(window.atob( str )));
}

// Usage:
utf8_to_b64('✓ à la mode'); // "4pyTIMOgIGxhIG1vZGU="
b64_to_utf8('4pyTIMOgIGxhIG1vZGU='); // "✓ à la mode"

Và một điều cuối cùng: Lần đầu tiên tôi gặp sự cố này khi gọi API GitHub. Để điều này hoạt động bình thường trên Safari (Di động), tôi thực sự phải loại bỏ tất cả khoảng trắng khỏi nguồn base64 trước khi tôi thậm chí có thể giải mã nguồn. Liệu điều này có còn phù hợp vào năm 2021 hay không, tôi không biết:

function b64_to_utf8( str ) {
    str = str.replace(/\s/g, '');    
    return decodeURIComponent(escape(window.atob( str )));
}

330 hữu ích 5 bình luận chia sẻ