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.
This commit is contained in:
Justine Tunney
2021-03-28 00:10:17 -07:00
parent dcbd2b8668
commit a1677d605a
14 changed files with 675 additions and 161 deletions

54
net/http/decodelatin1.c Normal file
View File

@ -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;
}

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/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;
}

View File

@ -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) */

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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)));
}

View File

@ -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])));
}

View File

@ -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)));
}

View File

@ -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 \

2
tool/net/redbean-xhr.lua Normal file
View File

@ -0,0 +1,2 @@
-- redbean xhr handler demo
SetHeader('X-Custom-Header', 'hello ' .. GetHeader('x-custom-header'))

View File

@ -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,24 +897,20 @@ 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;
u->c = u->data[u->i++] & 0xff;
if (u->c == '#') {
break;
case '+':
*u->p++ = isform ? ' ' : '+';
break;
case '%':
} else if (u->c == '%') {
ParseEscape(u);
break;
case '&':
} else if (u->c == '+') {
*u->p++ = u->isform ? ' ' : '+';
} else if (u->c == '&') {
EmitParamVal(u, h, t);
t = false;
break;
case '=':
} else if (u->c == '=') {
if (!t) {
if (u->p > u->q) {
EmitParamKey(u, h);
@ -905,22 +919,24 @@ static void ParseParams(struct Parser *u, struct Params *h, bool isform) {
} else {
*u->p++ = '=';
}
break;
case '#':
goto EndOfParams;
} 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,21 +1355,28 @@ 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,
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,
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,
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,
FreeLater(strndup(inbuf.p + msg.xheaders.p[i].k.a,
msg.xheaders.p[i].k.b - msg.xheaders.p[i].k.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)) {
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;
@ -1642,8 +1653,6 @@ static const luaL_Reg kLuaFuncs[] = {
{"GetUri", LuaGetUri}, //
{"GetVersion", LuaGetVersion}, //
{"HasParam", LuaHasParam}, //
{"IsValidFieldContent", LuaIsValidFieldContent}, //
{"IsValidFieldName", LuaIsValidFieldName}, //
{"LoadAsset", LuaLoadAsset}, //
{"ParseDate", LuaParseDate}, //
{"ServeAsset", LuaServeAsset}, //
@ -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;
}

View File

@ -58,16 +58,38 @@ local function main()
Write('<p><em>none</em>\n')
end
Write('<h3>post request html form demo</h3>\n')
Write('<form action="/tool/net/redbean-form.lua" method="post">\n')
Write('<input type="text" id="firstname" name="firstname">\n')
Write('<label for="firstname">first name</label>\n')
Write('<br>\n')
Write('<input type="text" id="lastname" name="lastname">\n')
Write('<label for="lastname">last name</label>\n')
Write('<br>\n')
Write('<input type="submit" value="Submit">\n')
Write('</form>\n')
Write([[
<h3>post request html form demo</h3>
<form action="/tool/net/redbean-form.lua" method="post">
<input type="text" id="firstname" name="firstname">
<label for="firstname">first name</label>
<br>
<input type="text" id="lastname" name="lastname">
<label for="lastname">last name</label>
<br>
<input type="submit" value="Submit">
</form>
]])
Write([[
<h3>xmlhttprequest request demo</h3>
<input id="x" value="lâtìn1">
<label for="x">name</label><br>
<button id="send">send (via http header)</button><br>
<div id="result"></div>
<script>
function OnSend() {
var r = new XMLHttpRequest();
r.onload = function() {
document.getElementById("result").innerText = this.getResponseHeader('X-Custom-Header');
};
r.open('POST', '/tool/net/redbean-xhr.lua');
r.setRequestHeader('X-Custom-Header', document.getElementById('x').value);
r.send();
}
document.getElementById('send').onclick = OnSend;
</script>
]])
end
main()