Używanie atob JavaScript do dekodowania base64 nie dekoduje poprawnie ciągów utf-8

108

Używam window.atob()funkcji JavaScript do dekodowania ciągu zakodowanego w formacie base64 (w szczególności treści zakodowanej w formacie base64 z interfejsu API GitHub). Problem polega na tym, że otrzymuję z powrotem znaki zakodowane w ASCII (jak â¢zamiast ). Jak mogę poprawnie obsłużyć przychodzący strumień zakodowany w base64, aby został zdekodowany jako utf-8?

brandonscript
źródło
3
Połączona strona MDN zawiera akapit zaczynający się od frazy „Do użytku z ciągami znaków Unicode lub UTF-8”.
Pointy
1
Czy jesteś na węźle? Są lepsze rozwiązania niżatob
Bergi

Odpowiedzi:

274

Jest świetny artykuł na temat dokumentacji MDN Mozilli, który opisuje dokładnie ten problem:

„Problem z Unicode” Ponieważ DOMStrings to ciągi zakodowane w 16-bitowym kodowaniu, w większości przeglądarek wywołanie window.btoaciągu Unicode spowoduje a, Character Out Of Range exceptionjeśli znak przekracza zakres 8-bitowego bajtu (0x00 ~ 0xFF). Istnieją dwie możliwe metody rozwiązania tego problemu:

  • pierwszym jest ucieczka od całego ciągu (za pomocą UTF-8, zobacz encodeURIComponent), a następnie zakodowanie go;
  • drugi polega na przekonwertowaniu UTF-16 DOMStringna tablicę znaków UTF-8, a następnie zakodowaniu.

Uwaga na temat poprzednich rozwiązań: artykuł MDN pierwotnie sugerował użycie unescapei escaperozwiązanie Character Out Of Rangeproblemu wyjątku, ale od tego czasu są one przestarzałe. Niektóre inne odpowiedzi tutaj sugerują pracy wokół to ze decodeURIComponenti encodeURIComponentta okazała się zawodna i nieprzewidywalne. Najnowsza aktualizacja tej odpowiedzi wykorzystuje nowoczesne funkcje JavaScript w celu zwiększenia szybkości i modernizacji kodu.

Jeśli chcesz zaoszczędzić trochę czasu, możesz również rozważyć skorzystanie z biblioteki:

Kodowanie 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=="

Dekodowanie 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"

Rozwiązanie sprzed 2018 r. (Funkcjonalne i prawdopodobnie lepsze wsparcie dla starszych przeglądarek, nieaktualne)

Oto aktualne zalecenie, bezpośrednio z MDN, z dodatkową kompatybilnością z TypeScript przez @ 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))
    }))
}

b64EncodeUnicode('✓ à la mode') // "4pyTIMOgIGxhIG1vZGU="
b64EncodeUnicode('\n') // "Cg=="

// 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(''))
}

b64DecodeUnicode('4pyTIMOgIGxhIG1vZGU=') // "✓ à la mode"
b64DecodeUnicode('Cg==') // "\n"

Oryginalne rozwiązanie (przestarzałe)

To używane escapei unescape(które są teraz przestarzałe, chociaż nadal działa we wszystkich nowoczesnych przeglądarkach):

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"

I ostatnia rzecz: po raz pierwszy napotkałem ten problem podczas wywoływania interfejsu API GitHub. Aby to działało poprawnie w (mobilnym) Safari, musiałem usunąć całą białą przestrzeń ze źródła base64, zanim mogłem nawet zdekodować źródło. Czy jest to nadal aktualne w 2017 roku, nie wiem:

