【JS】Base64的url轉換
最近有需要將base64的字串放到url上當參數調用。
查了一下文檔
發現base64有定義說「不建議放到url」上。
後來實作後發現,的確放在網址上會產生出許多問題。
像是base64最後的「=」
這其實算是一個補充碼,也就是當長度沒滿足條件的時候,
會自動使用「=」填充用。
而且還有一些字符也會造成問題。
後來看了一些文章後。
大概理解,只要解決三個字符的問題即可。
並且在decode強制一定要加上urldecode
不然在url轉換或跳轉時,(譬如有透過fb做過水)
有的網站會自動encode你的參數,導致在解碼的時候有問題。
以下這種方式算是另類解法,缺點是只有自己能夠讀取
(由於轉換規則是自訂)
下面是js實現的轉換代碼:(js es6語法)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
以上採用的base64庫是使用這個github上的方式 (包裝成class)
https://gist.github.com/mikedamage/73711
不使用js內建的base64加解碼,是因為為了能更好兼容各個瀏覽器。
上述類可以兼容大部分瀏覽器。(需靠插件將es6降轉版本)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* | |
* 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+/="; |
後記:
在寫完2個月以後,後來去查詢RFC其實原來早就已經有定義比較安全的BASE64規範
上述的寫的轉換程式碼,應該還是要參照RFC的標準來定義比較好
在Safe base64定義裡,「=」還是保持著是補充碼的性質
唯一有變的是「+」變成「-」,「/」變成「_」
效能最好的做法,就是直接去改老外寫的base64的js,替換就好。
張貼留言