[解決済み] Javascriptのatobを使ってbase64をデコードすると、utf-8の文字列が正しくデコードされない
質問
私はJavascriptを使用しています。
window.atob()
関数を使って base64 エンコードした文字列 (具体的には GitHub API の内容を base64 でエンコードしたもの) をデコードします。問題は、ASCII エンコードされた文字が戻ってくることです (たとえば
â¢
ではなく
™
). どうすれば、受信したbase64エンコードされたストリームを適切に処理し、utf-8としてデコードできるようになりますか?
どのように解決するのですか?
ユニコード問題
JavaScript (ECMAScript) は成熟してきましたが、Base64、ASCII、そして Unicode エンコーディングのもろさは、多くの頭痛の種でした(その多くはこの質問の歴史にあります)。
次のような例を考えてみましょう。
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
なぜ、このようなことになるのでしょうか。
<ブロッククオートBase64は、設計上、バイナリデータを入力として想定しています。JavaScriptの文字列で言えば、これは各文字が1バイトしか占めない文字列を意味します。したがって、1バイト以上を占める文字を含む文字列をbtoa()に渡すと、バイナリデータとはみなされないため、エラーが発生します。
出典 MDN (2021)
MDN の元記事では、壊れた
window.btoa
と
.atob
が、現代のECMAScriptでは修正されている。オリジナルの、今は亡き MDN の記事で説明されています。
ユニコード問題。
以来
DOMString
は16ビットでエンコードされた文字列であり、ほとんどのブラウザで
window.btoa
ユニコード文字列に対して
Character Out Of Range exception
が8ビットバイトの範囲(0x00~0xFF)を超える場合。
バイナリ互換による解決策
(ASCII base64による解決策はスクロールしてください。)
出典 MDN (2021)
MDNが推奨する解決策は、バイナリ文字列表現との間で実際にエンコードすることです。
エンコーディング UTF8 ⇢ バイナリ
// 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=="
バイナリ ⇢ 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"
ここで少し失敗するのは、エンコードされた文字列である
EycgAOAAIABsAGEAIABtAG8AZABlAA==
という文字列は、もはや前の解決策の文字列
4pyTIMOgIGxhIG1vZGU=
. これは、UTF-8でエンコードされた文字列ではなく、バイナリでエンコードされた文字列であるためです。もしこれがあなたにとって問題でなければ(つまり、他のシステムからUTF-8で表現された文字列を変換するのでなければ)、そのままで大丈夫でしょう。しかし、UTF-8の機能を維持したいのであれば、以下で説明する解決策を使用したほうがよいでしょう。
ASCII base64の相互運用性を考慮した解決策
この質問の歴史全体から、長年にわたって壊れたエンコーディングシステムを回避するために、いかに多くの異なる方法が取られてきたかがわかります。オリジナルのMDN記事はもう存在しませんが、この解決策は間違いなくより良いもので、例えば、デコードできるプレーンテキストのbase64文字列を維持しながら、"The Unicode Problem" を解決する素晴らしい仕事をしています。 base64decode.org .
この問題を解決する方法として、2つの方法が考えられます。
- 1つ目は、文字列全体をエスケープする方法です(UTF-8の場合。
encodeURIComponent
を使用し、エンコードします。- 2つ目は、UTF-16を変換して
DOMString
をUTF-8の文字配列に変換し、エンコードします。
以前の解決策に関するメモ:MDN の記事では、もともと
unescape
と
escape
を解決するために
Character Out Of Range
例外の問題がありましたが、その後、非推奨となりました。他の回答では、この問題を回避するために
decodeURIComponent
と
encodeURIComponent
しかし、これは信頼性が低く、予測不可能であることが証明されています。この回答に対する最新の更新では、最新のJavaScript関数を使用して、速度を向上させ、コードを近代化しました。
時間を節約したいのであれば、ライブラリの利用を検討するのも手です。
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=="
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"
(なぜ、こんなことをする必要があるのでしょうか?
('00' + c.charCodeAt(0).toString(16)).slice(-2)
は1文字の文字列に0を付加します。
c == \n
は、その
c.charCodeAt(0).toString(16)
を返します。
a
強制的に
a
として表現されます。
0a
).
TypeScript対応
同じソリューションにTypeScriptの互換性を追加したものがこちらです(@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(''))
}
最初の解決策(非推奨)
これは、使用した
escape
と
unescape
(これは現在では非推奨ですが、すべてのモダンブラウザで動作します)。
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"
最後にもうひとつ。私がこの問題に最初に遭遇したのは、GitHub APIを呼び出したときでした。モバイルの)Safari で正しく動作させるためには、base64 のソースからすべての空白文字を削除する必要がありました。 前に ソースをデコードすることもできました。これが2021年にも当てはまるかどうかは分かりませんが。
function b64_to_utf8( str ) {
str = str.replace(/\s/g, '');
return decodeURIComponent(escape(window.atob( str )));
}
関連
-
HTML+CSS+JavaScriptで簡単な三目並べゲームを作成する。
-
vue+webrtc(Tencent cloud)ライブ機能の実践を実現するために
-
VUEグローバルフィルターの概念と留意点、基本的な使い方
-
Vueのクラススタイルの使い方の詳細
-
[解決済み] Web API エラー - このリクエストはブロックされました; コンテンツは HTTPS で提供されなければなりません
-
[解決済み】TypeErrorの解決方法。未定義またはヌルをオブジェクトに変換できない
-
[解決済み] PythonでUnicode(UTF-8)のファイル読み書きをする。
-
[解決済み】JavaScriptでBase64文字列からBLOBを作成する場合
-
[解決済み】PythonでUrlのUTF-8をデコードする。
-
[解決済み】PHPでUTF-8のCSVを出力して、Excelが正しく読めるようにするにはどうしたらいいですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
vueネットワークリクエストソリューション ネイティブネットワークリクエストとjsネットワークリクエストライブラリ
-
元のイベントが実行されなかった後に要素を追加するためのjQueryソリューション
-
vue ディレクティブ v-html と v-text
-
Vueのイベント処理とイベントモディファイアの解説
-
[解決済み】Uncaught SyntaxError: JSONの位置0に予期しないトークンuがあります。
-
[解決済み】ERROR エラーです。スイッチのname属性が指定されていないフォームコントロールの値アクセッサがない
-
[解決済み】 `string.split is not a function` というエラーの原因は何ですか?
-
[解決済み] ライブラリを使用せずに、javascriptでjwtトークンをデコードするにはどうすればよいですか?
-
[解決済み] HTMLのdata属性にJSONオブジェクトを格納する jQuery
-
[解決済み] DataURLからBlob?