function b64_to_utf8( str ) {
    str = str.replace(/\s/g, '');    
    return decodeURIComponent(escape(window.atob( str )));
}
brandonscript
źródło
1
w3schools.com/jsref/jsref_unescape.asp "Funkcja unescape () została wycofana w JavaScript w wersji 1.5. Zamiast tego użyj decodeURI () lub decodeURIComponent ()."
Tedd Hansen,
1
Uratowałeś mi dni, bracie
panie Neo,
2
Aktualizacja: Rozwiązanie nr 1 w MDN "Problem z Unicode" został naprawiony, b64DecodeUnicode('4pyTIMOgIGxhIG1vZGU=');teraz poprawnie wyświetla "✓ à la mode"
weeix
2
Innym sposobem dekodowania byłby decodeURIComponent(atob('4pyTIMOgIGxhIG1vZGU=').split('').map(x => '%' + x.charCodeAt(0).toString(16)).join('')) nie najbardziej wydajny kod, ale taki właśnie jest.
daniel.gindi
2
return String.fromCharCode(parseInt(p1, 16));mieć zgodność z TypeScript.
Martin Schneider,
21

Rzeczy się zmieniają. Metody ucieczki / unescape zostały wycofane.

Możesz zakodować ciąg znaków URI przed zakodowaniem go w formacie Base64. Zauważ, że to nie tworzy danych zakodowanych w formacie Base64 UTF8, ale raczej dane zakodowane w formacie Base64 zakodowane w adresie URL. Obie strony muszą zgodzić się na to samo kodowanie.

Zobacz działający przykład tutaj: http://codepen.io/anon/pen/PZgbPW

// encode string
var base64 = window.btoa(encodeURIComponent('€ 你好 æøåÆØÅ'));
// decode string
var str = decodeURIComponent(window.atob(tmp));
// str is now === '€ 你好 æøåÆØÅ'

W przypadku problemu OP, biblioteka innej firmy, taka jak js-base64, powinna rozwiązać problem.

Tedd Hansen
źródło
1
Chciałbym zaznaczyć, że nie tworzysz base64 ciągu wejściowego, ale jego zakodowany komponent. Więc jeśli wyślesz go, druga strona nie będzie mogła go zdekodować jako „base64” i uzyskać oryginalnego ciągu
Riccardo Galli
3
Masz rację, zaktualizowałem tekst, aby to zaznaczyć. Dzięki. Alternatywą wydaje się być samodzielne implementowanie base64 przy użyciu biblioteki innej firmy (takiej jak js-base64) lub otrzymywanie komunikatu „Błąd: nie udało się wykonać„ btoa ”w„ oknie ”: ciąg do zakodowania zawiera znaki spoza zakresu Latin1. "
Tedd Hansen
14

Jeśli bardziej interesuje Cię traktowanie łańcuchów jako bajtów, możesz użyć następujących funkcji

function u_atob(ascii) {
    return Uint8Array.from(atob(ascii), c => c.charCodeAt(0));
}

function u_btoa(buffer) {
    var binary = [];
    var bytes = new Uint8Array(buffer);
    for (var i = 0, il = bytes.byteLength; i < il; i++) {
        binary.push(String.fromCharCode(bytes[i]));
    }
    return btoa(binary.join(''));
}


// example, it works also with astral plane characters such as '𝒞'
var encodedString = new TextEncoder().encode('✓');
var base64String = u_btoa(encodedString);
console.log('✓' === new TextDecoder().decode(u_atob(base64String)))
Riccardo Galli
źródło
1
Dzięki. Twoja odpowiedź była kluczowa i pomogła mi w tym, co zajęło mi wiele godzin przez wiele dni. +1. stackoverflow.com/a/51814273/470749
Ryan
Aby uzyskać znacznie szybsze i bardziej wydajne rozwiązanie w różnych przeglądarkach (ale zasadniczo te same dane wyjściowe), zobacz stackoverflow.com/a/53433503/5601591
Jack Giffin
u_atob i u_btoa korzystają z funkcji dostępnych w każdej przeglądarce od IE10 (2012), dla mnie wygląda solidnie (jeśli odwołujesz się do TextEncodera, to tylko przykład)
Riccardo Galli
5

Oto zaktualizowane rozwiązanie 2018, opisane w zasobach programistycznych Mozilli

ABY KODOWAĆ Z UNICODE DO B64

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=="

ODKODOWAĆ OD B64 DO UNICODE

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"
Manuel G.
źródło
5

Cały artykuł, który działa dla mnie: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding

Część, w której kodujemy z Unicode / UTF-8, to

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"

Jest to obecnie jedna z najczęściej stosowanych metod.

rika
źródło
3

Przypuszczam, że można chcieć rozwiązania, które tworzy powszechnie używany identyfikator URI base64. Odwiedź witrynę, data:text/plain;charset=utf-8;base64,4pi44pi54pi64pi74pi84pi+4pi/aby zobaczyć demonstrację (skopiuj uri danych, otwórz nową kartę, wklej identyfikator URI danych w pasku adresu, a następnie naciśnij klawisz Enter, aby przejść do strony). Pomimo faktu, że ten identyfikator URI jest zakodowany w base64, przeglądarka nadal jest w stanie rozpoznać wysokie punkty kodowe i poprawnie je zdekodować. Zminimalizowany koder + dekoder ma 1058 bajtów (+ Gzip → 589 bajtów)

!function(e){"use strict";function h(b){var a=b.charCodeAt(0);if(55296<=a&&56319>=a)if(b=b.charCodeAt(1),b===b&&56320<=b&&57343>=b){if(a=1024*(a-55296)+b-56320+65536,65535<a)return d(240|a>>>18,128|a>>>12&63,128|a>>>6&63,128|a&63)}else return d(239,191,189);return 127>=a?inputString:2047>=a?d(192|a>>>6,128|a&63):d(224|a>>>12,128|a>>>6&63,128|a&63)}function k(b){var a=b.charCodeAt(0)<<24,f=l(~a),c=0,e=b.length,g="";if(5>f&&e>=f){a=a<<f>>>24+f;for(c=1;c<f;++c)a=a<<6|b.charCodeAt(c)&63;65535>=a?g+=d(a):1114111>=a?(a-=65536,g+=d((a>>10)+55296,(a&1023)+56320)):c=0}for(;c<e;++c)g+="\ufffd";return g}var m=Math.log,n=Math.LN2,l=Math.clz32||function(b){return 31-m(b>>>0)/n|0},d=String.fromCharCode,p=atob,q=btoa;e.btoaUTF8=function(b,a){return q((a?"\u00ef\u00bb\u00bf":"")+b.replace(/[\x80-\uD7ff\uDC00-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]?/g,h))};e.atobUTF8=function(b,a){a||"\u00ef\u00bb\u00bf"!==b.substring(0,3)||(b=b.substring(3));return p(b).replace(/[\xc0-\xff][\x80-\xbf]*/g,k)}}(""+void 0==typeof global?""+void 0==typeof self?this:self:global)

Poniżej znajduje się kod źródłowy użyty do jego wygenerowania.

var fromCharCode = String.fromCharCode;
var btoaUTF8 = (function(btoa, replacer){"use strict";
    return function(inputString, BOMit){
        return btoa((BOMit ? "\xEF\xBB\xBF" : "") + inputString.replace(
            /[\x80-\uD7ff\uDC00-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]?/g, replacer
        ));
    }
})(btoa, function(nonAsciiChars){"use strict";
    // make the UTF string into a binary UTF-8 encoded string
    var point = nonAsciiChars.charCodeAt(0);
    if (point >= 0xD800 && point <= 0xDBFF) {
        var nextcode = nonAsciiChars.charCodeAt(1);
        if (nextcode !== nextcode) // NaN because string is 1 code point long
            return fromCharCode(0xef/*11101111*/, 0xbf/*10111111*/, 0xbd/*10111101*/);
        // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
        if (nextcode >= 0xDC00 && nextcode <= 0xDFFF) {
            point = (point - 0xD800) * 0x400 + nextcode - 0xDC00 + 0x10000;
            if (point > 0xffff)
                return fromCharCode(
                    (0x1e/*0b11110*/<<3) | (point>>>18),
                    (0x2/*0b10*/<<6) | ((point>>>12)&0x3f/*0b00111111*/),
                    (0x2/*0b10*/<<6) | ((point>>>6)&0x3f/*0b00111111*/),
                    (0x2/*0b10*/<<6) | (point&0x3f/*0b00111111*/)
                );
        } else return fromCharCode(0xef, 0xbf, 0xbd);
    }
    if (point <= 0x007f) return nonAsciiChars;
    else if (point <= 0x07ff) {
        return fromCharCode((0x6<<5)|(point>>>6), (0x2<<6)|(point&0x3f));
    } else return fromCharCode(
        (0xe/*0b1110*/<<4) | (point>>>12),
        (0x2/*0b10*/<<6) | ((point>>>6)&0x3f/*0b00111111*/),
        (0x2/*0b10*/<<6) | (point&0x3f/*0b00111111*/)
    );
});

