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); int64_t ParseHttpDateTime(const char *, size_t);
const char *GetHttpReason(int); const char *GetHttpReason(int);
const char *GetHttpHeaderName(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_ COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #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. * 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 ParseHttpRequest(struct HttpRequest *r, const char *p, size_t n) {
int c, h; int c, h, i;
struct HttpRequestHeader *x; struct HttpRequestHeader *x;
for (n = MIN(n, LIMIT); r->i < n; ++r->i) { for (n = MIN(n, LIMIT); r->i < n; ++r->i) {
c = p[r->i] & 0xff; c = p[r->i] & 0xff;
@ -122,14 +132,16 @@ int ParseHttpRequest(struct HttpRequest *r, const char *p, size_t n) {
/* fallthrough */ /* fallthrough */
case HVAL: case HVAL:
if (c == '\r' || c == '\n') { 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) { if ((h = GetHttpHeader(p + r->k.a, r->k.b - r->k.a)) != -1) {
r->headers[h].a = r->a; 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) * } else if ((x = realloc(r->xheaders.p, (r->xheaders.n + 1) *
sizeof(*r->xheaders.p)))) { sizeof(*r->xheaders.p)))) {
x[r->xheaders.n].k = r->k; x[r->xheaders.n].k = r->k;
x[r->xheaders.n].v.a = r->a; 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.p = x;
++r->xheaders.n; ++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); DestroyHttpRequest(req);
} }
/* TEST(ParseHttpRequest, soLittleState) { */
/* ASSERT_EQ(280, sizeof(struct HttpRequest)); */
/* } */
TEST(ParseHttpRequest, testEmpty_tooShort) { TEST(ParseHttpRequest, testEmpty_tooShort) {
EXPECT_EQ(0, ParseHttpRequest(req, "", 0)); 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("X-User-Agent", gc(slice(m, req->xheaders.p[0].k)));
EXPECT_STREQ("hi", gc(slice(m, req->xheaders.p[0].v))); 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.html.zip.o \
o/$(MODE)/tool/net/redbean.lua.zip.o \ o/$(MODE)/tool/net/redbean.lua.zip.o \
o/$(MODE)/tool/net/redbean-form.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/.init.lua.zip.o \
o/$(MODE)/tool/net/.reload.lua.zip.o \ o/$(MODE)/tool/net/.reload.lua.zip.o \
o/$(MODE)/tool/net/net.pkg \ 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; int c;
const char *data; const char *data;
int size; int size;
bool isform;
bool islatin1;
char *p; char *p;
char *q; char *q;
}; };
@ -296,6 +298,7 @@ static bool heartless;
static bool printport; static bool printport;
static bool heartbeat; static bool heartbeat;
static bool daemonize; static bool daemonize;
static bool logbodies;
static bool terminated; static bool terminated;
static bool uniprocess; static bool uniprocess;
static bool invalidated; static bool invalidated;
@ -316,12 +319,12 @@ static uint32_t clientaddrsize;
static lua_State *L; static lua_State *L;
static void *content; static void *content;
static uint8_t *zmap;
static uint8_t *zdir; static uint8_t *zdir;
static uint8_t *zmap;
static size_t hdrsize; static size_t hdrsize;
static size_t msgsize; static size_t msgsize;
static size_t amtread; static size_t amtread;
static size_t zmapsize; static size_t zsize;
static char *luaheaderp; static char *luaheaderp;
static const char *pidpath; static const char *pidpath;
static const char *logpath; static const char *logpath;
@ -513,7 +516,7 @@ static wontreturn void PrintUsage(FILE *f, int rc) {
static void GetOpts(int argc, char *argv[]) { static void GetOpts(int argc, char *argv[]) {
int opt; 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) { switch (opt) {
case 'v': case 'v':
__log_level++; __log_level++;
@ -527,6 +530,9 @@ static void GetOpts(int argc, char *argv[]) {
case 'm': case 'm':
logmessages = true; logmessages = true;
break; break;
case 'b':
logbodies = true;
break;
case 'z': case 'z':
printport = true; printport = true;
break; break;
@ -543,7 +549,7 @@ static void GetOpts(int argc, char *argv[]) {
CHECK_EQ(1, inet_pton(AF_INET, optarg, &serveraddr.sin_addr)); CHECK_EQ(1, inet_pton(AF_INET, optarg, &serveraddr.sin_addr));
break; break;
case 'B': case 'B':
serverheader = optarg; serverheader = emptytonull(EncodeHttpHeaderValue(optarg, -1, 0));
break; break;
case 'L': case 'L':
logpath = optarg; logpath = optarg;
@ -777,13 +783,18 @@ static void IndexAssets(void) {
static void OpenZip(const char *path) { static void OpenZip(const char *path) {
int fd; int fd;
uint8_t *p;
struct stat st; struct stat st;
CHECK_NE(-1, (fd = open(path, O_RDONLY))); CHECK_NE(-1, (fd = open(path, O_RDONLY)));
CHECK_NE(-1, fstat(fd, &st)); CHECK_NE(-1, fstat(fd, &st));
CHECK((zmapsize = st.st_size)); CHECK((zsize = st.st_size));
CHECK_NE(MAP_FAILED, CHECK_NE(MAP_FAILED,
(zmap = mmap(NULL, zmapsize, PROT_READ, MAP_SHARED, fd, 0))); (zmap = mmap(NULL, zsize, PROT_READ, MAP_SHARED, fd, 0)));
CHECK_NOTNULL((zdir = zipfindcentraldir(zmap, zmapsize))); CHECK_NOTNULL((zdir = zipfindcentraldir(zmap, zsize)));
if (endswith(path, ".com.dbg") && (p = memmem(zmap, zsize, "MZqFpD", 6))) {
zsize -= p - zmap;
zmap = p;
}
close(fd); 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) { static void ParseEscape(struct Parser *u) {
int a, b; int a, b;
a = u->i < u->size ? u->data[u->i++] & 0xff : 0; 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; u->c = u->data[u->i++] & 0xff;
if (u->c == '#' || u->c == '?') { if (u->c == '#' || u->c == '?') {
break; break;
} else if (u->c != '%') { } else if (u->c == '%') {
*u->p++ = u->c;
} else {
ParseEscape(u); ParseEscape(u);
} else if (u->c >= 0200 && u->islatin1) {
ParseLatin1(u);
} else {
*u->p++ = u->c;
} }
} }
h->p = u->q; h->p = u->q;
@ -879,24 +897,20 @@ static void ParsePath(struct Parser *u, struct Buffer *h) {
u->q = u->p; 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; bool t = false;
while (u->i < u->size) { while (u->i < u->size) {
switch ((u->c = u->data[u->i++] & 0xff)) { u->c = u->data[u->i++] & 0xff;
default: if (u->c == '#') {
*u->p++ = u->c;
break; break;
case '+': } else if (u->c == '%') {
*u->p++ = isform ? ' ' : '+';
break;
case '%':
ParseEscape(u); ParseEscape(u);
break; } else if (u->c == '+') {
case '&': *u->p++ = u->isform ? ' ' : '+';
} else if (u->c == '&') {
EmitParamVal(u, h, t); EmitParamVal(u, h, t);
t = false; t = false;
break; } else if (u->c == '=') {
case '=':
if (!t) { if (!t) {
if (u->p > u->q) { if (u->p > u->q) {
EmitParamKey(u, h); EmitParamKey(u, h);
@ -905,22 +919,24 @@ static void ParseParams(struct Parser *u, struct Params *h, bool isform) {
} else { } else {
*u->p++ = '='; *u->p++ = '=';
} }
break; } else if (u->c >= 0200 && u->islatin1) {
case '#': ParseLatin1(u);
goto EndOfParams; } else {
*u->p++ = u->c;
} }
} }
EndOfParams:
EmitParamVal(u, h, t); EmitParamVal(u, h, t);
} }
static void ParseFragment(struct Parser *u, struct Buffer *h) { static void ParseFragment(struct Parser *u, struct Buffer *h) {
while (u->i < u->size) { while (u->i < u->size) {
u->c = u->data[u->i++] & 0xff; u->c = u->data[u->i++] & 0xff;
if (u->c != '%') { if (u->c == '%') {
*u->p++ = u->c;
} else {
ParseEscape(u); ParseEscape(u);
} else if (u->c >= 0200 && u->islatin1) {
ParseLatin1(u);
} else {
*u->p++ = u->c;
} }
} }
h->p = u->q; h->p = u->q;
@ -936,13 +952,15 @@ static bool ParseRequestUri(void) {
struct Parser u; struct Parser u;
u.i = 0; u.i = 0;
u.c = '/'; u.c = '/';
u.isform = false;
u.islatin1 = true;
u.data = inbuf.p + msg.uri.a; u.data = inbuf.p + msg.uri.a;
u.size = msg.uri.b - msg.uri.a; u.size = msg.uri.b - msg.uri.a;
memset(&request, 0, sizeof(request)); memset(&request, 0, sizeof(request));
if (!u.size || *u.data != '/') return false; 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 == '/') 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); if (u.c == '#') ParseFragment(&u, &request.fragment);
return u.i == u.size && !IsForbiddenPath(&request.path); return u.i == u.size && !IsForbiddenPath(&request.path);
} }
@ -951,18 +969,20 @@ static void ParseFormParams(void) {
struct Parser u; struct Parser u;
u.i = 0; u.i = 0;
u.c = 0; u.c = 0;
u.isform = true;
u.islatin1 = false;
u.data = inbuf.p + hdrsize; u.data = inbuf.p + hdrsize;
u.size = msgsize - hdrsize; u.size = msgsize - hdrsize;
u.q = u.p = FreeLater(xmalloc(u.size)); 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) { static void *AddRange(char *content, long start, long length) {
intptr_t mend, mstart; intptr_t mend, mstart;
if (!__builtin_add_overflow((intptr_t)content, start, &mstart) || if (!__builtin_add_overflow((intptr_t)content, start, &mstart) ||
!__builtin_add_overflow(mstart, length, &mend) || !__builtin_add_overflow(mstart, length, &mend) ||
((intptr_t)zmap <= mstart && mstart <= (intptr_t)zmap + zmapsize) || ((intptr_t)zmap <= mstart && mstart <= (intptr_t)zmap + zsize) ||
((intptr_t)zmap <= mend && mend <= (intptr_t)zmap + zmapsize)) { ((intptr_t)zmap <= mend && mend <= (intptr_t)zmap + zsize)) {
return (void *)mstart; return (void *)mstart;
} else { } else {
abort(); abort();
@ -1002,6 +1022,7 @@ static char *SetStatus(int code, const char *reason) {
} }
static char *AppendHeader(char *p, const char *k, const char *v) { static char *AppendHeader(char *p, const char *k, const char *v) {
if (!v) return p;
return AppendCrlf(stpcpy(AppendHeaderName(p, k), v)); 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; 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) { static int LuaServeAsset(lua_State *L) {
size_t pathlen; size_t pathlen;
struct Asset *a; struct Asset *a;
@ -1263,6 +1247,7 @@ static int LuaServeAsset(lua_State *L) {
} }
static int LuaRespond(lua_State *L, char *respond(int, const char *)) { static int LuaRespond(lua_State *L, char *respond(int, const char *)) {
char *p;
int code; int code;
size_t reasonlen; size_t reasonlen;
const char *reason; 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"); return luaL_argerror(L, 1, "bad status code");
} }
if (lua_isnoneornil(L, 2)) { if (lua_isnoneornil(L, 2)) {
reason = GetHttpReason(code); luaheaderp = respond(code, GetHttpReason(code));
} else { } else {
reason = lua_tolstring(L, 2, &reasonlen); 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"); return luaL_argerror(L, 2, "invalid");
} }
} }
luaheaderp = respond(code, reason);
return 0; return 0;
} }
@ -1368,21 +1355,28 @@ static int LuaGetPayload(lua_State *L) {
return 1; 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) { static int LuaGetHeader(lua_State *L) {
int h; int h;
const char *key, *val; const char *key;
size_t i, keylen, vallen; size_t i, keylen;
key = luaL_checklstring(L, 1, &keylen); key = luaL_checklstring(L, 1, &keylen);
if ((h = GetHttpHeader(key, keylen)) != -1) { if ((h = GetHttpHeader(key, keylen)) != -1) {
val = inbuf.p + msg.headers[h].a; LuaPushLatin1(L, inbuf.p + msg.headers[h].a,
vallen = msg.headers[h].b - msg.headers[h].a; msg.headers[h].b - msg.headers[h].a);
lua_pushlstring(L, val, vallen);
return 1; return 1;
} }
for (i = 0; i < msg.xheaders.n; ++i) { for (i = 0; i < msg.xheaders.n; ++i) {
if (!CompareSlicesCase(key, keylen, inbuf.p + msg.xheaders.p[i].k.a, if (!CompareSlicesCase(key, keylen, inbuf.p + msg.xheaders.p[i].k.a,
msg.xheaders.p[i].k.b - 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); msg.xheaders.p[i].v.b - msg.xheaders.p[i].v.a);
return 1; return 1;
} }
@ -1393,42 +1387,47 @@ static int LuaGetHeader(lua_State *L) {
static int LuaGetHeaders(lua_State *L) { static int LuaGetHeaders(lua_State *L) {
size_t i; size_t i;
char *name;
lua_newtable(L); lua_newtable(L);
for (i = 0; i < kHttpHeadersMax; ++i) { for (i = 0; i < kHttpHeadersMax; ++i) {
if (msg.headers[i].b - msg.headers[i].a) { 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); msg.headers[i].b - msg.headers[i].a);
lua_setfield(L, -2, GetHttpHeaderName(i)); lua_setfield(L, -2, GetHttpHeaderName(i));
} }
} }
for (i = 0; i < msg.xheaders.n; ++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); msg.xheaders.p[i].v.b - msg.xheaders.p[i].v.a);
lua_setfield( lua_setfield(L, -2,
L, -2, (name = DecodeLatin1(
FreeLater(strndup(inbuf.p + msg.xheaders.p[i].k.a, inbuf.p + msg.xheaders.p[i].k.a,
msg.xheaders.p[i].k.b - msg.xheaders.p[i].k.a))); msg.xheaders.p[i].k.b - msg.xheaders.p[i].k.a, 0)));
free(name);
} }
return 1; return 1;
} }
static int LuaSetHeader(lua_State *L) { static int LuaSetHeader(lua_State *L) {
int h;
char *p; char *p;
ssize_t rc; ssize_t rc;
const char *key, *val; const char *key, *val, *eval;
size_t i, keylen, vallen; size_t i, keylen, vallen, evallen;
key = luaL_checklstring(L, 1, &keylen); key = luaL_checklstring(L, 1, &keylen);
val = luaL_checklstring(L, 2, &vallen); 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"); return luaL_argerror(L, 1, "invalid");
} }
if (!IsValidFieldContent(val, vallen)) { }
if (!(eval = EncodeHttpHeaderValue(val, vallen, &evallen))) {
return luaL_argerror(L, 2, "invalid"); return luaL_argerror(L, 2, "invalid");
} }
if (!luaheaderp) { if (!luaheaderp) {
p = SetStatus(200, "OK"); p = SetStatus(200, "OK");
} else { } 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; hdrbuf.n += hdrbuf.n >> 1;
p = xrealloc(hdrbuf.p, hdrbuf.n); p = xrealloc(hdrbuf.p, hdrbuf.n);
luaheaderp = p + (luaheaderp - hdrbuf.p); luaheaderp = p + (luaheaderp - hdrbuf.p);
@ -1436,30 +1435,31 @@ static int LuaSetHeader(lua_State *L) {
} }
p = luaheaderp; p = luaheaderp;
} }
switch (GetHttpHeader(key, keylen)) { switch (h) {
case kHttpDate: case kHttpDate:
case kHttpContentRange: case kHttpContentRange:
case kHttpContentLength: case kHttpContentLength:
case kHttpContentEncoding: case kHttpContentEncoding:
return luaL_argerror(L, 1, "abstracted"); return luaL_argerror(L, 1, "abstracted");
case kHttpConnection: case kHttpConnection:
if (vallen != 5 || memcmp(val, "close", 5)) { if (evallen != 5 || memcmp(eval, "close", 5)) {
return luaL_argerror(L, 2, "unsupported"); return luaL_argerror(L, 2, "unsupported");
} }
connectionclose = true; connectionclose = true;
break; break;
case kHttpContentType: case kHttpContentType:
p = AppendContentType(p, val); p = AppendContentType(p, eval);
break; break;
case kHttpServer: case kHttpServer:
branded = true; branded = true;
p = AppendHeader(p, key, val); p = AppendHeader(p, "Server", eval);
break; break;
default: default:
p = AppendHeader(p, key, val); p = AppendHeader(p, key, eval);
break; break;
} }
luaheaderp = p; luaheaderp = p;
free(eval);
return 0; return 0;
} }
@ -1579,6 +1579,17 @@ static int LuaDecodeBase64(lua_State *L) {
return 1; 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) { static int LuaPopcnt(lua_State *L) {
lua_pushinteger(L, popcnt(luaL_checkinteger(L, 1))); lua_pushinteger(L, popcnt(luaL_checkinteger(L, 1)));
return 1; return 1;
@ -1642,8 +1653,6 @@ static const luaL_Reg kLuaFuncs[] = {
{"GetUri", LuaGetUri}, // {"GetUri", LuaGetUri}, //
{"GetVersion", LuaGetVersion}, // {"GetVersion", LuaGetVersion}, //
{"HasParam", LuaHasParam}, // {"HasParam", LuaHasParam}, //
{"IsValidFieldContent", LuaIsValidFieldContent}, //
{"IsValidFieldName", LuaIsValidFieldName}, //
{"LoadAsset", LuaLoadAsset}, // {"LoadAsset", LuaLoadAsset}, //
{"ParseDate", LuaParseDate}, // {"ParseDate", LuaParseDate}, //
{"ServeAsset", LuaServeAsset}, // {"ServeAsset", LuaServeAsset}, //
@ -1748,15 +1757,35 @@ static char *HandleRedirect(struct Redirect *r) {
kHttpMethod[msg.method], request.path.n, request.path.p, kHttpMethod[msg.method], request.path.n, request.path.p,
r->location); r->location);
p = SetStatus(307, "Temporary Redirect"); p = SetStatus(307, "Temporary Redirect");
p = AppendHeader(p, "Location", r->location); p = AppendHeader(p, "Location",
FreeLater(EncodeHttpHeaderValue(r->location, -1, 0)));
} }
return p; return p;
} }
static void LogMessage(const char *d, const char *s, size_t n) { static void LogMessage(const char *d, const char *s, size_t n) {
char *s2, *s3;
size_t n2, n3;
if (!logmessages) return; if (!logmessages) return;
while (n && (s[n - 1] == '\r' || s[n - 1] == '\n')) --n; 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) { static ssize_t SendMessageString(const char *s) {
@ -1872,6 +1901,7 @@ static char *HandleMessage(void) {
} }
} }
msgsize = need; /* we are now synchronized */ msgsize = need; /* we are now synchronized */
LogBody("received", inbuf.p + hdrsize, msgsize - hdrsize);
if (httpversion != 101 || IsConnectionClose()) { if (httpversion != 101 || IsConnectionClose()) {
connectionclose = true; connectionclose = true;
} }

View file

@ -58,16 +58,38 @@ local function main()
Write('<p><em>none</em>\n') Write('<p><em>none</em>\n')
end end
Write('<h3>post request html form demo</h3>\n') Write([[
Write('<form action="/tool/net/redbean-form.lua" method="post">\n') <h3>post request html form demo</h3>
Write('<input type="text" id="firstname" name="firstname">\n') <form action="/tool/net/redbean-form.lua" method="post">
Write('<label for="firstname">first name</label>\n') <input type="text" id="firstname" name="firstname">
Write('<br>\n') <label for="firstname">first name</label>
Write('<input type="text" id="lastname" name="lastname">\n') <br>
Write('<label for="lastname">last name</label>\n') <input type="text" id="lastname" name="lastname">
Write('<br>\n') <label for="lastname">last name</label>
Write('<input type="submit" value="Submit">\n') <br>
Write('</form>\n') <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 end
main() main()