Add DecodeBase32 to redbean

This commit is contained in:
Paul Kulchenko 2023-10-09 22:13:47 -07:00
parent 9abacaa6f3
commit 82e6a4ed0b
7 changed files with 129 additions and 15 deletions

View file

@ -33,15 +33,27 @@ int tobits(int b) {
return bits; return bits;
} }
// the next function is based on // these functions are based on
// https://github.com/google/google-authenticator-libpam/blob/master/src/base32.c // https://github.com/google/google-authenticator-libpam/blob/master/src/base32.c
// Copyright 2010 Google Inc.; Author: Markus Gutschke // Copyright 2010 Google Inc.; Author: Markus Gutschke
// licensed under Apache License, Version 2.0 // licensed under Apache License, Version 2.0
/**
* Encodes binary to base32 ascii representation.
*
* @param s is input value
* @param sl if -1 implies strlen
* @param a is alphabet string (with power of 2 length)
* @param al is alphabet length (if 0 then Crockford's encoding is used)
* @param ol if non-NULL receives output length
* @return allocated NUL-terminated buffer, or NULL w/ errno
*/
char* EncodeBase32(const char *s, size_t sl, char* EncodeBase32(const char *s, size_t sl,
const char *a, size_t al, const char *a, size_t al,
size_t *ol) { size_t *ol) {
size_t count = 0; size_t count = 0;
char *r; char *r = NULL;
if (sl == -1) sl = s ? strlen(s) : 0;
if (al == 0) { if (al == 0) {
a = base32def; a = base32def;
al = sizeof(base32def)/sizeof(a[0]); al = sizeof(base32def)/sizeof(a[0]);
@ -49,16 +61,12 @@ char* EncodeBase32(const char *s, size_t sl,
unassert(2 <= al && al <= 128); unassert(2 <= al && al <= 128);
int bl = tobits(al); int bl = tobits(al);
int mask = (1 << bl) - 1; int mask = (1 << bl) - 1;
*ol = (sl * 8 + bl - 1) / bl; // calculate exact output length size_t n = (sl * 8 + bl - 1) / bl; // calculate output length
if (!(r = malloc(*ol + 1))) { if ((r = malloc(n + 1))) {
*ol = 0;
return r;
}
if (sl > 0) {
int buffer = s[0]; int buffer = s[0];
size_t next = 1; size_t next = 1;
int bitsLeft = 8; int bitsLeft = 8;
while (count < *ol && (bitsLeft > 0 || next < sl)) { while (count < n && (bitsLeft > 0 || next < sl)) {
if (bitsLeft < bl) { if (bitsLeft < bl) {
if (next < sl) { if (next < sl) {
buffer <<= 8; buffer <<= 8;
@ -73,7 +81,85 @@ char* EncodeBase32(const char *s, size_t sl,
bitsLeft -= bl; bitsLeft -= bl;
r[count++] = a[mask & (buffer >> bitsLeft)]; r[count++] = a[mask & (buffer >> bitsLeft)];
} }
r[count] = '\0';
} }
if (count < *ol) r[count] = '\000'; if (ol) *ol = r ? count : 0;
return r;
}
static signed char kBase32cust[256];
static signed char kBase32[256] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -2, -2, -1, -1, -2, -1, -1, // 0x00
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x10
-2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -2, -1, -1, // 0x20
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, // 0x30
-1, 10, 11, 12, 13, 14, 15, 16, 17, 1, 18, 19, 1, 20, 21, -1, // 0x40
22, 23, 24, 25, 26, 0, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, // 0x50
-1, 10, 11, 12, 13, 14, 15, 16, 17, 1, 18, 19, 1, 20, 21, -1, // 0x60
22, 23, 24, 25, 26, 0, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, // 0x70
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x80
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x90
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0xa0
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0xb0
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0xc0
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0xd0
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0xe0
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0xf0
};
/**
* Decodes base32 ascii representation to binary.
*
* This uses Crockford's encoding and skips whitespaces.
* The decoding stops at the first character not in the alphabet.
*
* @param s is input value
* @param sl if -1 implies strlen
* @param a is alphabet string (with power of 2 length)
* @param al is alphabet length (if 0 then Crockford's encoding is used)
* @param ol if non-NULL receives output length
* @return allocated NUL-terminated buffer, or NULL w/ errno
*/
char* DecodeBase32(const char *s, size_t sl,
const char *a, size_t al,
size_t *ol) {
size_t count = 0;
char *r = NULL;
if (sl == -1) sl = s ? strlen(s) : 0;
if (al == 0) {
a = base32def;
al = sizeof(base32def)/sizeof(a[0]);
}
unassert(2 <= al && al <= 128);
int bl = tobits(al);
size_t n = (sl * bl + 1) / 8 + 1; // calculate output length
// process input
if ((r = malloc(n + 1))) {
unsigned int buffer = 0;
signed char *map = kBase32;
int bitsLeft = 0;
if (a != base32def) {
// if the provided alphabet doesn't match the default
map = kBase32cust;
memset(map, -1, 256);
// populate the map based on alphabet
for (int i = 0; i < al; i++) map[a[i] & 0xff] = i;
}
while (count < n && *s) {
signed char m = map[*s++ & 0xff];
if (m == -2) continue;
if (m == -1) break;
buffer <<= bl;
buffer |= m;
bitsLeft += bl;
if (bitsLeft >= 8) {
r[count++] = buffer >> (bitsLeft - 8);
bitsLeft -= 8;
}
}
r[count] = '\0';
}
if (ol) *ol = r ? count : 0;
return r; return r;
} }

View file

@ -35,6 +35,7 @@ char *EncodeHttpHeaderValue(const char *, size_t, size_t *);
char *VisualizeControlCodes(const char *, size_t, size_t *); char *VisualizeControlCodes(const char *, size_t, size_t *);
char *IndentLines(const char *, size_t, size_t *, size_t); char *IndentLines(const char *, size_t, size_t *, size_t);
char *EncodeBase32(const char *, size_t, const char *, size_t, size_t *); char *EncodeBase32(const char *, size_t, const char *, size_t, size_t *);
char *DecodeBase32(const char *, size_t, const char *, size_t, size_t *);
char *EncodeBase64(const char *, size_t, size_t *); char *EncodeBase64(const char *, size_t, size_t *);
char *DecodeBase64(const char *, size_t, size_t *); char *DecodeBase64(const char *, size_t, size_t *);

View file

@ -55,6 +55,14 @@ assert(EncodeBase32("12") == "64s0")
assert(EncodeBase32("\33", "01") == "00100001") assert(EncodeBase32("\33", "01") == "00100001")
assert(EncodeBase32("\33", "0123456789abcdef") == "21") assert(EncodeBase32("\33", "0123456789abcdef") == "21")
assert(DecodeBase32("64s36d1n6r") == "123456")
assert(DecodeBase32("64s0") == "12")
assert(DecodeBase32("64 \t\r\ns0") == "12")
assert(DecodeBase32("64s0!64s0") == "12")
assert(EncodeBase32("\1\2\3\4\255", "0123456789abcdef") == "01020304ff")
assert(DecodeBase32("01020304ff", "0123456789abcdef") == "\1\2\3\4\255")
assert(EscapeHtml(nil) == nil) assert(EscapeHtml(nil) == nil)
assert(EscapeHtml("?hello&there<>") == "?hello&amp;there&lt;&gt;") assert(EscapeHtml("?hello&there<>") == "?hello&amp;there&lt;&gt;")

View file

@ -737,12 +737,20 @@ FUNCTIONS
Turns ASCII base-16 hexadecimal byte string into binary string, Turns ASCII base-16 hexadecimal byte string into binary string,
case-insensitively. Non-hex characters may not appear in string. case-insensitively. Non-hex characters may not appear in string.
DecodeBase32(ascii:str[, alphabet:str]) → binary:str
Turns ASCII into binary using provided alphabet. The default
decoding uses Crockford's base32 alphabet in a permissive way
that ignores whitespaces and dash ('-') and stops at the first
character outside of the alphabet.
EncodeBase32(binary:str[, alphabet:str]) → ascii:str EncodeBase32(binary:str[, alphabet:str]) → ascii:str
Turns binary into ASCII using provided alphabet (using Crockford's Turns binary into ASCII using provided alphabet (using Crockford's
base32 encoding by default). base32 encoding by default). Any alphabet that has a power of 2
length (up to 128) may be supplied for encoding and decoding,
which allows to provide alternative base32 encodings.
DecodeBase64(ascii:str) → binary:str DecodeBase64(ascii:str) → binary:str
Turns ASCII into binary, in a permissive way that ignores Turns ASCII into binary in a permissive way that ignores
characters outside the base64 alphabet, such as whitespace. See characters outside the base64 alphabet, such as whitespace. See
decodebase64.c. decodebase64.c.

View file

@ -605,21 +605,30 @@ int LuaEncodeLatin1(lua_State *L) {
} }
} }
int LuaEncodeBase32(lua_State *L) { dontinline int LuaBase32Impl(lua_State *L,
char *B32(const char *, size_t, const char *, size_t, size_t *)) {
char *p; char *p;
size_t sl, al; // source/output and alphabet lengths size_t sl, al; // source/output and alphabet lengths
const char *s = luaL_checklstring(L, 1, &sl); const char *s = luaL_checklstring(L, 1, &sl);
// use an empty string, as EncodeBase32 provides a default value // use an empty string, as EncodeBase32 provides a default value
const char *a = luaL_optlstring(L, 2, "", &al); const char *a = luaL_optlstring(L, 2, "", &al);
if (al & (al - 1) || al > 128 || al == 1) if (!IS2POW(al) || al > 128 || al == 1)
return luaL_error(L, "alphabet length is not a power of 2 in range 2..128"); return luaL_error(L, "alphabet length is not a power of 2 in range 2..128");
if (!(p = EncodeBase32(s, sl, a, al, &sl))) if (!(p = B32(s, sl, a, al, &sl)))
return luaL_error(L, "out of memory"); return luaL_error(L, "out of memory");
lua_pushlstring(L, p, sl); lua_pushlstring(L, p, sl);
free(p); free(p);
return 1; return 1;
} }
int LuaEncodeBase32(lua_State *L) {
return LuaBase32Impl(L, EncodeBase32);
}
int LuaDecodeBase32(lua_State *L) {
return LuaBase32Impl(L, DecodeBase32);
}
int LuaEncodeHex(lua_State *L) { int LuaEncodeHex(lua_State *L) {
char *p; char *p;
size_t n; size_t n;

View file

@ -20,6 +20,7 @@ int LuaCompress(lua_State *);
int LuaCrc32(lua_State *); int LuaCrc32(lua_State *);
int LuaCrc32c(lua_State *); int LuaCrc32c(lua_State *);
int LuaDecimate(lua_State *); int LuaDecimate(lua_State *);
int LuaDecodeBase32(lua_State *);
int LuaDecodeBase64(lua_State *); int LuaDecodeBase64(lua_State *);
int LuaDecodeHex(lua_State *); int LuaDecodeHex(lua_State *);
int LuaDecodeLatin1(lua_State *); int LuaDecodeLatin1(lua_State *);

View file

@ -5127,6 +5127,7 @@ static const luaL_Reg kLuaFuncs[] = {
{"Crc32", LuaCrc32}, // {"Crc32", LuaCrc32}, //
{"Crc32c", LuaCrc32c}, // {"Crc32c", LuaCrc32c}, //
{"Decimate", LuaDecimate}, // {"Decimate", LuaDecimate}, //
{"DecodeBase32", LuaDecodeBase32}, //
{"DecodeBase64", LuaDecodeBase64}, // {"DecodeBase64", LuaDecodeBase64}, //
{"DecodeHex", LuaDecodeHex}, // {"DecodeHex", LuaDecodeHex}, //
{"DecodeJson", LuaDecodeJson}, // {"DecodeJson", LuaDecodeJson}, //