Następnie, aby zdekodować dane base64, pobierz dane HTTP jako identyfikator URI danych lub użyj poniższej funkcji.

var clz32 = Math.clz32 || (function(log, LN2){"use strict";
    return function(x) {return 31 - log(x >>> 0) / LN2 | 0};
})(Math.log, Math.LN2);
var fromCharCode = String.fromCharCode;
var atobUTF8 = (function(atob, replacer){"use strict";
    return function(inputString, keepBOM){
        inputString = atob(inputString);
        if (!keepBOM && inputString.substring(0,3) === "\xEF\xBB\xBF")
            inputString = inputString.substring(3); // eradicate UTF-8 BOM
        // 0xc0 => 0b11000000; 0xff => 0b11111111; 0xc0-0xff => 0b11xxxxxx
        // 0x80 => 0b10000000; 0xbf => 0b10111111; 0x80-0xbf => 0b10xxxxxx
        return inputString.replace(/[\xc0-\xff][\x80-\xbf]*/g, replacer);
    }
})(atob, function(encoded){"use strict";
    var codePoint = encoded.charCodeAt(0) << 24;
    var leadingOnes = clz32(~codePoint);
    var endPos = 0, stringLen = encoded.length;
    var result = "";
    if (leadingOnes < 5 && stringLen >= leadingOnes) {
        codePoint = (codePoint<<leadingOnes)>>>(24+leadingOnes);
        for (endPos = 1; endPos < leadingOnes; ++endPos)
            codePoint = (codePoint<<6) | (encoded.charCodeAt(endPos)&0x3f/*0b00111111*/);
        if (codePoint <= 0xFFFF) { // BMP code point
          result += fromCharCode(codePoint);
        } else if (codePoint <= 0x10FFFF) {
          // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
          codePoint -= 0x10000;
          result += fromCharCode(
            (codePoint >> 10) + 0xD800,  // highSurrogate
            (codePoint & 0x3ff) + 0xDC00 // lowSurrogate
          );
        } else endPos = 0; // to fill it in with INVALIDs
    }
    for (; endPos < stringLen; ++endPos) result += "\ufffd"; // replacement character
    return result;
});

Zaletą bycia bardziej standardowym jest to, że ten koder i ten dekoder mają szersze zastosowanie, ponieważ mogą być używane jako prawidłowy adres URL, który wyświetla się poprawnie. Przestrzegać.

