Skip to content

Instantly share code, notes, and snippets.

@paulmoore
Created May 1, 2012 00:28
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save paulmoore/2563975 to your computer and use it in GitHub Desktop.
Save paulmoore/2563975 to your computer and use it in GitHub Desktop.
URL safe Base64 in Lua
--- base64.lua
--
-- https://gist.github.com/2563975
--
-- V0.3 for Lua 5.1
--
-- A simple Base64 encoder/decoder that uses a URL safe variant of the standard.
-- This implementation encodes character 62 as '-' (instead of '+') and character 63 as '_' (instead of '/').
-- In addition, padding is not used by default.
-- A full description of the specification can be found here: http://tools.ietf.org/html/rfc4648
--
-- To encode, use base64.encode(input), where input is a string of arbitrary bytes. The output is a Base64 encoded string.
-- To decode, use base64.decode(input), where input is a Base64 encoded string. The output is a string of arbitrary bytes.
--
-- The library will throw an error on invalid input, you can catch these as such:
--
-- local status, result = pcall(base64.decode(invalidInput))
-- if not status then
-- print("Error, "..result)
-- end
--
-- If you prefer a different Base64 variant, you can simply change the ENCODABET to your liking.
-- If you wish to use padding, you can change the PAD value to a non-nil, non-empty string.
-- The library will still be able to decode non-padded strings if a PAD is given.
--
-- For all valid input, input == base64.decode(base64.encode(input)).
--
-- This library has a dependency on LuaBit v0.4, which can be found here: http://luaforge.net/projects/bit/
--
-- Copyright (C) 2012 by Paul Moore
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-- copies of the Software, and to permit persons to whom the Software is
-- furnished to do so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in
-- all copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-- THE SOFTWARE.
require "bit"
base64 = {}
--- octet -> char encoding.
local ENCODABET = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', '-', '_'
}
--- char -> octet encoding.
local DECODABET = {}
for i, v in ipairs(ENCODABET) do
DECODABET[v] = i - 1
end
local PAD = nil
--- Converts a 6-bit octet into the associated Base64 character.
--
-- @param octet A 6-bit integer.
-- @return The Base64 representation of the character
local function toChar (octet)
return assert(ENCODABET[octet + 1], "No Base64 character for octet: "..tostring(octet))
end
--- Converts a Base64 character into the associated octet.
--
-- @param char The single Base64 character.
-- @return The 6-bit integer representing the Base64 character.
local function toOctet (char)
return assert(DECODABET[char], "Not a valid Base64 character: "..tostring(char))
end
--- Encodes a string into a Base64 string.
-- The input can be any string of arbitrary bytes.
-- If the input is not a string, or the string is empty, an error will be thrown.
--
-- @param input The input string.
-- @return The Base64 representation of the input string.
function base64.encode (input)
assert(type(input) == "string", "Invalid input, expected type string but got: "..tostring(input).." as a: "..type(input))
assert(#input > 0, "Invalid input, cannot encode an empty string.")
local bytes = { input:byte(i, #input) }
local out = {}
-- Go through each triplet of 3 bytes, which produce 4 octets.
local i = 1
while i <= #bytes - 2 do
local buffer = 0
-- Fill the buffer with the bytes, producing a 24-bit integer.
local b = bit.blshift(bytes[i], 16)
b = bit.band(b, 0xff0000)
buffer = bit.bor(buffer, b)
b = bit.blshift(bytes[i + 1], 8)
b = bit.band(b, 0xff00)
buffer = bit.bor(buffer, b)
b = bit.band(bytes[i + 2], 0xff)
buffer = bit.bor(buffer, b)
-- Read out the 4 octets into the output buffer.
b = bit.blogic_rshift(buffer, 18)
b = bit.band(b, 0x3f)
out[#out + 1] = toChar(b)
b = bit.blogic_rshift(buffer, 12)
b = bit.band(b, 0x3f)
out[#out + 1] = toChar(b)
b = bit.blogic_rshift(buffer, 6)
b = bit.band(b, 0x3f)
out[#out + 1] = toChar(b)
b = bit.band(buffer, 0x3f)
out[#out + 1] = toChar(b)
i = i + 3
end
-- Special case 1: One byte extra, will produce 2 octets.
if #bytes % 3 == 1 then
local buffer = bit.blshift(bytes[i], 16)
buffer = bit.band(buffer, 0xff0000)
local b = bit.blogic_rshift(buffer, 18)
b = bit.band(b, 0x3f)
out[#out + 1] = toChar(b)
b = bit.blogic_rshift(buffer, 12)
b = bit.band(b, 0x3f)
out[#out + 1] = toChar(b)
out[#out + 1] = PAD
out[#out + 1] = PAD
-- Special case 2: Two bytes extra, will produce 3 octets.
elseif #bytes % 3 == 2 then
local buffer = 0
local b = bit.blshift(bytes[i], 16)
b = bit.band(b, 0xff0000)
buffer = bit.bor(buffer, b)
b = bit.blshift(bytes[i + 1], 8)
b = bit.band(b, 0xff00)
buffer = bit.bor(buffer, b)
b = bit.blogic_rshift(buffer, 18)
b = bit.band(b, 0x3f)
out[#out + 1] = toChar(b)
b = bit.blogic_rshift(buffer, 12)
b = bit.band(b, 0x3f)
out[#out + 1] = toChar(b)
b = bit.blogic_rshift(buffer, 6)
b = bit.band(b, 0x3f)
out[#out + 1] = toChar(b)
out[#out + 1] = PAD
end
return table.concat(out)
end
--- Decodes a Base64 string into an output string of arbitrary bytes.
-- If the input is not a string, or the string is empty, or the string is not well-formed Base64, an error will be thrown.
--
-- @param input The Base64 input to decode.
-- @return The decoded Base64 string, as a string of bytes.
function base64.decode (input)
assert(type(input) == "string", "Invalid input, expected type string but got: "..tostring(input).." as a: "..type(input))
assert(#input > 0, "Invalid input, cannot decode an empty string.")
local length = #input
-- Ignore any padding.
if PAD then
length = input:find(PAD, 1, true) or (length + 1)
length = length - 1
end
assert(length > 0, "Invalid input, cannot decode a padded string with no bytes: "..tostring(input))
local out = {}
-- Go through each group of 4 octets to obtain 3 bytes.
local i = 1
while i <= length - 3 do
local buffer = 0
-- Read the 4 octets into the buffer, producing a 24-bit integer.
local b = toOctet(input:sub(i, i))
b = bit.blshift(b, 18)
buffer = bit.bor(buffer, b)
i = i + 1
b = toOctet(input:sub(i, i))
b = bit.blshift(b, 12)
buffer = bit.bor(buffer, b)
i = i + 1
b = toOctet(input:sub(i, i))
b = bit.blshift(b, 6)
buffer = bit.bor(buffer, b)
i = i + 1
b = toOctet(input:sub(i, i))
buffer = bit.bor(buffer, b)
i = i + 1
-- Append the 3 re-constructed bytes into the output buffer.
b = bit.blogic_rshift(buffer, 16)
b = bit.band(b, 0xff)
out[#out + 1] = b
b = bit.blogic_rshift(buffer, 8)
b = bit.band(b, 0xff)
out[#out + 1] = b
b = bit.band(buffer, 0xff)
out[#out + 1] = b
end
-- Special case 1: Only 2 octets remain, producing 1 byte.
if length % 4 == 2 then
local buffer = 0
local b = toOctet(input:sub(i, i))
b = bit.blshift(b, 18)
buffer = bit.bor(buffer, b)
i = i + 1
b = toOctet(input:sub(i, i))
b = bit.blshift(b, 12)
buffer = bit.bor(buffer, b)
i = i + 1
b = bit.blogic_rshift(buffer, 16)
b = bit.band(b, 0xff)
out[#out + 1] = b
-- Special case 2: Only 3 octets remain, producing 2 bytes.
elseif length % 4 == 3 then
local buffer = 0
local b = toOctet(input:sub(i, i))
b = bit.blshift(b, 18)
buffer = bit.bor(buffer, b)
i = i + 1
b = toOctet(input:sub(i, i))
b = bit.blshift(b, 12)
buffer = bit.bor(buffer, b)
i = i + 1
b = toOctet(input:sub(i, i))
b = bit.blshift(b, 6)
buffer = bit.bor(buffer, b)
i = i + 1
b = bit.blogic_rshift(buffer, 16)
b = bit.band(b, 0xff)
out[#out + 1] = b
b = bit.blogic_rshift(buffer, 8)
b = bit.band(b, 0xff)
out[#out + 1] = b
-- Special case 3: One octet remains, we can't get any bytes out of this, throw error.
elseif length % 4 == 1 then
error("Invalid length input string, extra character: "..tostring(input:sub(i, i)))
end
return string.char(unpack(out))
end
--[[---------------
LuaBit v0.4
-------------------
a bitwise operation lib for lua.
http://luaforge.net/projects/bit/
How to use:
-------------------
bit.bnot(n) -- bitwise not (~n)
bit.band(m, n) -- bitwise and (m & n)
bit.bor(m, n) -- bitwise or (m | n)
bit.bxor(m, n) -- bitwise xor (m ^ n)
bit.brshift(n, bits) -- right shift (n >> bits)
bit.blshift(n, bits) -- left shift (n << bits)
bit.blogic_rshift(n, bits) -- logic right shift(zero fill >>>)
Please note that bit.brshift and bit.blshift only support number within
32 bits.
2 utility functions are provided too:
bit.tobits(n) -- convert n into a bit table(which is a 1/0 sequence)
-- high bits first
bit.tonumb(bit_tbl) -- convert a bit table into a number
-------------------
Under the MIT license.
copyright(c) 2006~2007 hanzhao (abrash_han@hotmail.com)
--]]---------------
do
------------------------
-- bit lib implementions
local function check_int(n)
-- checking not float
if(n - math.floor(n) > 0) then
error("trying to use bitwise operation on non-integer!")
end
end
local function to_bits(n)
check_int(n)
if(n < 0) then
-- negative
return to_bits(bit.bnot(math.abs(n)) + 1)
end
-- to bits table
local tbl = {}
local cnt = 1
while (n > 0) do
local last = math.mod(n,2)
if(last == 1) then
tbl[cnt] = 1
else
tbl[cnt] = 0
end
n = (n-last)/2
cnt = cnt + 1
end
return tbl
end
local function tbl_to_number(tbl)
local n = table.getn(tbl)
local rslt = 0
local power = 1
for i = 1, n do
rslt = rslt + tbl[i]*power
power = power*2
end
return rslt
end
local function expand(tbl_m, tbl_n)
local big = {}
local small = {}
if(table.getn(tbl_m) > table.getn(tbl_n)) then
big = tbl_m
small = tbl_n
else
big = tbl_n
small = tbl_m
end
-- expand small
for i = table.getn(small) + 1, table.getn(big) do
small[i] = 0
end
end
local function bit_or(m, n)
local tbl_m = to_bits(m)
local tbl_n = to_bits(n)
expand(tbl_m, tbl_n)
local tbl = {}
local rslt = math.max(table.getn(tbl_m), table.getn(tbl_n))
for i = 1, rslt do
if(tbl_m[i]== 0 and tbl_n[i] == 0) then
tbl[i] = 0
else
tbl[i] = 1
end
end
return tbl_to_number(tbl)
end
local function bit_and(m, n)
local tbl_m = to_bits(m)
local tbl_n = to_bits(n)
expand(tbl_m, tbl_n)
local tbl = {}
local rslt = math.max(table.getn(tbl_m), table.getn(tbl_n))
for i = 1, rslt do
if(tbl_m[i]== 0 or tbl_n[i] == 0) then
tbl[i] = 0
else
tbl[i] = 1
end
end
return tbl_to_number(tbl)
end
local function bit_not(n)
local tbl = to_bits(n)
local size = math.max(table.getn(tbl), 32)
for i = 1, size do
if(tbl[i] == 1) then
tbl[i] = 0
else
tbl[i] = 1
end
end
return tbl_to_number(tbl)
end
local function bit_xor(m, n)
local tbl_m = to_bits(m)
local tbl_n = to_bits(n)
expand(tbl_m, tbl_n)
local tbl = {}
local rslt = math.max(table.getn(tbl_m), table.getn(tbl_n))
for i = 1, rslt do
if(tbl_m[i] ~= tbl_n[i]) then
tbl[i] = 1
else
tbl[i] = 0
end
end
--table.foreach(tbl, print)
return tbl_to_number(tbl)
end
local function bit_rshift(n, bits)
check_int(n)
local high_bit = 0
if(n < 0) then
-- negative
n = bit_not(math.abs(n)) + 1
high_bit = 2147483648 -- 0x80000000
end
for i=1, bits do
n = n/2
n = bit_or(math.floor(n), high_bit)
end
return math.floor(n)
end
-- logic rightshift assures zero filling shift
local function bit_logic_rshift(n, bits)
check_int(n)
if(n < 0) then
-- negative
n = bit_not(math.abs(n)) + 1
end
for i=1, bits do
n = n/2
end
return math.floor(n)
end
local function bit_lshift(n, bits)
check_int(n)
if(n < 0) then
-- negative
n = bit_not(math.abs(n)) + 1
end
for i=1, bits do
n = n*2
end
return bit_and(n, 4294967295) -- 0xFFFFFFFF
end
local function bit_xor2(m, n)
local rhs = bit_or(bit_not(m), bit_not(n))
local lhs = bit_or(m, n)
local rslt = bit_and(lhs, rhs)
return rslt
end
--------------------
-- bit lib interface
bit = {
-- bit operations
bnot = bit_not,
band = bit_and,
bor = bit_or,
bxor = bit_xor,
brshift = bit_rshift,
blshift = bit_lshift,
bxor2 = bit_xor2,
blogic_rshift = bit_logic_rshift,
-- utility func
tobits = to_bits,
tonumb = tbl_to_number,
}
end
--[[
for i = 1, 100 do
for j = 1, 100 do
if(bit.bxor(i, j) ~= bit.bxor2(i, j)) then
error("bit.xor failed.")
end
end
end
--]]
@ykarhus
Copy link

ykarhus commented Dec 13, 2013

I was trying to use it to send a base64 in an app in Gideros Mobile. How I can see it's old. Do you agree this one could work fine?:

https://gist.github.com/bortels/1436940

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment