diff --git a/libc/str/a64l.c b/libc/str/a64l.c index 3986fa460..8f57df6fe 100644 --- a/libc/str/a64l.c +++ b/libc/str/a64l.c @@ -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; diff --git a/libc/str/l64a.c b/libc/str/l64a.c index 28157c3a0..c31eb888b 100644 --- a/libc/str/l64a.c +++ b/libc/str/l64a.c @@ -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]; diff --git a/net/http/base64.h b/net/http/base64.h new file mode 100644 index 000000000..57752859f --- /dev/null +++ b/net/http/base64.h @@ -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_ */ diff --git a/net/http/decodebase64.c b/net/http/decodebase64.c new file mode 100644 index 000000000..7a8a1a72d --- /dev/null +++ b/net/http/decodebase64.c @@ -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; +} diff --git a/net/http/encodebase64.c b/net/http/encodebase64.c new file mode 100644 index 000000000..1515bdfd0 --- /dev/null +++ b/net/http/encodebase64.c @@ -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; +} diff --git a/net/http/parsehttprequest.c b/net/http/parsehttprequest.c index 185930ba5..768c8d790 100644 --- a/net/http/parsehttprequest.c +++ b/net/http/parsehttprequest.c @@ -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 }; diff --git a/test/net/http/encodebase64_test.c b/test/net/http/encodebase64_test.c new file mode 100644 index 000000000..4e4f23a30 --- /dev/null +++ b/test/net/http/encodebase64_test.c @@ -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))); +} diff --git a/tool/net/redbean.c b/tool/net/redbean.c index 7e023bdb5..5fa110084 100644 --- a/tool/net/redbean.c +++ b/tool/net/redbean.c @@ -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));