(function(window){
    "use strict";
    var sourceEle = document.getElementById("source");
    var urlBarEle = document.getElementById("urlBar");
    var mainFrameEle = document.getElementById("mainframe");
    var gotoButton = document.getElementById("gotoButton");
    var parseInt = window.parseInt;
    var fromCodePoint = String.fromCodePoint;
    var parse = JSON.parse;
    
    function unescape(str){
        return str.replace(/\\u[\da-f]{0,4}|\\x[\da-f]{0,2}|\\u{[^}]*}|\\[bfnrtv"'\\]|\\0[0-7]{1,3}|\\\d{1,3}/g, function(match){
          try{
            if (match.startsWith("\\u{"))
              return fromCodePoint(parseInt(match.slice(2,-1),16));
            if (match.startsWith("\\u") || match.startsWith("\\x"))
              return fromCodePoint(parseInt(match.substring(2),16));
            if (match.startsWith("\\0") && match.length > 2)
              return fromCodePoint(parseInt(match.substring(2),8));
            if (/^\\\d/.test(match)) return fromCodePoint(+match.slice(1));
          }catch(e){return "\ufffd".repeat(match.length)}
          return parse('"' + match + '"');
        });
    }
    
    function whenChange(){
      try{ urlBarEle.value = "data:text/plain;charset=UTF-8;base64," + btoaUTF8(unescape(sourceEle.value), true);
      } finally{ gotoURL(); }
    }
    sourceEle.addEventListener("change",whenChange,{passive:1});
    sourceEle.addEventListener("input",whenChange,{passive:1});
    
    // IFrame Setup:
    function gotoURL(){mainFrameEle.src = urlBarEle.value}
    gotoButton.addEventListener("click", gotoURL, {passive: 1});
    function urlChanged(){urlBarEle.value = mainFrameEle.src}
    mainFrameEle.addEventListener("load", urlChanged, {passive: 1});
    urlBarEle.addEventListener("keypress", function(evt){
      if (evt.key === "enter") evt.preventDefault(), urlChanged();
    }, {passive: 1});
    
        
    var fromCharCode = String.fromCharCode;
    var btoaUTF8 = (function(btoa, replacer){
		    "use strict";
        return function(inputString, BOMit){
        	return btoa((BOMit?"\xEF\xBB\xBF":"") + inputString.replace(
        		/[\x80-\uD7ff\uDC00-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]?/g, replacer
    		));
    	}
    })(btoa, function(nonAsciiChars){
		"use strict";
    	// make the UTF string into a binary UTF-8 encoded string
    	var point = nonAsciiChars.charCodeAt(0);
    	if (point >= 0xD800 && point <= 0xDBFF) {
    		var nextcode = nonAsciiChars.charCodeAt(1);
    		if (nextcode !== nextcode) { // NaN because string is 1code point long
    			return fromCharCode(0xef/*11101111*/, 0xbf/*10111111*/, 0xbd/*10111101*/);
    		}
    		// https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
    		if (nextcode >= 0xDC00 && nextcode <= 0xDFFF) {
    			point = (point - 0xD800) * 0x400 + nextcode - 0xDC00 + 0x10000;
    			if (point > 0xffff) {
    				return fromCharCode(
    					(0x1e/*0b11110*/<<3) | (point>>>18),
    					(0x2/*0b10*/<<6) | ((point>>>12)&0x3f/*0b00111111*/),
    					(0x2/*0b10*/<<6) | ((point>>>6)&0x3f/*0b00111111*/),
    					(0x2/*0b10*/<<6) | (point&0x3f/*0b00111111*/)
    				);
    			}
    		} else {
    			return fromCharCode(0xef, 0xbf, 0xbd);
    		}
    	}
    	if (point <= 0x007f) { return inputString; }
    	else if (point <= 0x07ff) {
    		return fromCharCode((0x6<<5)|(point>>>6), (0x2<<6)|(point&0x3f/*00111111*/));
    	} else {
    		return fromCharCode(
    			(0xe/*0b1110*/<<4) | (point>>>12),
    			(0x2/*0b10*/<<6) | ((point>>>6)&0x3f/*0b00111111*/),
    			(0x2/*0b10*/<<6) | (point&0x3f/*0b00111111*/)
    		);
    	}
    });
    setTimeout(whenChange, 0);
})(window);
img:active{opacity:0.8}
<center>
<textarea id="source" style="width:66.7vw">Hello \u1234 W\186\0256ld!
Enter text into the top box. Then the URL will update automatically.
</textarea><br />
<div style="width:66.7vw;display:inline-block;height:calc(25vw + 1em + 6px);border:2px solid;text-align:left;line-height:1em">
<input id="urlBar" style="width:calc(100% - 1em - 13px)" /><img id="gotoButton" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABsAAAAeCAMAAADqx5XUAAAAclBMVEX///9NczZ8e32ko6fDxsU/fBoSQgdFtwA5pAHVxt+7vLzq5ex23y4SXABLiiTm0+/c2N6DhoQ6WSxSyweVlZVvdG/Uz9aF5kYlbwElkwAggACxs7Jl3hX07/cQbQCar5SU9lRntEWGum+C9zIDHwCGnH5IvZAOAAABmUlEQVQoz7WS25acIBBFkRLkIgKKtOCttbv//xdDmTGZzHv2S63ltuBQQP4rdRiRUP8UK4wh6nVddQwj/NtDQTvac8577zTQb72zj65/876qqt7wykU6/1U6vFEgjE1mt/5LRqrpu7oVsn0sjZejMfxR3W/yLikqAFcUx93YxLmZGOtElmEu6Ufd9xV3ZDTGcEvGLbMk0mHHlUSvS5svCwS+hVL8loQQyfpI1Ay8RF/xlNxcsTchGjGDIuBG3Ik7TMyNxn8m0TSnBAK6Z8UZfp3IbAonmJvmsEACum6aNv7B0CnvpezDcNhw9XWsuAr7qnRg6dABmeM4dTgn/DZdXWs3LMspZ1KDMt1kcPJ6S1icWNp2qaEmjq6myx7jbQK3VKItLJaW5FR+cuYlRhYNKzGa9vF4vM5roLW3OSVjkmiGJrPhUq301/16pVKZRGFYWjTP50spTxBN5Z4EKnSonruk+n4tUokv1aJSEl/MLZU90S3L6/U6o0J142iQVp3HcZxKSo8LfkNRCtJaKYFSRX7iaoAAUDty8wvWYR6HJEepdwAAAABJRU5ErkJggg==" style="width:calc(1em + 4px);line-height:1em;vertical-align:-40%;cursor:pointer" />
<iframe id="mainframe" style="width:66.7vw;height:25vw" frameBorder="0"></iframe>
</div>
</center>

