Cara Perbaiki Error Ketika Decoding dan Encoding Base64 Antar JavaScript Dan PHP

Ketika anda mencoba menyandikan string dalam javascript dan mendekodekannya di php. Masalahnya adalah data yg keluar selalu mendapatkan string 98% benar tetapi dengan beberapa karakter yang berbeda. Misalnya: (string pertama adalah string yang dicetak di kotak input). Dan itu bahkan dilakukan menggunakan browser Chrome dengan penyandian karakter utf-8 di header dan meta.

Dalam kasus yang terlihat pengkodean ke base64 tampaknya bukan masalah utama. String yang disandikan base64 sama di browser daripada di server. Jika saya base64-decode dalam javascript saya mendapatkan string asli tetapi jika saya decode dalam PHP saya mendapatkan string yang sedikit berbeda.

Masalahnya bukan pada penyandian di JS, atau penguraian kode di PHP. Masalahnya adalah ketika Anda mencoba menyalin dan menempelkan data biner dari sumber eksternal ke dalam formulir HTML. Pendekatan yang tepat adalah membuat sumber eksternal menyandikan data dalam base64 [atau pengkodean aman 7-bit lainnya] untuk memastikan bahwa data melewati sistem tanpa gangguan

Base64 adalah sekelompok skema pengkodean biner-ke-teks yang mewakili data biner dalam format string ASCII dengan menerjemahkannya ke dalam representasi base-64. Istilah Base64 berasal dari pengkodean transfer konten MIME tertentu.

Skema pengkodean Base64 biasanya digunakan ketika ada kebutuhan untuk mengkodekan data biner yang perlu disimpan dan ditransfer melalui media yang dirancang untuk menangani ASCII. Hal ini untuk memastikan bahwa data tetap utuh tanpa modifikasi selama transportasi. Base64 umumnya digunakan di sejumlah aplikasi termasuk email melalui MIME, dan menyimpan data kompleks dalam XML.

Salah satu aplikasi umum pengkodean Base64 di web adalah untuk mengkodekan data biner sehingga dapat dimasukkan dalam data: URL.

Dalam JavaScript ada dua fungsi masing-masing untuk decoding dan encoding string Base64:

  • btoa(): creates a Base64-encoded ASCII string from a "string" of binary data ("btoa" should be read as "binary to ASCII").
  • atob(): decodes a Base64-encoded string("atob" should be read as "ASCII to binary").

Perhatikan bahwa btoa() mengharapkan untuk melewatkan data biner, dan akan mengeluarkan pengecualian jika string yang diberikan berisi karakter apa pun yang representasi UTF-16 menempati lebih dari satu byte.

Peningkatan ukuran yang dikodekan

Setiap digit Base64 mewakili tepat 6 bit data. Jadi, tiga byte 8-bit dari string input/file biner (3x8 bit = 24 bit) dapat diwakili oleh empat digit Base64 6-bit (4x6 = 24 bit).

Ini berarti bahwa versi Base64 dari string atau file setidaknya berukuran 133% dari ukuran sumbernya (peningkatan ~33%). Peningkatannya mungkin lebih besar jika data yang dikodekan kecil. Misalnya, string "a" dengan panjang === 1 dikodekan menjadi "YQ==" dengan panjang === 4 — peningkatan 300%.

"Masalah Unicode"

Karena string JavaScript adalah string yang dikodekan 16-bit, di sebagian besar browser yang memanggil window.btoa pada string Unicode akan menyebabkan pengecualian Character Out Of Range jika karakter melebihi rentang karakter yang dikodekan ASCII 8-bit. Ada dua cara yang mungkin untuk menyelesaikan masalah ini:

  1. yang pertama adalah untuk keluar dari seluruh string dan kemudian menyandikannya;
  2. yang kedua adalah mengonversi string UTF-16 ke array karakter UTF-8 dan kemudian menyandikannya.


Solusi #1 Menggunakan fungsi btoa dan atob melalui javascript.

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

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

Penggunaan

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

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

Solusi lain yang mungkin tanpa menggunakan fungsi 'unescape' dan 'escape' yang sekarang sudah tidak digunakan lagi. Namun, alternatif ini tidak melakukan pengkodean base64 dari string input. Perhatikan perbedaan output utf8_to_b64 dan b64EncodeUnicode. Mengadopsi alternatif ini dapat menyebabkan masalah interoperabilitas dengan aplikasi lain.

function b64EncodeUnicode(str) {return btoa(encodeURIComponent(str));};

