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

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()