diff --git a/net/http/base32.c b/net/http/base32.c index 4a2f87aeb36..a78f8170aec 100644 --- a/net/http/base32.c +++ b/net/http/base32.c @@ -33,15 +33,27 @@ int tobits(int b) { 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 // Copyright 2010 Google Inc.; Author: Markus Gutschke // 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, const char *a, size_t al, size_t *ol) { size_t count = 0; - char *r; + char *r = NULL; + if (sl == -1) sl = s ? strlen(s) : 0; if (al == 0) { a = base32def; al = sizeof(base32def)/sizeof(a[0]); @@ -49,16 +61,12 @@ char* EncodeBase32(const char *s, size_t sl, unassert(2 <= al && al <= 128); int bl = tobits(al); int mask = (1 << bl) - 1; - *ol = (sl * 8 + bl - 1) / bl; // calculate exact output length - if (!(r = malloc(*ol + 1))) { - *ol = 0; - return r; - } - if (sl > 0) { + size_t n = (sl * 8 + bl - 1) / bl; // calculate output length + if ((r = malloc(n + 1))) { int buffer = s[0]; size_t next = 1; int bitsLeft = 8; - while (count < *ol && (bitsLeft > 0 || next < sl)) { + while (count < n && (bitsLeft > 0 || next < sl)) { if (bitsLeft < bl) { if (next < sl) { buffer <<= 8; @@ -73,7 +81,85 @@ char* EncodeBase32(const char *s, size_t sl, bitsLeft -= bl; r[count++] = a[mask & (buffer >> bitsLeft)]; } + r[count] = '\0'; + } + 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 (count < *ol) r[count] = '\000'; + if (ol) *ol = r ? count : 0; return r; } diff --git a/net/http/escape.h b/net/http/escape.h index 402338e8f6d..cd2b19b71d1 100644 --- a/net/http/escape.h +++ b/net/http/escape.h @@ -35,6 +35,7 @@ char *EncodeHttpHeaderValue(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 *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 *DecodeBase64(const char *, size_t, size_t *); diff --git a/test/tool/net/lfuncs_test.lua b/test/tool/net/lfuncs_test.lua index 757dc20fe3f..8ce31d062cd 100644 --- a/test/tool/net/lfuncs_test.lua +++ b/test/tool/net/lfuncs_test.lua @@ -55,6 +55,14 @@ assert(EncodeBase32("12") == "64s0") assert(EncodeBase32("\33", "01") == "00100001") 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("?hello&there<>") == "?hello&there<>") diff --git a/tool/net/help.txt b/tool/net/help.txt index 8c0834bcef2..226d6ee5620 100644 --- a/tool/net/help.txt +++ b/tool/net/help.txt @@ -737,12 +737,20 @@ FUNCTIONS Turns ASCII base-16 hexadecimal byte string into binary 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 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 - 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 decodebase64.c. diff --git a/tool/net/lfuncs.c b/tool/net/lfuncs.c index a701102ae26..f525e6f1996 100644 --- a/tool/net/lfuncs.c +++ b/tool/net/lfuncs.c @@ -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; size_t sl, al; // source/output and alphabet lengths const char *s = luaL_checklstring(L, 1, &sl); // use an empty string, as EncodeBase32 provides a default value 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"); - if (!(p = EncodeBase32(s, sl, a, al, &sl))) + if (!(p = B32(s, sl, a, al, &sl))) return luaL_error(L, "out of memory"); lua_pushlstring(L, p, sl); free(p); 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) { char *p; size_t n; diff --git a/tool/net/lfuncs.h b/tool/net/lfuncs.h index 27318517209..47687aa0d4a 100644 --- a/tool/net/lfuncs.h +++ b/tool/net/lfuncs.h @@ -20,6 +20,7 @@ int LuaCompress(lua_State *); int LuaCrc32(lua_State *); int LuaCrc32c(lua_State *); int LuaDecimate(lua_State *); +int LuaDecodeBase32(lua_State *); int LuaDecodeBase64(lua_State *); int LuaDecodeHex(lua_State *); int LuaDecodeLatin1(lua_State *); diff --git a/tool/net/redbean.c b/tool/net/redbean.c index d43a5be010a..4009f15b848 100644 --- a/tool/net/redbean.c +++ b/tool/net/redbean.c @@ -5127,6 +5127,7 @@ static const luaL_Reg kLuaFuncs[] = { {"Crc32", LuaCrc32}, // {"Crc32c", LuaCrc32c}, // {"Decimate", LuaDecimate}, // + {"DecodeBase32", LuaDecodeBase32}, // {"DecodeBase64", LuaDecodeBase64}, // {"DecodeHex", LuaDecodeHex}, // {"DecodeJson", LuaDecodeJson}, //