Add base64 support

Lua Server Pages may now call the EncodeBase64() and DecodeBase64()
functions should they wish to do things like emit embeded data URIs

See #97
This commit is contained in:
Justine Tunney 2021-03-27 18:17:54 -07:00
parent 4d21cd315d
commit dcbd2b8668
8 changed files with 333 additions and 5 deletions

View file

@ -38,7 +38,9 @@ static const signed char kBase64i[256] = {
};
/**
* Converts base64 to 32-bit integer.
* Converts base64 to 32-bit integer, the posix way.
* @see l64a() for inverse
* @see DecodeBase64()
*/
long a64l(const char *s) {
uint32_t i, v, x;

View file

@ -19,7 +19,9 @@
#include "libc/str/str.h"
/**
* Converts 32-bit integer to base64.
* Converts 32-bit integer to base64, the posix way.
* @see a64l() for inverse
* @see EncodeBase64()
*/
char *l64a(long x) {
static char b[7];

11
net/http/base64.h Normal file
View file

@ -0,0 +1,11 @@
#ifndef COSMOPOLITAN_NET_HTTP_BASE64_H_
#define COSMOPOLITAN_NET_HTTP_BASE64_H_
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
char *EncodeBase64(const void *, size_t, size_t *);
void *DecodeBase64(const char *, size_t, size_t *);
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_NET_HTTP_BASE64_H_ */

85
net/http/decodebase64.c Normal file
View file

@ -0,0 +1,85 @@
/*-*- 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/mem/mem.h"
#include "libc/str/str.h"
#include "net/http/base64.h"
static const signed char kBase64[256] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x00
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x10
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, // 0x20
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, // 0x30
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 0x40
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, // 0x50
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 0x60
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -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 base64 ascii representation to binary.
*/
void *DecodeBase64(const char *data, size_t size, size_t *out_size) {
unsigned w;
char *r, *q;
int a, b, c, d;
const char *p, *pe;
if (size == -1) size = strlen(data);
if ((r = malloc(size / 4 * 3 + 1))) {
q = r;
p = data;
pe = p + size;
for (;;) {
do {
if (p == pe) goto Done;
a = kBase64[*p++ & 0xff];
} while (a == -1);
if (a == -2) continue;
do {
if (p == pe) goto Done;
b = kBase64[*p++ & 0xff];
} while (b == -1);
if (b == -2) continue;
do {
c = p < pe ? kBase64[*p++ & 0xff] : -2;
} while (c == -1);
do {
d = p < pe ? kBase64[*p++ & 0xff] : -2;
} while (d == -1);
w = a << 18 | b << 12;
if (c != -2) w |= c << 6;
if (d != -2) w |= d;
*q++ = (w & 0xFF0000) >> 020;
if (c != -2) *q++ = (w & 0x00FF00) >> 010;
if (d != -2) *q++ = (w & 0x0000FF) >> 000;
}
Done:
if (out_size) *out_size = q - r;
*q++ = '\0';
if ((q = realloc(r, q - r))) r = q;
}
return r;
}

48
net/http/encodebase64.c Normal file
View file

@ -0,0 +1,48 @@
/*-*- 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/mem/mem.h"
#include "net/http/base64.h"
#define CHARS "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
/**
* Encodes binary to base64 ascii representation.
*/
char *EncodeBase64(const void *data, size_t size, size_t *out_size) {
size_t n;
unsigned w;
char *r, *q;
const unsigned char *p, *pe;
if ((n = size) % 3) n += 3 - size % 3;
n /= 3, n *= 4;
if ((r = malloc(n + 1))) {
if (out_size) *out_size = n;
for (q = r, p = data, pe = p + size; p < pe; p += 3) {
w = p[0] << 020;
if (p + 1 < pe) w |= p[1] << 010;
if (p + 2 < pe) w |= p[2] << 000;
*q++ = CHARS[(w >> 18) & 077];
*q++ = CHARS[(w >> 12) & 077];
*q++ = p + 1 < pe ? CHARS[(w >> 6) & 077] : '=';
*q++ = p + 2 < pe ? CHARS[w & 077] : '=';
}
*q++ = '\0';
}
return r;
}

View file

@ -27,7 +27,7 @@
#include "libc/x/x.h"
#include "net/http/http.h"
#define LIMIT (SHRT_MAX - 1)
#define LIMIT (SHRT_MAX - 2)
enum { START, METHOD, URI, VERSION, HKEY, HSEP, HVAL, CR1, LF1, LF2 };

View file

@ -0,0 +1,155 @@
/*-*- 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/mem/mem.h"
#include "libc/rand/rand.h"
#include "libc/runtime/gc.internal.h"
#include "libc/str/str.h"
#include "libc/testlib/ezbench.h"
#include "libc/testlib/hyperion.h"
#include "libc/testlib/testlib.h"
#include "net/http/base64.h"
size_t i, n, m;
char *p, *q, b[32];
TEST(EncodeBase64, test) {
EXPECT_STREQ("", gc(EncodeBase64("", 0, 0)));
EXPECT_STREQ("AA==", gc(EncodeBase64("\0", 1, 0)));
EXPECT_STREQ("AAA=", gc(EncodeBase64("\0\0", 2, 0)));
EXPECT_STREQ("AAAA", gc(EncodeBase64("\0\0\0", 3, 0)));
EXPECT_STREQ("/w==", gc(EncodeBase64("\377", 1, 0)));
EXPECT_STREQ("//8=", gc(EncodeBase64("\377\377", 2, 0)));
EXPECT_STREQ("////", gc(EncodeBase64("\377\377\377", 3, 0)));
EXPECT_STREQ("aGVsbG8=", gc(EncodeBase64("hello", 5, 0)));
EXPECT_STREQ(
"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEy"
"MzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2Rl"
"ZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeY"
"mZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrL"
"zM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+"
"/w==",
gc(EncodeBase64(
"\000\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020"
"\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037\040\041"
"\042\043\044\045\046\047\050\051\052\053\054\055\056\057\060\061\062"
"\063\064\065\066\067\070\071\072\073\074\075\076\077\100\101\102\103"
"\104\105\106\107\110\111\112\113\114\115\116\117\120\121\122\123\124"
"\125\126\127\130\131\132\133\134\135\136\137\140\141\142\143\144\145"
"\146\147\150\151\152\153\154\155\156\157\160\161\162\163\164\165\166"
"\167\170\171\172\173\174\175\176\177\200\201\202\203\204\205\206\207"
"\210\211\212\213\214\215\216\217\220\221\222\223\224\225\226\227\230"
"\231\232\233\234\235\236\237\240\241\242\243\244\245\246\247\250\251"
"\252\253\254\255\256\257\260\261\262\263\264\265\266\267\270\271\272"
"\273\274\275\276\277\300\301\302\303\304\305\306\307\310\311\312\313"
"\314\315\316\317\320\321\322\323\324\325\326\327\330\331\332\333\334"
"\335\336\337\340\341\342\343\344\345\346\347\350\351\352\353\354\355"
"\356\357\360\361\362\363\364\365\366\367\370\371\372\373\374\375\376"
"\377",
256, &n)));
EXPECT_EQ(344, n);
}
TEST(DecodeBase64, test) {
EXPECT_BINEQ(u" ", gc(DecodeBase64("", 0, 0)));
EXPECT_BINEQ(u"  ", gc(DecodeBase64("AA==", 4, 0)));
EXPECT_BINEQ(u"♦ ", gc(DecodeBase64("BB==", 4, 0)));
EXPECT_BINEQ(u"   ", gc(DecodeBase64("AAA=", 4, 0)));
EXPECT_BINEQ(u"    ", gc(DecodeBase64("AAAA", 4, 0)));
EXPECT_BINEQ(u"λ ", gc(DecodeBase64("/w==", 4, 0)));
EXPECT_BINEQ(u"λλ ", gc(DecodeBase64("//8=", 4, 0)));
EXPECT_BINEQ(u"λλλ ", gc(DecodeBase64("////", 4, 0)));
EXPECT_BINEQ(u"hello ", gc(DecodeBase64("aGVsbG8=", 8, 0)));
EXPECT_BINEQ(u"hello ", gc(DecodeBase64("aGVsbG8=", -1, 0)));
EXPECT_EQ(
0,
memcmp(
"\000\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020"
"\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037\040\041"
"\042\043\044\045\046\047\050\051\052\053\054\055\056\057\060\061\062"
"\063\064\065\066\067\070\071\072\073\074\075\076\077\100\101\102\103"
"\104\105\106\107\110\111\112\113\114\115\116\117\120\121\122\123\124"
"\125\126\127\130\131\132\133\134\135\136\137\140\141\142\143\144\145"
"\146\147\150\151\152\153\154\155\156\157\160\161\162\163\164\165\166"
"\167\170\171\172\173\174\175\176\177\200\201\202\203\204\205\206\207"
"\210\211\212\213\214\215\216\217\220\221\222\223\224\225\226\227\230"
"\231\232\233\234\235\236\237\240\241\242\243\244\245\246\247\250\251"
"\252\253\254\255\256\257\260\261\262\263\264\265\266\267\270\271\272"
"\273\274\275\276\277\300\301\302\303\304\305\306\307\310\311\312\313"
"\314\315\316\317\320\321\322\323\324\325\326\327\330\331\332\333\334"
"\335\336\337\340\341\342\343\344\345\346\347\350\351\352\353\354\355"
"\356\357\360\361\362\363\364\365\366\367\370\371\372\373\374\375\376"
"\377",
gc(DecodeBase64(
"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUm\r\n"
"JygpKissLS4vMDEy\r\n"
"MzQ1Njc4OTo7PD0+\r\n"
"P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2Rl\r\n"
"ZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+\r\n"
"AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeY\r\n"
"mZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/\r\n"
"wMHCw8TFxsfIycrL\r\n"
"zM3Oz9DR0tPU1dbX2Nna29zd3t/\r\n"
"g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+\r\n"
"/w==\r\n",
-1, &n)),
256));
EXPECT_EQ(256, n);
}
TEST(DecodeBase64, testSeparators_skipsOverThemAtAnyState) {
EXPECT_BINEQ(u" ", gc(DecodeBase64(" ", 1, 0)));
EXPECT_BINEQ(u"  ", gc(DecodeBase64(" A A = = ", 9, 0)));
EXPECT_BINEQ(u"♦ ", gc(DecodeBase64(" B B = = ", 4, 0)));
EXPECT_BINEQ(u"hello ", gc(DecodeBase64("a\nG\nV\ns\nb\nG\n8\n=\n", 16, 0)));
}
TEST(DecodeBase64, testInvalidSequences_skipsOverThem) {
EXPECT_BINEQ(u" ", gc(DecodeBase64("A===", 4, 0)));
EXPECT_BINEQ(u" ", gc(DecodeBase64("B===", 4, 0)));
EXPECT_BINEQ(u"♦ ", gc(DecodeBase64("B===BB==", 8, 0)));
EXPECT_BINEQ(u"♦ ", gc(DecodeBase64("====BB==", 8, 0)));
}
TEST(Base64, RoundTrip) {
for (i = 0; i < 1000; ++i) {
n = rand() % 32;
rngset(b, n, rand64, -1);
p = EncodeBase64(b, n, &m);
q = DecodeBase64(p, m, &m);
ASSERT_EQ(n, m);
ASSERT_EQ(0, memcmp(b, q, n));
free(p);
free(q);
}
}
TEST(Base64, Fuzz) {
for (i = 0; i < 1000; ++i) {
n = rand() % 32;
rngset(b, n, rand64, -1);
free(DecodeBase64(p, m, 0));
}
}
BENCH(EncodeBase64, bench) {
EZBENCH2("EncodeBase64", donothing,
free(EncodeBase64(kHyperion, kHyperionSize, 0)));
p = gc(EncodeBase64(kHyperion, kHyperionSize, &n));
EZBENCH2("DecodeBase64", donothing, free(DecodeBase64(p, n, 0)));
}

View file

@ -73,6 +73,7 @@
#include "libc/x/x.h"
#include "libc/zip.h"
#include "libc/zipos/zipos.internal.h"
#include "net/http/base64.h"
#include "net/http/escape.h"
#include "net/http/http.h"
#include "third_party/getopt/getopt.h"
@ -886,7 +887,7 @@ static void ParseParams(struct Parser *u, struct Params *h, bool isform) {
*u->p++ = u->c;
break;
case '+':
*u->p++ = isform ? ' ' : u->c;
*u->p++ = isform ? ' ' : '+';
break;
case '%':
ParseEscape(u);
@ -1556,6 +1557,28 @@ static int LuaEscapeLiteral(lua_State *L) {
return LuaEscaper(L, EscapeJsStringLiteral);
}
static int LuaEncodeBase64(lua_State *L) {
char *p;
size_t size, n;
const char *data;
data = luaL_checklstring(L, 1, &size);
p = EncodeBase64(data, size, &n);
lua_pushlstring(L, p, n);
free(p);
return 1;
}
static int LuaDecodeBase64(lua_State *L) {
char *p;
size_t size, n;
const char *data;
data = luaL_checklstring(L, 1, &size);
p = DecodeBase64(data, size, &n);
lua_pushlstring(L, p, n);
free(p);
return 1;
}
static int LuaPopcnt(lua_State *L) {
lua_pushinteger(L, popcnt(luaL_checkinteger(L, 1)));
return 1;
@ -1596,6 +1619,8 @@ static void LuaRun(const char *path) {
}
static const luaL_Reg kLuaFuncs[] = {
{"DecodeBase64", LuaDecodeBase64}, //
{"EncodeBase64", LuaEncodeBase64}, //
{"EscapeFragment", LuaEscapeFragment}, //
{"EscapeHtml", LuaEscapeHtml}, //
{"EscapeLiteral", LuaEscapeLiteral}, //
@ -2052,7 +2077,6 @@ static void TuneServerSocket(void) {
void RedBean(void) {
uint32_t addrsize;
if (IsWindows()) uniprocess = true;
if (daemonize) Daemonize();
gmtoff = GetGmtOffset();
OpenZip((const char *)getauxval(AT_EXECFN));
IndexAssets();
@ -2079,6 +2103,7 @@ void RedBean(void) {
}
exit(1);
}
if (daemonize) Daemonize();
CHECK_NE(-1, listen(server, 10));
addrsize = sizeof(serveraddr);
CHECK_NE(-1, getsockname(server, &serveraddr, &addrsize));