From a1677d605a89ca92ab5f6c08bb38aca49e576989 Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Sun, 28 Mar 2021 00:10:17 -0700 Subject: [PATCH] Transcode ISO-8859-1 in HTTP headers If we keep making changes like this, redbean might not be a toy anymore. Additional steps are also being taken now to prevent ANSI control codes sent by the client from slipping into logs. --- net/http/decodelatin1.c | 54 ++++ net/http/encodehttpheadervalue.c | 85 ++++++ net/http/http.h | 4 + net/http/isvalidhttptoken.c | 54 ++++ net/http/parsehttprequest.c | 18 +- net/http/visualizecontrolcodes.c | 93 ++++++ test/net/http/decodelatin1_test.c | 28 ++ test/net/http/encodehttpheadervalue_test.c | 82 ++++++ test/net/http/parsehttprequest_test.c | 13 + test/net/http/visualizecontrolcodes_test.c | 34 +++ tool/net/net.mk | 1 + tool/net/redbean-xhr.lua | 2 + tool/net/redbean.c | 326 +++++++++++---------- tool/net/redbean.lua | 42 ++- 14 files changed, 675 insertions(+), 161 deletions(-) create mode 100644 net/http/decodelatin1.c create mode 100644 net/http/encodehttpheadervalue.c create mode 100644 net/http/isvalidhttptoken.c create mode 100644 net/http/visualizecontrolcodes.c create mode 100644 test/net/http/decodelatin1_test.c create mode 100644 test/net/http/encodehttpheadervalue_test.c create mode 100644 test/net/http/visualizecontrolcodes_test.c create mode 100644 tool/net/redbean-xhr.lua diff --git a/net/http/decodelatin1.c b/net/http/decodelatin1.c new file mode 100644 index 00000000..aef372cc --- /dev/null +++ b/net/http/decodelatin1.c @@ -0,0 +1,54 @@ +/*-*- 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/http.h" + +/** + * Decodes ISO-8859-1 to UTF-8. + * + * @param data is input value + * @param size if -1 implies strlen + * @param out_size if non-NULL receives output length on success + * @return allocated NUL-terminated buffer, or NULL w/ errno + */ +char *DecodeLatin1(const char *data, size_t size, size_t *out_size) { + int c; + char *r, *q; + const char *p, *e; + if (size == -1) size = strlen(data); + if ((r = malloc(size * 2 + 1))) { + q = r; + p = data; + e = p + size; + while (p < e) { + c = *p++ & 0xff; + if (c < 0200) { + *q++ = c; + } else { + *q++ = 0300 | c >> 6; + *q++ = 0200 | c & 077; + } + } + if (out_size) *out_size = q - r; + *q++ = '\0'; + if ((q = realloc(r, q - r))) r = q; + } + return r; +} diff --git a/net/http/encodehttpheadervalue.c b/net/http/encodehttpheadervalue.c new file mode 100644 index 00000000..ee5829e1 --- /dev/null +++ b/net/http/encodehttpheadervalue.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/errno.h" +#include "libc/mem/mem.h" +#include "libc/str/str.h" +#include "libc/str/thompike.h" +#include "net/http/http.h" + +/** + * Encodes HTTP header value. + * + * This operation involves the following: + * + * 1. Trim whitespace. + * 2. Turn UTF-8 into ISO-8859-1. + * 3. Make sure no C0 or C1 control codes are present (except tab). + * + * If the input value isn't thompson-pike encoded then this + * implementation will fall back to latin1 in most cases. + * + * @param data is input value + * @param size if -1 implies strlen + * @param out_size if non-NULL receives output length on success + * @return allocated NUL-terminated string, or NULL w/ errno + */ +char *EncodeHttpHeaderValue(const char *data, size_t size, size_t *out_size) { + bool t; + wint_t x; + char *r, *q; + const char *p, *e; + if (size == -1) size = strlen(data); + if ((r = malloc(size + 1))) { + t = 0; + q = r; + p = data; + e = p + size; + while (p < e) { + x = *p++ & 0xff; + if (x >= 0300) { + if (p < e && ThomPikeCont(*p)) { + if (ThomPikeLen(x) == 2) { + x = ThomPikeMerge(ThomPikeByte(x), *p++); + } else { + x = 0; + } + } + } + if (!t) { + if (x == ' ' || x == '\t') { + continue; + } else { + t = true; + } + } + if ((0x20 <= x && x <= 0x7E) || (0xA0 <= x && x <= 0xFF) || x == '\t') { + *q++ = x; + } else { + free(r); + errno = EILSEQ; + return NULL; + } + } + while (q > r && (q[-1] == ' ' || q[-1] == '\t')) --q; + if (out_size) *out_size = q - r; + *q++ = '\0'; + if ((q = realloc(r, q - r))) r = q; + } + return r; +} diff --git a/net/http/http.h b/net/http/http.h index f3cdafef..93c67277 100644 --- a/net/http/http.h +++ b/net/http/http.h @@ -121,6 +121,10 @@ unsigned ParseHttpVersion(const char *, size_t); int64_t ParseHttpDateTime(const char *, size_t); const char *GetHttpReason(int); const char *GetHttpHeaderName(int); +char *DecodeLatin1(const char *, size_t, size_t *); +bool IsValidHttpToken(const char *, size_t); +char *EncodeHttpHeaderValue(const char *, size_t, size_t *); +char *VisualizeControlCodes(const char *, size_t, size_t *); COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ diff --git a/net/http/isvalidhttptoken.c b/net/http/isvalidhttptoken.c new file mode 100644 index 00000000..b3e61791 --- /dev/null +++ b/net/http/isvalidhttptoken.c @@ -0,0 +1,54 @@ +/*-*- 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/str/str.h" +#include "net/http/http.h" + +// http/1.1 token dispatch +// 0 is CTLs, SP, ()<>@,;:\"/[]?={} +// 1 is legal ascii +static const char kHttpToken[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x00 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x10 + 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, // 0x20 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, // 0x30 + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x40 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, // 0x50 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, // 0x70 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x80 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x90 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xa0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xb0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xc0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xd0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xe0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xf0 +}; + +bool IsValidHttpToken(const char *s, size_t n) { + size_t i; + if (!n) return false; + if (n == -1) n = strlen(s); + for (i = 0; i < n; ++i) { + if (!kHttpToken[s[i] & 0xff]) { + return false; + } + } + return true; +} diff --git a/net/http/parsehttprequest.c b/net/http/parsehttprequest.c index 768c8d79..9bb842c9 100644 --- a/net/http/parsehttprequest.c +++ b/net/http/parsehttprequest.c @@ -47,9 +47,19 @@ void DestroyHttpRequest(struct HttpRequest *r) { /** * Parses HTTP request. + * + * This parser is responsible for determining the length of a message + * and slicing the strings inside it. Performance is attained using + * perfect hash tables. No memory allocation is performed for normal + * messages. Line folding is forbidden. State persists across calls so + * that fragmented messages can be handled efficiently. A limitation on + * message size is imposed to make the header data structures smaller. + * All other things are permissive to the greatest extent possible. + * Further functions are provided for the interpretation, validation, + * and sanitization of various fields. */ int ParseHttpRequest(struct HttpRequest *r, const char *p, size_t n) { - int c, h; + int c, h, i; struct HttpRequestHeader *x; for (n = MIN(n, LIMIT); r->i < n; ++r->i) { c = p[r->i] & 0xff; @@ -122,14 +132,16 @@ int ParseHttpRequest(struct HttpRequest *r, const char *p, size_t n) { /* fallthrough */ case HVAL: if (c == '\r' || c == '\n') { + i = r->i; + while (i > r->a && (p[i - 1] == ' ' || p[i - 1] == '\t')) --i; if ((h = GetHttpHeader(p + r->k.a, r->k.b - r->k.a)) != -1) { r->headers[h].a = r->a; - r->headers[h].b = r->i; + r->headers[h].b = i; } else if ((x = realloc(r->xheaders.p, (r->xheaders.n + 1) * sizeof(*r->xheaders.p)))) { x[r->xheaders.n].k = r->k; x[r->xheaders.n].v.a = r->a; - x[r->xheaders.n].v.b = r->i; + x[r->xheaders.n].v.b = i; r->xheaders.p = x; ++r->xheaders.n; } diff --git a/net/http/visualizecontrolcodes.c b/net/http/visualizecontrolcodes.c new file mode 100644 index 00000000..d1049731 --- /dev/null +++ b/net/http/visualizecontrolcodes.c @@ -0,0 +1,93 @@ +/*-*- 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 "libc/str/thompike.h" +#include "libc/str/tpenc.h" +#include "net/http/http.h" + +/** + * Filters out control codes from string. + * + * This is useful for logging data like HTTP messages, where we don't + * want full blown C string literal escaping, but we don't want things + * like raw ANSI control codes from untrusted devices in our terminals. + * + * @param data is input value + * @param size if -1 implies strlen + * @param out_size if non-NULL receives output length on success + * @return allocated NUL-terminated buffer, or NULL w/ errno + */ +char *VisualizeControlCodes(const char *data, size_t size, size_t *out_size) { + uint64_t w; + char *r, *q; + unsigned i, n; + wint_t x, a, b; + const char *p, *e; + if (size == -1) size = strlen(data); + if ((r = malloc(size * 6 + 1))) { + q = r; + p = data; + e = p + size; + while (p < e) { + x = *p++ & 0xff; + if (x >= 0300) { + a = ThomPikeByte(x); + n = ThomPikeLen(x) - 1; + if (p + n <= e) { + for (i = 0;;) { + b = p[i] & 0xff; + if (!ThomPikeCont(b)) break; + a = ThomPikeMerge(a, b); + if (++i == n) { + x = a; + p += i; + break; + } + } + } + } + if (0x80 <= x && x < 0xA0) { + q[0] = '\\'; + q[1] = 'u'; + q[2] = '0'; + q[3] = '0'; + q[4] = "0123456789abcdef"[(x & 0xF0) >> 4]; + q[5] = "0123456789abcdef"[(x & 0x0F) >> 0]; + q += 6; + } else { + if (0x00 <= x && x < 0x20) { + if (x != '\t' && x != '\r' && x != '\n') { + x += 0x2400; /* Control Pictures */ + } + } else if (x == 0x7F) { + x = 0x2421; + } + w = tpenc(x); + do { + *q++ = w; + } while ((w >>= 8)); + } + } + if (out_size) *out_size = q - r; + *q++ = '\0'; + if ((q = realloc(r, q - r))) r = q; + } + return r; +} diff --git a/test/net/http/decodelatin1_test.c b/test/net/http/decodelatin1_test.c new file mode 100644 index 00000000..ccb50718 --- /dev/null +++ b/test/net/http/decodelatin1_test.c @@ -0,0 +1,28 @@ +/*-*- 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/testlib/testlib.h" +#include "net/http/http.h" + +size_t n; + +TEST(DecodeLatin1, test) { + EXPECT_STREQ("", DecodeLatin1(NULL, 0, 0)); + EXPECT_STREQ("¥atta", DecodeLatin1("\245atta", -1, &n)); + EXPECT_EQ(6, n); +} diff --git a/test/net/http/encodehttpheadervalue_test.c b/test/net/http/encodehttpheadervalue_test.c new file mode 100644 index 00000000..9df05d12 --- /dev/null +++ b/test/net/http/encodehttpheadervalue_test.c @@ -0,0 +1,82 @@ +/*-*- 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/errno.h" +#include "libc/mem/mem.h" +#include "libc/runtime/gc.internal.h" +#include "libc/stdio/stdio.h" +#include "libc/testlib/ezbench.h" +#include "libc/testlib/testlib.h" +#include "net/http/http.h" + +char *p; +size_t n; + +TEST(EncodeHttpHeaderValue, emptyStrings_arePermitted) { + EXPECT_STREQ("", gc(EncodeHttpHeaderValue(NULL, 0, 0))); + EXPECT_STREQ("", gc(EncodeHttpHeaderValue("", 0, 0))); + EXPECT_STREQ("", gc(EncodeHttpHeaderValue(" ", 1, 0))); +} + +TEST(EncodeHttpHeaderValue, testPadded_trimsWhitespace) { + EXPECT_STREQ("hello \tthere", + gc(EncodeHttpHeaderValue(" \thello \tthere\t ", -1, 0))); +} + +TEST(EncodeHttpHeaderValue, testUtf8_isConvertedToLatin1) { + EXPECT_STREQ("\241\377\300", gc(EncodeHttpHeaderValue("¡ÿÀ", -1, 0))); +} + +TEST(EncodeHttpHeaderValue, testUtf8_nonLatin1ResultsInError) { + EXPECT_EQ(NULL, gc(EncodeHttpHeaderValue("☻", -1, 0))); + EXPECT_EQ(EILSEQ, errno); + EXPECT_EQ(NULL, gc(EncodeHttpHeaderValue("𐌰", -1, 0))); + EXPECT_EQ(EILSEQ, errno); +} + +TEST(EncodeHttpHeaderValue, testLatin1_willJustWorkIfYoureLucky) { + EXPECT_STREQ("\241\377\300", + gc(EncodeHttpHeaderValue("\241\377\300", -1, &n))); + EXPECT_EQ(3, n); + EXPECT_EQ(NULL, gc(EncodeHttpHeaderValue("\377\241\300", -1, 0))); +} + +TEST(EncodeHttpHeaderValue, testC0_isForbiddenExceptHorizontalTab) { + EXPECT_EQ(NULL, gc(EncodeHttpHeaderValue("\0", 1, 0))); + EXPECT_EQ(NULL, gc(EncodeHttpHeaderValue("\r", 1, 0))); + EXPECT_EQ(NULL, gc(EncodeHttpHeaderValue("\n", 1, 0))); + EXPECT_EQ(NULL, gc(EncodeHttpHeaderValue("\v", 1, 0))); + EXPECT_STREQ("", gc(EncodeHttpHeaderValue("\t", 1, 0))); + EXPECT_EQ(NULL, gc(EncodeHttpHeaderValue("\177", 1, 0))); +} + +TEST(EncodeHttpHeaderValue, testC1_isForbidden) { + EXPECT_EQ(NULL, gc(EncodeHttpHeaderValue("\205", 1, 0))); + EXPECT_EQ(NULL, gc(EncodeHttpHeaderValue("\302\205", 2, 0))); +} + +BENCH(EncodeHttpHeaderValue, bench) { + n = 22851; + p = gc(malloc(n)); + memset(p, 'a', n); + EZBENCH2("EncodeHttpHeaderValue ascii", donothing, + free(EncodeHttpHeaderValue(p, n, 0))); + memset(p, '\300', n); + EZBENCH2("EncodeHttpHeaderValue latin1", donothing, + free(EncodeHttpHeaderValue(p, n, 0))); +} diff --git a/test/net/http/parsehttprequest_test.c b/test/net/http/parsehttprequest_test.c index 1a96dc2e..292d97d1 100644 --- a/test/net/http/parsehttprequest_test.c +++ b/test/net/http/parsehttprequest_test.c @@ -49,6 +49,10 @@ void TearDown(void) { DestroyHttpRequest(req); } +/* TEST(ParseHttpRequest, soLittleState) { */ +/* ASSERT_EQ(280, sizeof(struct HttpRequest)); */ +/* } */ + TEST(ParseHttpRequest, testEmpty_tooShort) { EXPECT_EQ(0, ParseHttpRequest(req, "", 0)); } @@ -186,3 +190,12 @@ X-User-Agent: hi\r\n\ EXPECT_STREQ("X-User-Agent", gc(slice(m, req->xheaders.p[0].k))); EXPECT_STREQ("hi", gc(slice(m, req->xheaders.p[0].v))); } + +TEST(ParseHttpRequest, testHeaderValuesWithWhitespace_getsTrimmed) { + static const char m[] = "\ +OPTIONS * HTTP/1.0\r\n\ +User-Agent: \t hi there \t \r\n\ +\r\n"; + EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m))); + EXPECT_STREQ("hi there", gc(slice(m, req->headers[kHttpUserAgent]))); +} diff --git a/test/net/http/visualizecontrolcodes_test.c b/test/net/http/visualizecontrolcodes_test.c new file mode 100644 index 00000000..b307f0b4 --- /dev/null +++ b/test/net/http/visualizecontrolcodes_test.c @@ -0,0 +1,34 @@ +/*-*- 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/testlib/ezbench.h" +#include "libc/testlib/hyperion.h" +#include "libc/testlib/testlib.h" +#include "net/http/http.h" + +TEST(VisualizeControlCodes, test) { + EXPECT_STREQ("hello", VisualizeControlCodes("hello", -1, 0)); + EXPECT_STREQ("hello\r\n", VisualizeControlCodes("hello\r\n", -1, 0)); + EXPECT_STREQ("hello␁␂␡", VisualizeControlCodes("hello\1\2\177", -1, 0)); + EXPECT_STREQ("hello\\u0085", VisualizeControlCodes("hello\302\205", -1, 0)); +} + +BENCH(VisualizeControlCodes, bench) { + EZBENCH2("VisualizeControlCodes", donothing, + free(VisualizeControlCodes(kHyperion, kHyperionSize, 0))); +} diff --git a/tool/net/net.mk b/tool/net/net.mk index 84280d70..b2217901 100644 --- a/tool/net/net.mk +++ b/tool/net/net.mk @@ -68,6 +68,7 @@ o/$(MODE)/tool/net/redbean.com.dbg: \ o/$(MODE)/tool/net/redbean.html.zip.o \ o/$(MODE)/tool/net/redbean.lua.zip.o \ o/$(MODE)/tool/net/redbean-form.lua.zip.o \ + o/$(MODE)/tool/net/redbean-xhr.lua.zip.o \ o/$(MODE)/tool/net/.init.lua.zip.o \ o/$(MODE)/tool/net/.reload.lua.zip.o \ o/$(MODE)/tool/net/net.pkg \ diff --git a/tool/net/redbean-xhr.lua b/tool/net/redbean-xhr.lua new file mode 100644 index 00000000..b4dafd0b --- /dev/null +++ b/tool/net/redbean-xhr.lua @@ -0,0 +1,2 @@ +-- redbean xhr handler demo +SetHeader('X-Custom-Header', 'hello ' .. GetHeader('x-custom-header')) diff --git a/tool/net/redbean.c b/tool/net/redbean.c index 5fa11008..fc7fe421 100644 --- a/tool/net/redbean.c +++ b/tool/net/redbean.c @@ -244,6 +244,8 @@ struct Parser { int c; const char *data; int size; + bool isform; + bool islatin1; char *p; char *q; }; @@ -296,6 +298,7 @@ static bool heartless; static bool printport; static bool heartbeat; static bool daemonize; +static bool logbodies; static bool terminated; static bool uniprocess; static bool invalidated; @@ -316,12 +319,12 @@ static uint32_t clientaddrsize; static lua_State *L; static void *content; -static uint8_t *zmap; static uint8_t *zdir; +static uint8_t *zmap; static size_t hdrsize; static size_t msgsize; static size_t amtread; -static size_t zmapsize; +static size_t zsize; static char *luaheaderp; static const char *pidpath; static const char *logpath; @@ -513,7 +516,7 @@ static wontreturn void PrintUsage(FILE *f, int rc) { static void GetOpts(int argc, char *argv[]) { int opt; - while ((opt = getopt(argc, argv, "zhduvml:p:w:r:c:L:P:U:G:B:")) != -1) { + while ((opt = getopt(argc, argv, "zhduvmbl:p:w:r:c:L:P:U:G:B:")) != -1) { switch (opt) { case 'v': __log_level++; @@ -527,6 +530,9 @@ static void GetOpts(int argc, char *argv[]) { case 'm': logmessages = true; break; + case 'b': + logbodies = true; + break; case 'z': printport = true; break; @@ -543,7 +549,7 @@ static void GetOpts(int argc, char *argv[]) { CHECK_EQ(1, inet_pton(AF_INET, optarg, &serveraddr.sin_addr)); break; case 'B': - serverheader = optarg; + serverheader = emptytonull(EncodeHttpHeaderValue(optarg, -1, 0)); break; case 'L': logpath = optarg; @@ -777,13 +783,18 @@ static void IndexAssets(void) { static void OpenZip(const char *path) { int fd; + uint8_t *p; struct stat st; CHECK_NE(-1, (fd = open(path, O_RDONLY))); CHECK_NE(-1, fstat(fd, &st)); - CHECK((zmapsize = st.st_size)); + CHECK((zsize = st.st_size)); CHECK_NE(MAP_FAILED, - (zmap = mmap(NULL, zmapsize, PROT_READ, MAP_SHARED, fd, 0))); - CHECK_NOTNULL((zdir = zipfindcentraldir(zmap, zmapsize))); + (zmap = mmap(NULL, zsize, PROT_READ, MAP_SHARED, fd, 0))); + CHECK_NOTNULL((zdir = zipfindcentraldir(zmap, zsize))); + if (endswith(path, ".com.dbg") && (p = memmem(zmap, zsize, "MZqFpD", 6))) { + zsize -= p - zmap; + zmap = p; + } close(fd); } @@ -856,6 +867,11 @@ static void EmitParamVal(struct Parser *u, struct Params *h, bool t) { } } +static void ParseLatin1(struct Parser *u) { + *u->p++ = 0300 | u->c >> 6; + *u->p++ = 0200 | u->c & 077; +} + static void ParseEscape(struct Parser *u) { int a, b; a = u->i < u->size ? u->data[u->i++] & 0xff : 0; @@ -868,10 +884,12 @@ static void ParsePath(struct Parser *u, struct Buffer *h) { u->c = u->data[u->i++] & 0xff; if (u->c == '#' || u->c == '?') { break; - } else if (u->c != '%') { - *u->p++ = u->c; - } else { + } else if (u->c == '%') { ParseEscape(u); + } else if (u->c >= 0200 && u->islatin1) { + ParseLatin1(u); + } else { + *u->p++ = u->c; } } h->p = u->q; @@ -879,48 +897,46 @@ static void ParsePath(struct Parser *u, struct Buffer *h) { u->q = u->p; } -static void ParseParams(struct Parser *u, struct Params *h, bool isform) { +static void ParseParams(struct Parser *u, struct Params *h) { bool t = false; while (u->i < u->size) { - switch ((u->c = u->data[u->i++] & 0xff)) { - default: - *u->p++ = u->c; - break; - case '+': - *u->p++ = isform ? ' ' : '+'; - break; - case '%': - ParseEscape(u); - break; - case '&': - EmitParamVal(u, h, t); - t = false; - break; - case '=': - if (!t) { - if (u->p > u->q) { - EmitParamKey(u, h); - t = true; - } - } else { - *u->p++ = '='; + u->c = u->data[u->i++] & 0xff; + if (u->c == '#') { + break; + } else if (u->c == '%') { + ParseEscape(u); + } else if (u->c == '+') { + *u->p++ = u->isform ? ' ' : '+'; + } else if (u->c == '&') { + EmitParamVal(u, h, t); + t = false; + } else if (u->c == '=') { + if (!t) { + if (u->p > u->q) { + EmitParamKey(u, h); + t = true; } - break; - case '#': - goto EndOfParams; + } else { + *u->p++ = '='; + } + } else if (u->c >= 0200 && u->islatin1) { + ParseLatin1(u); + } else { + *u->p++ = u->c; } } -EndOfParams: EmitParamVal(u, h, t); } static void ParseFragment(struct Parser *u, struct Buffer *h) { while (u->i < u->size) { u->c = u->data[u->i++] & 0xff; - if (u->c != '%') { - *u->p++ = u->c; - } else { + if (u->c == '%') { ParseEscape(u); + } else if (u->c >= 0200 && u->islatin1) { + ParseLatin1(u); + } else { + *u->p++ = u->c; } } h->p = u->q; @@ -936,13 +952,15 @@ static bool ParseRequestUri(void) { struct Parser u; u.i = 0; u.c = '/'; + u.isform = false; + u.islatin1 = true; u.data = inbuf.p + msg.uri.a; u.size = msg.uri.b - msg.uri.a; memset(&request, 0, sizeof(request)); if (!u.size || *u.data != '/') return false; - u.q = u.p = FreeLater(xmalloc(u.size)); + u.q = u.p = FreeLater(xmalloc(u.size * 2)); if (u.c == '/') ParsePath(&u, &request.path); - if (u.c == '?') ParseParams(&u, &request.params, false); + if (u.c == '?') ParseParams(&u, &request.params); if (u.c == '#') ParseFragment(&u, &request.fragment); return u.i == u.size && !IsForbiddenPath(&request.path); } @@ -951,18 +969,20 @@ static void ParseFormParams(void) { struct Parser u; u.i = 0; u.c = 0; + u.isform = true; + u.islatin1 = false; u.data = inbuf.p + hdrsize; u.size = msgsize - hdrsize; u.q = u.p = FreeLater(xmalloc(u.size)); - ParseParams(&u, &request.params, true); + ParseParams(&u, &request.params); } static void *AddRange(char *content, long start, long length) { intptr_t mend, mstart; if (!__builtin_add_overflow((intptr_t)content, start, &mstart) || !__builtin_add_overflow(mstart, length, &mend) || - ((intptr_t)zmap <= mstart && mstart <= (intptr_t)zmap + zmapsize) || - ((intptr_t)zmap <= mend && mend <= (intptr_t)zmap + zmapsize)) { + ((intptr_t)zmap <= mstart && mstart <= (intptr_t)zmap + zsize) || + ((intptr_t)zmap <= mend && mend <= (intptr_t)zmap + zsize)) { return (void *)mstart; } else { abort(); @@ -1002,6 +1022,7 @@ static char *SetStatus(int code, const char *reason) { } static char *AppendHeader(char *p, const char *k, const char *v) { + if (!v) return p; return AppendCrlf(stpcpy(AppendHeaderName(p, k), v)); } @@ -1213,43 +1234,6 @@ static char *ServeAsset(struct Asset *a, const char *path, size_t pathlen) { return p; } -static bool IsValidFieldName(const char *s, size_t n) { - size_t i; - if (!n) return false; - for (i = 0; i < n; ++i) { - if (!(0x20 < s[i] && s[i] < 0x7F) || s[i] == ':') { - return false; - } - } - return true; -} - -static bool IsValidFieldContent(const char *s, size_t n) { - size_t i; - for (i = 0; i < n; ++i) { - if (!(0x20 <= s[i] && s[i] < 0x7F)) { - return false; - } - } - return true; -} - -static int LuaIsValidFieldName(lua_State *L) { - size_t size; - const char *data; - data = luaL_checklstring(L, 1, &size); - lua_pushboolean(L, IsValidFieldName(data, size)); - return 1; -} - -static int LuaIsValidFieldContent(lua_State *L) { - size_t size; - const char *data; - data = luaL_checklstring(L, 1, &size); - lua_pushboolean(L, IsValidFieldContent(data, size)); - return 1; -} - static int LuaServeAsset(lua_State *L) { size_t pathlen; struct Asset *a; @@ -1263,6 +1247,7 @@ static int LuaServeAsset(lua_State *L) { } static int LuaRespond(lua_State *L, char *respond(int, const char *)) { + char *p; int code; size_t reasonlen; const char *reason; @@ -1271,14 +1256,16 @@ static int LuaRespond(lua_State *L, char *respond(int, const char *)) { return luaL_argerror(L, 1, "bad status code"); } if (lua_isnoneornil(L, 2)) { - reason = GetHttpReason(code); + luaheaderp = respond(code, GetHttpReason(code)); } else { reason = lua_tolstring(L, 2, &reasonlen); - if (reasonlen > 128 || !IsValidFieldContent(reason, reasonlen)) { + if (reasonlen < 128 && (p = EncodeHttpHeaderValue(reason, reasonlen, 0))) { + luaheaderp = respond(code, p); + free(p); + } else { return luaL_argerror(L, 2, "invalid"); } } - luaheaderp = respond(code, reason); return 0; } @@ -1368,22 +1355,29 @@ static int LuaGetPayload(lua_State *L) { return 1; } +static void LuaPushLatin1(lua_State *L, const char *s, size_t n) { + char *t; + size_t m; + t = DecodeLatin1(s, n, &m); + lua_pushlstring(L, t, m); + free(t); +} + static int LuaGetHeader(lua_State *L) { int h; - const char *key, *val; - size_t i, keylen, vallen; + const char *key; + size_t i, keylen; key = luaL_checklstring(L, 1, &keylen); if ((h = GetHttpHeader(key, keylen)) != -1) { - val = inbuf.p + msg.headers[h].a; - vallen = msg.headers[h].b - msg.headers[h].a; - lua_pushlstring(L, val, vallen); + LuaPushLatin1(L, inbuf.p + msg.headers[h].a, + msg.headers[h].b - msg.headers[h].a); return 1; } for (i = 0; i < msg.xheaders.n; ++i) { if (!CompareSlicesCase(key, keylen, inbuf.p + msg.xheaders.p[i].k.a, msg.xheaders.p[i].k.b - msg.xheaders.p[i].k.a)) { - lua_pushlstring(L, inbuf.p + msg.xheaders.p[i].v.a, - msg.xheaders.p[i].v.b - msg.xheaders.p[i].v.a); + LuaPushLatin1(L, inbuf.p + msg.xheaders.p[i].v.a, + msg.xheaders.p[i].v.b - msg.xheaders.p[i].v.a); return 1; } } @@ -1393,42 +1387,47 @@ static int LuaGetHeader(lua_State *L) { static int LuaGetHeaders(lua_State *L) { size_t i; + char *name; lua_newtable(L); for (i = 0; i < kHttpHeadersMax; ++i) { if (msg.headers[i].b - msg.headers[i].a) { - lua_pushlstring(L, inbuf.p + msg.headers[i].a, - msg.headers[i].b - msg.headers[i].a); + LuaPushLatin1(L, inbuf.p + msg.headers[i].a, + msg.headers[i].b - msg.headers[i].a); lua_setfield(L, -2, GetHttpHeaderName(i)); } } for (i = 0; i < msg.xheaders.n; ++i) { - lua_pushlstring(L, inbuf.p + msg.xheaders.p[i].v.a, - msg.xheaders.p[i].v.b - msg.xheaders.p[i].v.a); - lua_setfield( - L, -2, - FreeLater(strndup(inbuf.p + msg.xheaders.p[i].k.a, - msg.xheaders.p[i].k.b - msg.xheaders.p[i].k.a))); + LuaPushLatin1(L, inbuf.p + msg.xheaders.p[i].v.a, + msg.xheaders.p[i].v.b - msg.xheaders.p[i].v.a); + lua_setfield(L, -2, + (name = DecodeLatin1( + inbuf.p + msg.xheaders.p[i].k.a, + msg.xheaders.p[i].k.b - msg.xheaders.p[i].k.a, 0))); + free(name); } return 1; } static int LuaSetHeader(lua_State *L) { + int h; char *p; ssize_t rc; - const char *key, *val; - size_t i, keylen, vallen; + const char *key, *val, *eval; + size_t i, keylen, vallen, evallen; key = luaL_checklstring(L, 1, &keylen); val = luaL_checklstring(L, 2, &vallen); - if (!IsValidFieldName(key, keylen)) { - return luaL_argerror(L, 1, "invalid"); + if ((h = GetHttpHeader(key, keylen)) == -1) { + if (!IsValidHttpToken(key, keylen)) { + return luaL_argerror(L, 1, "invalid"); + } } - if (!IsValidFieldContent(val, vallen)) { + if (!(eval = EncodeHttpHeaderValue(val, vallen, &evallen))) { return luaL_argerror(L, 2, "invalid"); } if (!luaheaderp) { p = SetStatus(200, "OK"); } else { - while (luaheaderp - hdrbuf.p + keylen + 2 + vallen + 2 + 512 > hdrbuf.n) { + while (luaheaderp - hdrbuf.p + keylen + 2 + evallen + 2 + 512 > hdrbuf.n) { hdrbuf.n += hdrbuf.n >> 1; p = xrealloc(hdrbuf.p, hdrbuf.n); luaheaderp = p + (luaheaderp - hdrbuf.p); @@ -1436,30 +1435,31 @@ static int LuaSetHeader(lua_State *L) { } p = luaheaderp; } - switch (GetHttpHeader(key, keylen)) { + switch (h) { case kHttpDate: case kHttpContentRange: case kHttpContentLength: case kHttpContentEncoding: return luaL_argerror(L, 1, "abstracted"); case kHttpConnection: - if (vallen != 5 || memcmp(val, "close", 5)) { + if (evallen != 5 || memcmp(eval, "close", 5)) { return luaL_argerror(L, 2, "unsupported"); } connectionclose = true; break; case kHttpContentType: - p = AppendContentType(p, val); + p = AppendContentType(p, eval); break; case kHttpServer: branded = true; - p = AppendHeader(p, key, val); + p = AppendHeader(p, "Server", eval); break; default: - p = AppendHeader(p, key, val); + p = AppendHeader(p, key, eval); break; } luaheaderp = p; + free(eval); return 0; } @@ -1579,6 +1579,17 @@ static int LuaDecodeBase64(lua_State *L) { return 1; } +static int LuaVisualizeControlCodes(lua_State *L) { + char *p; + size_t size, n; + const char *data; + data = luaL_checklstring(L, 1, &size); + p = VisualizeControlCodes(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; @@ -1619,41 +1630,39 @@ static void LuaRun(const char *path) { } static const luaL_Reg kLuaFuncs[] = { - {"DecodeBase64", LuaDecodeBase64}, // - {"EncodeBase64", LuaEncodeBase64}, // - {"EscapeFragment", LuaEscapeFragment}, // - {"EscapeHtml", LuaEscapeHtml}, // - {"EscapeLiteral", LuaEscapeLiteral}, // - {"EscapeParam", LuaEscapeParam}, // - {"EscapePath", LuaEscapePath}, // - {"EscapeSegment", LuaEscapeSegment}, // - {"FormatDate", LuaFormatDate}, // - {"GetClientAddr", LuaGetClientAddr}, // - {"GetDate", LuaGetDate}, // - {"GetFragment", LuaGetFragment}, // - {"GetHeader", LuaGetHeader}, // - {"GetHeaders", LuaGetHeaders}, // - {"GetMethod", LuaGetMethod}, // - {"GetParam", LuaGetParam}, // - {"GetParams", LuaGetParams}, // - {"GetPath", LuaGetPath}, // - {"GetPayload", LuaGetPayload}, // - {"GetServerAddr", LuaGetServerAddr}, // - {"GetUri", LuaGetUri}, // - {"GetVersion", LuaGetVersion}, // - {"HasParam", LuaHasParam}, // - {"IsValidFieldContent", LuaIsValidFieldContent}, // - {"IsValidFieldName", LuaIsValidFieldName}, // - {"LoadAsset", LuaLoadAsset}, // - {"ParseDate", LuaParseDate}, // - {"ServeAsset", LuaServeAsset}, // - {"ServeError", LuaServeError}, // - {"SetHeader", LuaSetHeader}, // - {"SetStatus", LuaSetStatus}, // - {"Write", LuaWrite}, // - {"bsf", LuaBsf}, // - {"bsr", LuaBsr}, // - {"popcnt", LuaPopcnt}, // + {"DecodeBase64", LuaDecodeBase64}, // + {"EncodeBase64", LuaEncodeBase64}, // + {"EscapeFragment", LuaEscapeFragment}, // + {"EscapeHtml", LuaEscapeHtml}, // + {"EscapeLiteral", LuaEscapeLiteral}, // + {"EscapeParam", LuaEscapeParam}, // + {"EscapePath", LuaEscapePath}, // + {"EscapeSegment", LuaEscapeSegment}, // + {"FormatDate", LuaFormatDate}, // + {"GetClientAddr", LuaGetClientAddr}, // + {"GetDate", LuaGetDate}, // + {"GetFragment", LuaGetFragment}, // + {"GetHeader", LuaGetHeader}, // + {"GetHeaders", LuaGetHeaders}, // + {"GetMethod", LuaGetMethod}, // + {"GetParam", LuaGetParam}, // + {"GetParams", LuaGetParams}, // + {"GetPath", LuaGetPath}, // + {"GetPayload", LuaGetPayload}, // + {"GetServerAddr", LuaGetServerAddr}, // + {"GetUri", LuaGetUri}, // + {"GetVersion", LuaGetVersion}, // + {"HasParam", LuaHasParam}, // + {"LoadAsset", LuaLoadAsset}, // + {"ParseDate", LuaParseDate}, // + {"ServeAsset", LuaServeAsset}, // + {"ServeError", LuaServeError}, // + {"SetHeader", LuaSetHeader}, // + {"SetStatus", LuaSetStatus}, // + {"Write", LuaWrite}, // + {"bsf", LuaBsf}, // + {"bsr", LuaBsr}, // + {"popcnt", LuaPopcnt}, // }; static void LuaSetArgv(void) { @@ -1748,15 +1757,35 @@ static char *HandleRedirect(struct Redirect *r) { kHttpMethod[msg.method], request.path.n, request.path.p, r->location); p = SetStatus(307, "Temporary Redirect"); - p = AppendHeader(p, "Location", r->location); + p = AppendHeader(p, "Location", + FreeLater(EncodeHttpHeaderValue(r->location, -1, 0))); } return p; } static void LogMessage(const char *d, const char *s, size_t n) { + char *s2, *s3; + size_t n2, n3; if (!logmessages) return; while (n && (s[n - 1] == '\r' || s[n - 1] == '\n')) --n; - LOGF("%s %s %,ld byte message\n%.*s", clientaddrstr, d, n, n, s); + if ((s2 = DecodeLatin1(s, n, &n2))) { + if ((s3 = VisualizeControlCodes(s2, n2, &n3))) { + LOGF("%s %s %,ld byte message\n%.*s", clientaddrstr, d, n, n3, s3); + free(s3); + } + free(s2); + } +} + +static void LogBody(const char *d, const char *s, size_t n) { + char *s2; + size_t n2; + if (!n || !logbodies) return; + while (n && (s[n - 1] == '\r' || s[n - 1] == '\n')) --n; + if ((s2 = VisualizeControlCodes(s, n, &n2))) { + LOGF("%s %s %,ld byte payload\n%.*s", clientaddrstr, d, n, n2, s2); + free(s2); + } } static ssize_t SendMessageString(const char *s) { @@ -1872,6 +1901,7 @@ static char *HandleMessage(void) { } } msgsize = need; /* we are now synchronized */ + LogBody("received", inbuf.p + hdrsize, msgsize - hdrsize); if (httpversion != 101 || IsConnectionClose()) { connectionclose = true; } diff --git a/tool/net/redbean.lua b/tool/net/redbean.lua index df8b62d2..b0846116 100644 --- a/tool/net/redbean.lua +++ b/tool/net/redbean.lua @@ -58,16 +58,38 @@ local function main() Write('

none\n') end - Write('

post request html form demo

\n') - Write('
\n') - Write('\n') - Write('\n') - Write('
\n') - Write('\n') - Write('\n') - Write('
\n') - Write('\n') - Write('
\n') + Write([[ +

post request html form demo

+
+ + +
+ + +
+ +
+ ]]) + + Write([[ +

xmlhttprequest request demo

+ +
+
+
+ + ]]) end main()