Add EncodeBase32() to Redbean (#856)

This commit is contained in:
Paul Kulchenko 2023-10-11 20:06:20 -07:00 committed by GitHub
parent 3b086af91b
commit 6e4b9b6515
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 221 additions and 1 deletions

165
net/http/base32.c Normal file
View file

@ -0,0 +1,165 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2021 Justine Alexandra Roberts Tunney
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/assert.h"
#include "libc/macros.internal.h"
#include "libc/mem/mem.h"
#include "libc/str/str.h"
asm(".ident\t\"\\n\\n\
Apache License, Version 2.0\\n\
Copyright 2010 Google Inc.\"");
asm(".include \"libc/disclaimer.inc\"");
const char base32def[] = "0123456789abcdefghjkmnpqrstvwxyz";
int tobits(int b) {
int bits = 0; while (b && (b >>= 1)) bits++;
return bits;
}
// 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 = 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);
int mask = (1 << bl) - 1;
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 < n && (bitsLeft > 0 || next < sl)) {
if (bitsLeft < bl) {
if (next < sl) {
buffer <<= 8;
buffer |= s[next++] & 0xFF;
bitsLeft += 8;
} else {
int pad = bl - bitsLeft;
buffer <<= pad;
bitsLeft += pad;
}
}
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 (ol) *ol = r ? count : 0;
return r;
}

View file

@ -34,6 +34,8 @@ char *EncodeLatin1(const char *, size_t, size_t *, int);
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 *);

View file

@ -50,6 +50,19 @@ assert(bin(0x1940efe9d47ae889) == "0b0001100101000000111011111110100111010100011
assert(EncodeHex("\1\2\3\4\255") == "01020304ff")
assert(DecodeHex("01020304ff") == "\1\2\3\4\255")
assert(EncodeBase32("123456") == "64s36d1n6r")
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&amp;there&lt;&gt;")

View file

@ -737,8 +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). 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.

View file

@ -605,6 +605,30 @@ int LuaEncodeLatin1(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 (!IS2POW(al) || al > 128 || al == 1)
return luaL_error(L, "alphabet length is not a power of 2 in range 2..128");
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;

View file

@ -20,10 +20,12 @@ 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 *);
int LuaDeflate(lua_State *);
int LuaEncodeBase32(lua_State *);
int LuaEncodeBase64(lua_State *);
int LuaEncodeHex(lua_State *);
int LuaEncodeLatin1(lua_State *);

View file

@ -5126,11 +5126,13 @@ static const luaL_Reg kLuaFuncs[] = {
{"Crc32", LuaCrc32}, //
{"Crc32c", LuaCrc32c}, //
{"Decimate", LuaDecimate}, //
{"DecodeBase32", LuaDecodeBase32}, //
{"DecodeBase64", LuaDecodeBase64}, //
{"DecodeHex", LuaDecodeHex}, //
{"DecodeJson", LuaDecodeJson}, //
{"DecodeLatin1", LuaDecodeLatin1}, //
{"Deflate", LuaDeflate}, //
{"EncodeBase32", LuaEncodeBase32}, //
{"EncodeBase64", LuaEncodeBase64}, //
{"EncodeHex", LuaEncodeHex}, //
{"EncodeJson", LuaEncodeJson}, //