Oprócz tego, że są bardzo znormalizowane, powyższe fragmenty kodu są również bardzo szybkie. Zamiast pośredniego łańcucha sukcesji, w którym dane muszą być wielokrotnie konwertowane między różnymi formami (tak jak w odpowiedzi Riccardo Galli), powyższy fragment kodu jest tak bezpośredni, jak to wykonalne. Używa tylko jednego prostego szybkiego String.prototype.replacewywołania do przetwarzania danych podczas kodowania i tylko jednego do dekodowania danych podczas dekodowania. Kolejną zaletą jest to, że (szczególnie w przypadku dużych ciągów znaków) String.prototype.replacepozwala przeglądarce automatycznie obsługiwać podstawowe zarządzanie pamięcią polegającą na zmianie rozmiaru ciągu, co prowadzi do znacznego wzrostu wydajności, szczególnie w wiecznie zielonych przeglądarkach, takich jak Chrome i Firefox, które mocno optymalizująString.prototype.replace. Wreszcie wisienką na torcie jest to, że dla użytkowników skryptów łacińskich exclūsīvō, ciągi znaków, które nie zawierają żadnych punktów kodowych powyżej 0x7f, są wyjątkowo szybkie w przetwarzaniu, ponieważ ciąg pozostaje niezmieniony przez algorytm zastępujący.

Utworzyłem repozytorium github dla tego rozwiązania pod adresem https://github.com/anonyco/BestBase64EncoderDecoder/

Jack Giffin
źródło
Czy możesz wyjaśnić, co masz na myśli, mówiąc „sposób stworzony przez użytkownika”, a „możliwy do zinterpretowania przez przeglądarkę”? Jaka jest wartość dodana korzystania z tego rozwiązania w porównaniu, powiedzmy, z zaleceniami Mozilli?
brandonscript
@brandonscript Mozilla różni się od MDN. MDN to treści tworzone przez użytkowników. Strona w MDN, która zaleca Twoje rozwiązanie, była treścią utworzoną przez użytkowników, a nie treścią utworzoną przez dostawcę przeglądarki.
Jack Giffin
Czy Twój dostawca rozwiązania został utworzony? Proponuję więc przypisać pochodzenie. Jeśli nie, to jest również tworzony przez użytkowników i nie różni się od odpowiedzi MDN?
brandonscript
@brandonscript Słuszna uwaga. Masz rację. Usunąłem ten fragment tekstu. Zobacz też demo, które dodałem.
Jack Giffin
0