function UnicodeDecodeB64(str) {return decodeURIComponent(atob(str));};

Penggunaan

b64EncodeUnicode("✓ à la mode"); // "JUUyJTlDJTkzJTIwJUMzJUEwJTIwbGElMjBtb2Rl"

UnicodeDecodeB64("JUUyJTlDJTkzJTIwJUMzJUEwJTIwbGElMjBtb2Rl"); // "✓ à la mode"


Solusi #2 – menulis ulang atob() dan btoa() menggunakan TypedArrays dan UTF-8

Kode berikut juga berguna untuk mendapatkan ArrayBuffer dari string Base64 dan/atau sebaliknya (lihat di bawah).

function b64ToUint6 (nChr) { return nChr > 64 && nChr < 91 ? nChr - 65 : nChr > 96 && nChr < 123 ? nChr - 71 : nChr > 47 && nChr < 58 ? nChr + 4 : nChr === 43 ? 62 : nChr === 47 ? 63 : 0; } function base64DecToArr (sBase64, nBlocksSize) { var sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""), nInLen = sB64Enc.length, nOutLen = nBlocksSize ? Math.ceil((nInLen * 3 + 1 >> 2) / nBlocksSize) * nBlocksSize : nInLen * 3 + 1 >> 2, taBytes = new Uint8Array(nOutLen); for (var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) { nMod4 = nInIdx & 3; nUint24 |= b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 6 * (3 - nMod4); if (nMod4 === 3 || nInLen - nInIdx === 1) { for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) { taBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255; } nUint24 = 0; } } return taBytes; } /* Base64 string to array encoding */ function uint6ToB64 (nUint6) { return nUint6 < 26 ? nUint6 + 65 : nUint6 < 52 ? nUint6 + 71 : nUint6 < 62 ? nUint6 - 4 : nUint6 === 62 ? 43 : nUint6 === 63 ? 47 : 65; } function base64EncArr (aBytes) { var nMod3 = 2, sB64Enc = ""; for (var nLen = aBytes.length, nUint24 = 0, nIdx = 0; nIdx < nLen; nIdx++) { nMod3 = nIdx % 3; if (nIdx > 0 && (nIdx * 4 / 3) % 76 === 0) { sB64Enc += "\r\n"; } nUint24 |= aBytes[nIdx] << (16 >>> nMod3 & 24); if (nMod3 === 2 || aBytes.length - nIdx === 1) { sB64Enc += String.fromCodePoint(uint6ToB64(nUint24 >>> 18 & 63), uint6ToB64(nUint24 >>> 12 & 63), uint6ToB64(nUint24 >>> 6 & 63), uint6ToB64(nUint24 & 63)); nUint24 = 0; } } return sB64Enc.substr(0, sB64Enc.length - 2 + nMod3) + (nMod3 === 2 ? '' : nMod3 === 1 ? '=' : '=='); } /* UTF-8 array to JS string and vice versa */ function UTF8ArrToStr (aBytes) { var sView = ""; for (var nPart, nLen = aBytes.length, nIdx = 0; nIdx < nLen; nIdx++) { nPart = aBytes[nIdx]; sView += String.fromCodePoint( nPart > 251 && nPart < 254 && nIdx + 5 < nLen ? /* six bytes */ /* (nPart - 252 << 30) may be not so safe in ECMAScript! So...: */ (nPart - 252) * 1073741824 + (aBytes[++nIdx] - 128 << 24) + (aBytes[++nIdx] - 128 << 18) + (aBytes[++nIdx] - 128 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128 : nPart > 247 && nPart < 252 && nIdx + 4 < nLen ? /* five bytes */ (nPart - 248 << 24) + (aBytes[++nIdx] - 128 << 18) + (aBytes[++nIdx] - 128 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128 : nPart > 239 && nPart < 248 && nIdx + 3 < nLen ? /* four bytes */ (nPart - 240 << 18) + (aBytes[++nIdx] - 128 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128 : nPart > 223 && nPart < 240 && nIdx + 2 < nLen ? /* three bytes */ (nPart - 224 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128 : nPart > 191 && nPart < 224 && nIdx + 1 < nLen ? /* two bytes */ (nPart - 192 << 6) + aBytes[++nIdx] - 128 : /* nPart < 127 ? */ /* one byte */ nPart ); } return sView; } function strToUTF8Arr (sDOMStr) { var aBytes, nChr, nStrLen = sDOMStr.length, nArrLen = 0; /* mapping... */ for (var nMapIdx = 0; nMapIdx < nStrLen; nMapIdx++) { nChr = sDOMStr.codePointAt(nMapIdx); if (nChr > 65536) { nMapIdx++; } nArrLen += nChr < 0x80 ? 1 : nChr < 0x800 ? 2 : nChr < 0x10000 ? 3 : nChr < 0x200000 ? 4 : nChr < 0x4000000 ? 5 : 6; } aBytes = new Uint8Array(nArrLen); /* transcription... */ for (var nIdx = 0, nChrIdx = 0; nIdx < nArrLen; nChrIdx++) { nChr = sDOMStr.codePointAt(nChrIdx); if (nChr < 128) { /* one byte */ aBytes[nIdx++] = nChr; } else if (nChr < 0x800) { /* two bytes */ aBytes[nIdx++] = 192 + (nChr >>> 6); aBytes[nIdx++] = 128 + (nChr & 63); } else if (nChr < 0x10000) { /* three bytes */ aBytes[nIdx++] = 224 + (nChr >>> 12); aBytes[nIdx++] = 128 + (nChr >>> 6 & 63); aBytes[nIdx++] = 128 + (nChr & 63); } else if (nChr < 0x200000) { /* four bytes */ aBytes[nIdx++] = 240 + (nChr >>> 18); aBytes[nIdx++] = 128 + (nChr >>> 12 & 63); aBytes[nIdx++] = 128 + (nChr >>> 6 & 63); aBytes[nIdx++] = 128 + (nChr & 63); nChrIdx++; } else if (nChr < 0x4000000) { /* five bytes */ aBytes[nIdx++] = 248 + (nChr >>> 24); aBytes[nIdx++] = 128 + (nChr >>> 18 & 63); aBytes[nIdx++] = 128 + (nChr >>> 12 & 63); aBytes[nIdx++] = 128 + (nChr >>> 6 & 63); aBytes[nIdx++] = 128 + (nChr & 63); nChrIdx++; } else /* if (nChr <= 0x7fffffff) */ { /* six bytes */ aBytes[nIdx++] = 252 + (nChr >>> 30); aBytes[nIdx++] = 128 + (nChr >>> 24 & 63); aBytes[nIdx++] = 128 + (nChr >>> 18 & 63); aBytes[nIdx++] = 128 + (nChr >>> 12 & 63); aBytes[nIdx++] = 128 + (nChr >>> 6 & 63); aBytes[nIdx++] = 128 + (nChr & 63); nChrIdx++; } } return aBytes; }

Tests

/* Tests */


var sMyInput = "Base 64 \u2014 Mozilla Developer Network";

var aMyUTF8Input = strToUTF8Arr(sMyInput);

var sMyBase64 = base64EncArr(aMyUTF8Input);

alert(sMyBase64);


var aMyUTF8Output = base64DecToArr(sMyBase64);

var sMyOutput = UTF8ArrToStr(aMyUTF8Output);

alert(sMyOutput);


Fungsi ini memungkinkan kita untuk membuat juga uint8Arrays atau arrayBuffers dari string yang disandikan Base64:

// "Base 64 \u2014 Mozilla Developer Network"

var myArray = base64DecToArr("QmFzZSA2NCDigJQgTW96aWxsYSBEZXZlbG9wZXIgTmV0d29yaw==");

// "Base 64 \u2014 Mozilla Developer Network"

var myBuffer = base64DecToArr("QmFzZSA2NCDigJQgTW96aWxsYSBEZXZlbG9wZXIgTmV0d29yaw==").buffer;

alert(myBuffer.byteLength);


Catatan: Fungsi base64DecToArr(sBase64[, nBlocksSize]) mengembalikan uint8Array byte. Jika tujuan Anda adalah membuat buffer data mentah 16-bit / 32-bit / 64-bit, gunakan argumen nBlocksSize, yang merupakan jumlah byte yang harus dihasilkan oleh properti uint8Array.buffer.bytesLength kelipatan (1 atau dihilangkan untuk ASCII, string biner (yaitu, string di mana setiap karakter dalam string diperlakukan sebagai byte data biner) atau string yang disandikan UTF-8, 2 untuk string UTF-16, 4 untuk string UTF-32).

2 metode diatas merupakan cara saya untuk memperbaiki error encoding dan decoding ketika melakukannya dari bahasa PHP ke Javascript maupun sebaliknya. Dalam kasus saya, solusi pertama sudah dapat memperbaiki error yang saya alamai.

Posting Komentar