Last active
December 3, 2022 22:16
-
-
Save mieszko4/01bab59fdeba6bd09bf475dda5968808 to your computer and use it in GitHub Desktop.
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
-- Lua uses at least 8 bytes to represent a number | |
-- Base64 minimum processable unit is represented by four 6-bit chunks which is 24-bits => 3 bytes | |
-- Hence we can safely use bit operations on number type for multiplexing and extraction | |
-- Define function to extract 8-bit ASCII char from 24-bit base64 unit | |
local function extractChar(unit, idx) | |
-- Skip mask for first ASCII char since shifting will destroy other two parts | |
if idx == 2 then | |
local charCode = (unit) >> (8 * idx) | |
return string.char(charCode) | |
end | |
-- Extract specific 8-bit | |
local mask = (2^8 - 1) << (8 * idx) | |
-- Move 8-bit to the beginning so it becomes a char code | |
local charCode = (unit & mask) >> (8 * idx) | |
-- Convert to string | |
return string.char(charCode) | |
end | |
-- Define mapping of base64 character to number | |
local mapping = { | |
A=0, B=1, C=2, D=3, E=4, F=5, G=6, H=7, | |
I=8, J=9, K=10, L=11, M=12, N=13, O=14, P=15, | |
Q=16, R=17, S=18, T=19, U=20, V=21, W=22, X=23, | |
Y=24, Z=25, a=26, b=27, c=28, d=29, e=30, f=31, | |
g=32, h=33, i=34, j=35, k=36, l=37, m=38, n=39, | |
o=40, p=41, q=42, r=43, s=44, t=45, u=46, v=47, | |
w=48, x=49, y=50, z=51, ["0"]=52, ["1"]=53, ["2"]=54, ["3"]=55, | |
["4"]=56, ["5"]=57, ["6"]=58, ["7"]=59, ["8"]=60, ["9"]=61, ["+"]=62, ["/"]=63 | |
} | |
local padding = "=" | |
local missing = "" | |
local function atob(encodedString) | |
-- Get four 6-bit chunks which forms one unit | |
-- There must be at least two 6-bit chunks to cover at least one 8-bit chunk | |
local decodedString = encodedString:gsub("(.)(.)(.?)(.?)", function(c1, c2, c3, c4) | |
-- Convert each 6-bit chunk to number | |
local b1 = mapping[c1] --24,23,22,21,20,19 | |
local b2 = mapping[c2] --18,17,16,15,14,13 | |
local b3 = mapping[c3] --12,11,10,09,08,07 | |
local b4 = mapping[c4] --06,05,04,03,02,01 | |
-- Set optional chunks to 0 to be able to multiplex | |
if b3 == nil then b3 = 0 end | |
if b4 == nil then b4 = 0 end | |
-- Multiplex onto 24-bit number | |
local muxedUnit = (b1 << (6 * 3)) + (b2 << (6 * 2)) + (b3 << (6 * 1)) + (b4 << (6 * 0)) | |
-- Extract 8-bit chunks | |
-- Use empty string if unit is not full or it is padded | |
-- and handle A base64 characters at the end of encodedString | |
local r1 = extractChar(muxedUnit, 2) | |
-- if c3 is padding or missing then we can skip r2 | |
local r2 = (c3 == padding or c3 == missing) and "" or extractChar(muxedUnit, 1) | |
-- if c4 is padding or missing then we can skip r3 | |
local r3 = (c4 == padding or c4 == missing) and "" or extractChar(muxedUnit, 0) | |
-- Concatanate | |
return r1..r2..r3 | |
end) | |
return decodedString | |
end | |
return atob | |
-- Test plan | |
assert(atob("bGlnaHQgd28=") == "light wo", "full units, single padding") | |
assert(atob("bGlnaHQgd28A") == "light wo\x00", "full units, explicit A") | |
assert(atob("bGlnaHQgd28") == "light wo", "2/3 of unit") | |
assert(atob("bGlnaHQgdw==") == "light w", "full unit, double padding") | |
assert(atob("bGlnaHQgdw") == "light w", "1/3 of unit") | |
assert(atob("bGlnaHQgd28=") == "light wo", "full units, single padding") | |
assert(atob("bGlnaHQgd29y") == "light wor", "full units, no padding") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment