mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-02-07 06:53:33 +00:00
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:
parent
dcbd2b8668
commit
a1677d605a
14 changed files with 675 additions and 161 deletions
54
net/http/decodelatin1.c
Normal file
54
net/http/decodelatin1.c
Normal 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;
|
||||
}
|
85
net/http/encodehttpheadervalue.c
Normal file
85
net/http/encodehttpheadervalue.c
Normal 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;
|
||||
}
|
|
@ -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) */
|
||||
|
|
54
net/http/isvalidhttptoken.c
Normal file
54
net/http/isvalidhttptoken.c
Normal 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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
93
net/http/visualizecontrolcodes.c
Normal file
93
net/http/visualizecontrolcodes.c
Normal 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;
|
||||
}
|
28
test/net/http/decodelatin1_test.c
Normal file
28
test/net/http/decodelatin1_test.c
Normal 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);
|
||||
}
|
82
test/net/http/encodehttpheadervalue_test.c
Normal file
82
test/net/http/encodehttpheadervalue_test.c
Normal 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)));
|
||||
}
|
|
@ -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])));
|
||||
}
|
||||
|
|
34
test/net/http/visualizecontrolcodes_test.c
Normal file
34
test/net/http/visualizecontrolcodes_test.c
Normal 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)));
|
||||
}
|
|
@ -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
2
tool/net/redbean-xhr.lua
Normal file
|
@ -0,0 +1,2 @@
|
|||
-- redbean xhr handler demo
|
||||
SetHeader('X-Custom-Header', 'hello ' .. GetHeader('x-custom-header'))
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in a new issue