Drobne poprawki, unescape i escape są przestarzałe, więc:

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

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


function b64_to_utf8( str ) {
    str = str.replace(/\s/g, '');    
    return decodeURIComponent(encodeURIComponent(window.atob(str)));
}
Darkves
źródło
2
Wygląda na to, że link do dokumentu jest teraz inny niż ten, co sugeruje rozwiązanie regex do zarządzania nim.
brandonscript
2
To nie zadziała, ponieważ encodeURIComponentjest odwrotnością decodeURIComponent, tj. Po prostu cofnie konwersję. Zobacz stackoverflow.com/a/31412163/1534459, aby uzyskać świetne wyjaśnienie, co się dzieje z escapei unescape.
bodo
1
@canaaerus Nie rozumiem twojego komentarza? escape i unescape są przestarzałe, po prostu zamieniam je na funkcję [dekodowanie | kodowanie] URIComponent :-) Wszystko działa dobrze. Przeczytaj najpierw pytanie
Darkves
1
@Darkves: Powodem, dla którego encodeURIComponentjest używany, jest poprawna obsługa (całego zakresu) ciągów znaków Unicode. Czyli np. window.btoa(decodeURIComponent(encodeURIComponent('€')))Daje Error: String contains an invalid characterbo to to samo co window.btoa('€')i btoanie potrafi zakodować .
bodo
2
@Darkves: Tak, zgadza się. Ale nie możesz zamienić ucieczki z EncodeURIComponent i unescape z DecodeURIComponent, ponieważ Encode i metody ucieczki nie robią tego samego. To samo z dekodowaniem i unescape. Przy okazji, pierwotnie popełniłem ten sam błąd. Należy zauważyć, że jeśli weźmiesz ciąg, UriEncode go, a następnie UriDecode, otrzymasz z powrotem ten sam ciąg, który wprowadziłeś. Więc zrobienie tego byłoby nonsensem. Kiedy usuwasz napisy zakodowane za pomocą encodeURIComponent, nie otrzymujesz z powrotem tego samego ciągu, który wprowadziłeś, więc dlatego z escape / unescape działa, ale nie z twoim.
Stefan Steiger,
0

Oto kilka przyszłych kodów dla przeglądarek, których może brakować escape/unescape(). Pamiętaj, że IE 9 i starsze nie obsługują atob/btoa(), więc musisz użyć dla nich niestandardowych funkcji base64.

// Polyfill for escape/unescape
if( !window.unescape ){
    window.unescape = function( s ){
        return s.replace( /%([0-9A-F]{2})/g, function( m, p ) {
            return String.fromCharCode( '0x' + p );
        } );
    };
}
if( !window.escape ){
    window.escape = function( s ){
        var chr, hex, i = 0, l = s.length, out = '';
        for( ; i < l; i ++ ){
            chr = s.charAt( i );
            if( chr.search( /[A-Za-z0-9\@\*\_\+\-\.\/]/ ) > -1 ){
                out += chr; continue; }
            hex = s.charCodeAt( i ).toString( 16 );
            out += '%' + ( hex.length % 2 != 0 ? '0' : '' ) + hex;
        }
        return out;
    };
}

// Base64 encoding of UTF-8 strings
var utf8ToB64 = function( s ){
    return btoa( unescape( encodeURIComponent( s ) ) );
};
var b64ToUtf8 = function( s ){
    return decodeURIComponent( escape( atob( s ) ) );
};

Bardziej obszerny przykład kodowania i dekodowania UTF-8 można znaleźć tutaj: http://jsfiddle.net/47zwb41o/

Beejor
źródło
-1

w tym powyższe rozwiązanie, jeśli nadal napotykasz problem, spróbuj jak poniżej, Rozważ przypadek, w którym ucieczka nie jest obsługiwana dla TS.

blob = new Blob(["\ufeff", csv_content]); // this will make symbols to appears in excel 

dla csv_content możesz spróbować jak poniżej.

function b64DecodeUnicode(str: any) {        
        return decodeURIComponent(atob(str).split('').map((c: any) => {
            return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
        }).join(''));
    }
Diwakar
źródło