【JS】Base64的url轉換

08 3月, 2019

【JS】Base64的url轉換





最近有需要將base64的字串放到url上當參數調用。
查了一下文檔
發現base64有定義說「不建議放到url」上。

後來實作後發現,的確放在網址上會產生出許多問題。
像是base64最後的「=」
這其實算是一個補充碼,也就是當長度沒滿足條件的時候,
會自動使用「=」填充用。

而且還有一些字符也會造成問題。
後來看了一些文章後。
大概理解,只要解決三個字符的問題即可。

並且在decode強制一定要加上urldecode
不然在url轉換或跳轉時,(譬如有透過fb做過水)
有的網站會自動encode你的參數,導致在解碼的時候有問題。




以下這種方式算是另類解法,缺點是只有自己能夠讀取
(由於轉換規則是自訂)

下面是js實現的轉換代碼:(js es6語法)


import Base64 from "./base64";
//網址使用base64的方案 By Leeson_Hsu
export default class Base64Url {
constructor() {
}
/**
* 傳入並轉為BASE64字串
* @param {*} sourceString 來源字串
*/
static encodeUrlBase64(sourceString) {
//使用暫時的變數來取代代入值,避免之後被覆寫
var base64String = "" + Base64.encode(JsonString);
console.log("replace!!");
//base64在URL會有下列三種符號會有問題
var replacements = {
"+": "."
, "=": "-"
, "/": "_"
};
//必須使用正則覆蓋,多重replace會有異常沒覆蓋的問題
var finalString = Base64Url.replace(base64String, replacements);
return finalString;
}
/**
* 傳入一個BASE64字串,並將其解開
* @param {*} base64String
*/
static decodeUrlBase64(base64String) {
//使用暫時的變數來取代代入值,避免之後被覆寫
var tmpString = "" + decodeURIComponent(base64String);
//base64在URL會有下列三種符號會有問題
var replacements = {
".": "+"
, "-": "="
, "_": "/"
};
//必須使用正則覆蓋,多重replace會有異常沒覆蓋的問題
var finalBase64 = Base64Url.replace(tmpString, replacements);
return Base64.decode(finalBase64);
}
/**
* 使用正則覆蓋,否則原始多重replace會造成解析過慢來不及覆蓋的問題
* @param {*} SourceString
* @param {*} replacements
*
* var replacements = {
"a": "d"
, "b": "e"
, "c": "f"
};
*/
static replace(SourceString, replacements) {
Array.prototype.each = function (trans) {
for (var i = 0; i < this.length; i++)
this[i] = trans(this[i], i, this);
return this;
};
Array.prototype.map = function (trans) {
return [].concat(this).each(trans);
};
RegExp.escape = function (str) {
return new String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
};
function properties(obj) {
var props = [];
for (var p in obj) props.push(p);
return props;
}
var regex = new RegExp(properties(replacements).map(RegExp.escape).join("|"), "g");
SourceString = SourceString.replace(regex, function ($0) { return replacements[$0]; });
return SourceString;
}
}
view raw base64.js hosted with ❤ by GitHub

以上採用的base64庫是使用這個github上的方式 (包裝成class)
https://gist.github.com/mikedamage/73711

不使用js內建的base64加解碼,是因為為了能更好兼容各個瀏覽器。
上述類可以兼容大部分瀏覽器。(需靠插件將es6降轉版本)




/**
*
* Base64 encode / decode
* http://www.webtoolkit.info
*
**/
export default class Base64 {
constructor() {
}
// public method for encoding
static encode(input) {
var output = "";
var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
var i = 0;
input = Base64._utf8_encode(input);
while (i < input.length) {
chr1 = input.charCodeAt(i++);
chr2 = input.charCodeAt(i++);
chr3 = input.charCodeAt(i++);
enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63;
if (isNaN(chr2)) {
enc3 = enc4 = 64;
//console.log("enc3 = enc4 = 64");
}
else if (isNaN(chr3)) {
enc4 = 64;
//console.log("enc4 = 64");
}
output = output +
Base64._keyStr.charAt(enc1) + Base64._keyStr.charAt(enc2) +
Base64._keyStr.charAt(enc3) + Base64._keyStr.charAt(enc4);
//console.log("output:"+output);
} // Whend
return output;
} // End Function encode
static encodeBase64Json(input) {
//Base64解碼
var jsonString = JSON.stringify(input);
var base64Json = Base64.encode(window.encodeURIComponent(jsonString));
console.log("[encodeBase64Json]=>" + base64Json);
return jsonObject;
}
// public method for decoding
static decode(input) {
var output = "";
var chr1, chr2, chr3;
var enc1, enc2, enc3, enc4;
var i = 0;
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
while (i < input.length) {
enc1 = Base64._keyStr.indexOf(input.charAt(i++));
enc2 = Base64._keyStr.indexOf(input.charAt(i++));
enc3 = Base64._keyStr.indexOf(input.charAt(i++));
enc4 = Base64._keyStr.indexOf(input.charAt(i++));
chr1 = (enc1 << 2) | (enc2 >> 4);
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
chr3 = ((enc3 & 3) << 6) | enc4;
output = output + String.fromCharCode(chr1);
if (enc3 != 64) {
output = output + String.fromCharCode(chr2);
}
if (enc4 != 64) {
output = output + String.fromCharCode(chr3);
}
} // Whend
output = Base64._utf8_decode(output);
return output;
} // End Function decode
// private method for UTF-8 encoding
static _utf8_encode(string) {
var utftext = "";
string = string.replace(/\r\n/g, "\n");
for (var n = 0; n < string.length; n++) {
var c = string.charCodeAt(n);
if (c < 128) {
utftext += String.fromCharCode(c);
}
else if ((c > 127) && (c < 2048)) {
utftext += String.fromCharCode((c >> 6) | 192);
utftext += String.fromCharCode((c & 63) | 128);
}
else {
utftext += String.fromCharCode((c >> 12) | 224);
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
utftext += String.fromCharCode((c & 63) | 128);
}
} // Next n
return utftext;
} // End Function _utf8_encode
// private method for UTF-8 decoding
static _utf8_decode(utftext) {
var string = "";
var i = 0;
var c, c1, c2, c3;
c = c1 = c2 = 0;
while (i < utftext.length) {
c = utftext.charCodeAt(i);
if (c < 128) {
string += String.fromCharCode(c);
i++;
}
else if ((c > 191) && (c < 224)) {
c2 = utftext.charCodeAt(i + 1);
string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
i += 2;
}
else {
c2 = utftext.charCodeAt(i + 1);
c3 = utftext.charCodeAt(i + 2);
string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
i += 3;
}
} // Whend
return string;
} // End Function _utf8_decode
}
Base64._keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
view raw base64Source.js hosted with ❤ by GitHub

後記:

在寫完2個月以後,後來去查詢RFC其實原來早就已經有定義比較安全的BASE64規範

目前有些語言已經有實作「safe base64」
上述的寫的轉換程式碼,應該還是要參照RFC的標準來定義比較好
在Safe base64定義裡,「=」還是保持著是補充碼的性質
唯一有變的是「+」變成「-」,「/」變成「_」 
效能最好的做法,就是直接去改老外寫的base64的js,替換就好。

張貼留言