Support POST parameters in redbean server pages

See #97
This commit is contained in:
Justine Tunney
2021-03-27 07:29:55 -07:00
parent da36e7e256
commit 4d21cd315d
20 changed files with 1075 additions and 664 deletions

37
libc/str/memcasecmp.c Normal file
View File

@ -0,0 +1,37 @@
/*-*- 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 2020 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"
/**
* Compares memory case-insensitively.
* @return is <0, 0, or >0 based on uint8_t comparison
*/
int memcasecmp(const void *p, const void *q, size_t n) {
int c;
size_t i;
const unsigned char *a, *b;
if ((a = p) != (b = q)) {
for (i = 0; i < n; ++i) {
if ((c = kToLower[a[i]] - kToLower[b[i]])) {
return c;
}
}
}
return 0;
}

View File

@ -123,6 +123,7 @@ int wcscmp(const wchar_t *, const wchar_t *) strlenesque;
int wcsncmp(const wchar_t *, const wchar_t *, size_t) strlenesque; int wcsncmp(const wchar_t *, const wchar_t *, size_t) strlenesque;
int wmemcmp(const wchar_t *, const wchar_t *, size_t) strlenesque; int wmemcmp(const wchar_t *, const wchar_t *, size_t) strlenesque;
int strcasecmp(const char *, const char *) strlenesque; int strcasecmp(const char *, const char *) strlenesque;
int memcasecmp(const void *, const void *, size_t) strlenesque;
int strcasecmp16(const char16_t *, const char16_t *) strlenesque; int strcasecmp16(const char16_t *, const char16_t *) strlenesque;
int wcscasecmp(const wchar_t *, const wchar_t *) strlenesque; int wcscasecmp(const wchar_t *, const wchar_t *) strlenesque;
int strncasecmp(const char *, const char *, size_t) strlenesque; int strncasecmp(const char *, const char *, size_t) strlenesque;

View File

@ -37,21 +37,13 @@ struct EscapeResult EscapeUrl(const char *data, size_t size,
struct EscapeResult r; struct EscapeResult r;
p = r.data = xmalloc(size * 6 + 1); p = r.data = xmalloc(size * 6 + 1);
for (i = 0; i < size; ++i) { for (i = 0; i < size; ++i) {
switch (xlat[(c = data[i] & 0xff)]) { if (!xlat[(c = data[i] & 0xff)]) {
case 0:
*p++ = c; *p++ = c;
break; } else {
case 1:
*p++ = '+';
break;
case 2:
p[0] = '%'; p[0] = '%';
p[1] = "0123456789ABCDEF"[(c & 0xF0) >> 4]; p[1] = "0123456789ABCDEF"[(c & 0xF0) >> 4];
p[2] = "0123456789ABCDEF"[(c & 0x0F) >> 0]; p[2] = "0123456789ABCDEF"[(c & 0x0F) >> 0];
p += 3; p += 3;
break;
default:
unreachable;
} }
} }
r.size = p - r.data; r.size = p - r.data;

View File

@ -21,27 +21,27 @@
// url fragment dispatch // url fragment dispatch
// - 0 is -/?.~_@:!$&'()*+,;=0-9A-Za-z // - 0 is -/?.~_@:!$&'()*+,;=0-9A-Za-z
// - 2 is everything else which needs uppercase hex %XX // - 1 is everything else which needs uppercase hex %XX
// note that '& can break html // note that '& can break html
// note that '() can break css urls // note that '() can break css urls
// note that unicode can still be wild // note that unicode can still be wild
static const char kEscapeUrlFragment[256] = { static const char kEscapeUrlFragment[256] = {
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0x00 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x00
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0x10 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x10
2, 0, 2, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x20 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x20
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, // 0x30 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, // 0x30
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x40 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x40
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, // 0x50 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, // 0x50
2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x60 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x60
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 0, 2, // 0x70 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, // 0x70
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0x80 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x80
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0x90 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x90
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xa0 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xa0
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xb0 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xb0
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xc0 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xc0
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xd0 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xd0
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xe0 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xe0
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xf0 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xf0
}; };
/** /**

View File

@ -21,26 +21,25 @@
// url query/form name/parameter dispatch // url query/form name/parameter dispatch
// - 0 is -.*_0-9A-Za-z // - 0 is -.*_0-9A-Za-z
// - 1 is ' ' which becomes '+' // - 1 is everything else which needs uppercase hex %XX
// - 2 is everything else which needs uppercase hex %XX
// note that unicode can still be wild // note that unicode can still be wild
static const char kEscapeUrlParam[256] = { static const char kEscapeUrlParam[256] = {
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0x00 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x00
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0x10 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x10
1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 0, 0, 2, // 0x20 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, // 0x20
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, // 0x30 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, // 0x30
2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x40 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x40
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, // 0x50 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, // 0x50
2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x60 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x60
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, // 0x70 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, // 0x70
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0x80 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x80
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0x90 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x90
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xa0 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xa0
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xb0 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xb0
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xc0 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xc0
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xd0 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xd0
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xe0 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xe0
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xf0 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xf0
}; };
/** /**

View File

@ -21,27 +21,27 @@
// url path dispatch // url path dispatch
// - 0 is -.~_@:!$&'()*+,;=0-9A-Za-z/ // - 0 is -.~_@:!$&'()*+,;=0-9A-Za-z/
// - 2 is everything else which needs uppercase hex %XX // - 1 is everything else which needs uppercase hex %XX
// note that '& can break html // note that '& can break html
// note that '() can break css urls // note that '() can break css urls
// note that unicode can still be wild // note that unicode can still be wild
static const char kEscapeUrlPath[256] = { static const char kEscapeUrlPath[256] = {
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0x00 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x00
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0x10 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x10
2, 0, 2, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x20 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x20
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 2, // 0x30 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, // 0x30
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x40 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x40
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, // 0x50 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, // 0x50
2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x60 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x60
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 0, 2, // 0x70 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, // 0x70
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0x80 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x80
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0x90 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x90
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xa0 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xa0
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xb0 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xb0
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xc0 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xc0
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xd0 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xd0
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xe0 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xe0
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xf0 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xf0
}; };
/** /**

View File

@ -21,27 +21,27 @@
// url path segment dispatch // url path segment dispatch
// - 0 is -.~_@:!$&'()*+,;=0-9A-Za-z // - 0 is -.~_@:!$&'()*+,;=0-9A-Za-z
// - 2 is everything else which needs uppercase hex %XX // - 1 is everything else which needs uppercase hex %XX
// note that '& can break html // note that '& can break html
// note that '() can break css urls // note that '() can break css urls
// note that unicode can still be wild // note that unicode can still be wild
static const char kEscapeUrlPathSegment[256] = { static const char kEscapeUrlPathSegment[256] = {
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0x00 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x00
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0x10 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x10
2, 0, 2, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, // 0x20 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, // 0x20
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 2, // 0x30 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, // 0x30
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x40 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x40
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, // 0x50 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, // 0x50
2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x60 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x60
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 0, 2, // 0x70 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, // 0x70
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0x80 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x80
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0x90 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x90
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xa0 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xa0
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xb0 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xb0
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xc0 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xc0
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xd0 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xd0
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xe0 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xe0
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xf0 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xf0
}; };
/** /**

View File

@ -65,3 +65,7 @@ Trailer, kHttpTrailer
TE, kHttpTe TE, kHttpTe
DNT, kHttpDnt DNT, kHttpDnt
Expect, kHttpExpect Expect, kHttpExpect
Content-Disposition, kHttpContentDisposition
Content-Description, kHttpContentDescription
Origin, kHttpOrigin
Upgrade-Insecure-Requests, kHttpUpgradeInsecureRequests

View File

@ -37,12 +37,12 @@
#line 12 "gethttpheader.gperf" #line 12 "gethttpheader.gperf"
struct HttpHeaderSlot { char *name; char code; }; struct HttpHeaderSlot { char *name; char code; };
#define TOTAL_KEYWORDS 54 #define TOTAL_KEYWORDS 58
#define MIN_WORD_LENGTH 2 #define MIN_WORD_LENGTH 2
#define MAX_WORD_LENGTH 19 #define MAX_WORD_LENGTH 25
#define MIN_HASH_VALUE 2 #define MIN_HASH_VALUE 2
#define MAX_HASH_VALUE 89 #define MAX_HASH_VALUE 97
/* maximum key range = 88, duplicates = 0 */ /* maximum key range = 96, duplicates = 0 */
#ifndef GPERF_DOWNCASE #ifndef GPERF_DOWNCASE
#define GPERF_DOWNCASE 1 #define GPERF_DOWNCASE 1
@ -101,32 +101,32 @@ hash (register const char *str, register size_t len)
{ {
static const unsigned char asso_values[] = static const unsigned char asso_values[] =
{ {
90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98,
90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98,
90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98,
90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98,
90, 90, 90, 90, 90, 30, 90, 90, 90, 90, 98, 98, 98, 98, 98, 30, 98, 98, 98, 98,
90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98,
90, 90, 90, 90, 90, 5, 5, 30, 50, 0, 98, 98, 98, 98, 98, 5, 5, 30, 55, 0,
35, 30, 0, 5, 90, 40, 0, 30, 0, 20, 35, 30, 0, 35, 98, 40, 0, 30, 0, 20,
45, 90, 0, 5, 10, 5, 0, 15, 20, 25, 55, 98, 0, 5, 10, 5, 0, 5, 20, 30,
90, 90, 90, 90, 90, 90, 90, 5, 5, 30, 98, 98, 98, 98, 98, 98, 98, 5, 5, 30,
50, 0, 35, 30, 0, 5, 90, 40, 0, 30, 55, 0, 35, 30, 0, 35, 98, 40, 0, 30,
0, 20, 45, 90, 0, 5, 10, 5, 0, 15, 0, 20, 55, 98, 0, 5, 10, 5, 0, 5,
20, 25, 90, 90, 90, 90, 90, 90, 90, 90, 20, 30, 98, 98, 98, 98, 98, 98, 98, 98,
90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98,
90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98,
90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98,
90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98,
90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98,
90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98,
90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98,
90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98,
90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98,
90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98,
90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98,
90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98,
90, 90, 90, 90, 90, 90 98, 98, 98, 98, 98, 98
}; };
register unsigned int hval = len; register unsigned int hval = len;
@ -180,10 +180,7 @@ LookupHttpHeader (register const char *str, register size_t len)
{"DNT", kHttpDnt}, {"DNT", kHttpDnt},
#line 33 "gethttpheader.gperf" #line 33 "gethttpheader.gperf"
{"Date", kHttpDate}, {"Date", kHttpDate},
{""}, {""}, {""}, {""}, {""}, {""},
#line 64 "gethttpheader.gperf"
{"Trailer", kHttpTrailer},
{""},
#line 37 "gethttpheader.gperf" #line 37 "gethttpheader.gperf"
{"Host", kHttpHost}, {"Host", kHttpHost},
#line 53 "gethttpheader.gperf" #line 53 "gethttpheader.gperf"
@ -203,8 +200,8 @@ LookupHttpHeader (register const char *str, register size_t len)
{"Content-Base", kHttpContentBase}, {"Content-Base", kHttpContentBase},
#line 31 "gethttpheader.gperf" #line 31 "gethttpheader.gperf"
{"Content-Range", kHttpContentRange}, {"Content-Range", kHttpContentRange},
#line 59 "gethttpheader.gperf" #line 69 "gethttpheader.gperf"
{"Vary", kHttpVary}, {"Content-Description", kHttpContentDescription},
#line 23 "gethttpheader.gperf" #line 23 "gethttpheader.gperf"
{"Close", kHttpClose}, {"Close", kHttpClose},
#line 27 "gethttpheader.gperf" #line 27 "gethttpheader.gperf"
@ -212,7 +209,8 @@ LookupHttpHeader (register const char *str, register size_t len)
{""}, {""},
#line 20 "gethttpheader.gperf" #line 20 "gethttpheader.gperf"
{"Authorization", kHttpAuthorization}, {"Authorization", kHttpAuthorization},
{""}, #line 59 "gethttpheader.gperf"
{"Vary", kHttpVary},
#line 49 "gethttpheader.gperf" #line 49 "gethttpheader.gperf"
{"Range", kHttpRange}, {"Range", kHttpRange},
#line 14 "gethttpheader.gperf" #line 14 "gethttpheader.gperf"
@ -235,56 +233,63 @@ LookupHttpHeader (register const char *str, register size_t len)
{"Accept-Language", kHttpAcceptLanguage}, {"Accept-Language", kHttpAcceptLanguage},
#line 29 "gethttpheader.gperf" #line 29 "gethttpheader.gperf"
{"Content-Location", kHttpContentLocation}, {"Content-Location", kHttpContentLocation},
#line 32 "gethttpheader.gperf" #line 64 "gethttpheader.gperf"
{"Content-Type", kHttpContentType}, {"Trailer", kHttpTrailer},
#line 40 "gethttpheader.gperf" #line 40 "gethttpheader.gperf"
{"If-None-Match", kHttpIfNoneMatch}, {"If-None-Match", kHttpIfNoneMatch},
#line 15 "gethttpheader.gperf" #line 15 "gethttpheader.gperf"
{"Accept-Charset", kHttpAcceptCharset}, {"Accept-Charset", kHttpAcceptCharset},
{""}, {""},
#line 67 "gethttpheader.gperf" #line 61 "gethttpheader.gperf"
{"Expect", kHttpExpect}, {"WWW-Authenticate", kHttpWwwAuthenticate},
{""}, #line 32 "gethttpheader.gperf"
{"Content-Type", kHttpContentType},
#line 21 "gethttpheader.gperf" #line 21 "gethttpheader.gperf"
{"Cache-Control", kHttpCacheControl}, {"Cache-Control", kHttpCacheControl},
#line 36 "gethttpheader.gperf" #line 36 "gethttpheader.gperf"
{"From", kHttpFrom}, {"From", kHttpFrom},
#line 43 "gethttpheader.gperf" #line 71 "gethttpheader.gperf"
{"Keep-Alive", kHttpKeepAlive}, {"Upgrade-Insecure-Requests", kHttpUpgradeInsecureRequests},
#line 48 "gethttpheader.gperf" #line 48 "gethttpheader.gperf"
{"Proxy-Connection", kHttpProxyConnection}, {"Proxy-Connection", kHttpProxyConnection},
#line 35 "gethttpheader.gperf" {""},
{"Expires", kHttpExpires},
#line 46 "gethttpheader.gperf" #line 46 "gethttpheader.gperf"
{"Proxy-Authenticate", kHttpProxyAuthenticate}, {"Proxy-Authenticate", kHttpProxyAuthenticate},
#line 47 "gethttpheader.gperf" #line 47 "gethttpheader.gperf"
{"Proxy-Authorization", kHttpProxyAuthorization}, {"Proxy-Authorization", kHttpProxyAuthorization},
{""}, {""},
#line 61 "gethttpheader.gperf" #line 67 "gethttpheader.gperf"
{"WWW-Authenticate", kHttpWwwAuthenticate}, {"Expect", kHttpExpect},
#line 44 "gethttpheader.gperf" #line 44 "gethttpheader.gperf"
{"Max-Forwards", kHttpMaxForwards}, {"Max-Forwards", kHttpMaxForwards},
#line 62 "gethttpheader.gperf" #line 62 "gethttpheader.gperf"
{"Last-Modified", kHttpLastModified}, {"Last-Modified", kHttpLastModified},
{""}, {""}, #line 68 "gethttpheader.gperf"
{"Content-Disposition", kHttpContentDisposition},
#line 43 "gethttpheader.gperf"
{"Keep-Alive", kHttpKeepAlive},
#line 63 "gethttpheader.gperf" #line 63 "gethttpheader.gperf"
{"Cookie", kHttpCookie}, {"Cookie", kHttpCookie},
{""}, {""},
#line 38 "gethttpheader.gperf" #line 38 "gethttpheader.gperf"
{"If-Match", kHttpIfMatch}, {"If-Match", kHttpIfMatch},
{""}, {""}, {""}, {""},
#line 30 "gethttpheader.gperf" #line 70 "gethttpheader.gperf"
{"Content-Md5", kHttpContentMd5}, {"Origin", kHttpOrigin},
{""}, {""}, {""}, {""}, {""}, {""},
#line 16 "gethttpheader.gperf" #line 16 "gethttpheader.gperf"
{"Accept-Encoding", kHttpAcceptEncoding}, {"Accept-Encoding", kHttpAcceptEncoding},
{""}, #line 30 "gethttpheader.gperf"
{"Content-Md5", kHttpContentMd5},
#line 39 "gethttpheader.gperf" #line 39 "gethttpheader.gperf"
{"If-Modified-Since", kHttpIfModifiedSince}, {"If-Modified-Since", kHttpIfModifiedSince},
{""}, {""}, {""}, {""}, {""}, {""}, {""}, {""}, {""}, {""}, {""}, {""}, {""}, {""}, {""}, {""}, {""}, {""},
{""}, {""}, {""}, {""},
#line 42 "gethttpheader.gperf" #line 42 "gethttpheader.gperf"
{"If-Unmodified-Since", kHttpIfUnmodifiedSince} {"If-Unmodified-Since", kHttpIfUnmodifiedSince},
{""}, {""}, {""}, {""}, {""}, {""}, {""},
#line 35 "gethttpheader.gperf"
{"Expires", kHttpExpires}
}; };
if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH) if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH)

View File

@ -0,0 +1,142 @@
/*-*- 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 "net/http/http.h"
const char *GetHttpHeaderName(int h) {
switch (h) {
case kHttpAccept:
return "Accept";
case kHttpAcceptCharset:
return "Accept-Charset";
case kHttpAcceptEncoding:
return "Accept-Encoding";
case kHttpAcceptLanguage:
return "Accept-Language";
case kHttpAge:
return "Age";
case kHttpAllow:
return "Allow";
case kHttpAuthorization:
return "Authorization";
case kHttpCacheControl:
return "Cache-Control";
case kHttpChunked:
return "Chunked";
case kHttpClose:
return "Close";
case kHttpConnection:
return "Connection";
case kHttpContentBase:
return "Content-Base";
case kHttpContentEncoding:
return "Content-Encoding";
case kHttpContentLanguage:
return "Content-Language";
case kHttpContentLength:
return "Content-Length";
case kHttpContentLocation:
return "Content-Location";
case kHttpContentMd5:
return "Content-Md5";
case kHttpContentRange:
return "Content-Range";
case kHttpContentType:
return "Content-Type";
case kHttpDate:
return "Date";
case kHttpEtag:
return "ETag";
case kHttpExpires:
return "Expires";
case kHttpFrom:
return "From";
case kHttpHost:
return "Host";
case kHttpIfMatch:
return "If-Match";
case kHttpIfModifiedSince:
return "If-Modified-Since";
case kHttpIfNoneMatch:
return "If-None-Match";
case kHttpIfRange:
return "If-Range";
case kHttpIfUnmodifiedSince:
return "If-Unmodified-Since";
case kHttpKeepAlive:
return "Keep-Alive";
case kHttpMaxForwards:
return "Max-Forwards";
case kHttpPragma:
return "Pragma";
case kHttpProxyAuthenticate:
return "Proxy-Authenticate";
case kHttpProxyAuthorization:
return "Proxy-Authorization";
case kHttpProxyConnection:
return "Proxy-Connection";
case kHttpRange:
return "Range";
case kHttpReferer:
return "Referer";
case kHttpTransferEncoding:
return "Transfer-Encoding";
case kHttpUpgrade:
return "Upgrade";
case kHttpUserAgent:
return "User-Agent";
case kHttpVia:
return "Via";
case kHttpLocation:
return "Location";
case kHttpPublic:
return "Public";
case kHttpRetryAfter:
return "Retry-After";
case kHttpServer:
return "Server";
case kHttpVary:
return "Vary";
case kHttpWarning:
return "Warning";
case kHttpWwwAuthenticate:
return "WWW-Authenticate";
case kHttpLastModified:
return "Last-Modified";
case kHttpCookie:
return "Cookie";
case kHttpTrailer:
return "Trailer";
case kHttpTe:
return "TE";
case kHttpDnt:
return "DNT";
case kHttpExpect:
return "Expect";
case kHttpContentDisposition:
return "Content-Disposition";
case kHttpContentDescription:
return "Content-Description";
case kHttpOrigin:
return "Origin";
case kHttpUpgradeInsecureRequests:
return "Upgrade-Insecure-Requests";
default:
return NULL;
}
}

View File

@ -75,7 +75,11 @@
#define kHttpTe 51 #define kHttpTe 51
#define kHttpDnt 52 #define kHttpDnt 52
#define kHttpExpect 53 #define kHttpExpect 53
#define kHttpHeadersMax 54 #define kHttpContentDisposition 54
#define kHttpContentDescription 55
#define kHttpOrigin 56
#define kHttpUpgradeInsecureRequests 57
#define kHttpHeadersMax 58
#if !(__ASSEMBLER__ + __LINKER__ + 0) #if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_ COSMOPOLITAN_C_START_
@ -85,12 +89,20 @@ struct HttpRequestSlice {
}; };
struct HttpRequest { struct HttpRequest {
int i, t, a, h; int i, t, a;
int method; int method;
struct HttpRequestSlice k;
struct HttpRequestSlice uri; struct HttpRequestSlice uri;
struct HttpRequestSlice version; struct HttpRequestSlice version;
struct HttpRequestSlice scratch; struct HttpRequestSlice scratch;
struct HttpRequestSlice headers[kHttpHeadersMax]; struct HttpRequestSlice headers[kHttpHeadersMax];
struct HttpRequestHeaders {
size_t n;
struct HttpRequestHeader {
struct HttpRequestSlice k;
struct HttpRequestSlice v;
} * p;
} xheaders;
}; };
extern const char kHttpMethod[17][8]; extern const char kHttpMethod[17][8];
@ -98,6 +110,7 @@ extern const char kHttpMethod[17][8];
int GetHttpHeader(const char *, size_t); int GetHttpHeader(const char *, size_t);
int GetHttpMethod(const char *, size_t); int GetHttpMethod(const char *, size_t);
void InitHttpRequest(struct HttpRequest *); void InitHttpRequest(struct HttpRequest *);
void DestroyHttpRequest(struct HttpRequest *);
int ParseHttpRequest(struct HttpRequest *, const char *, size_t); int ParseHttpRequest(struct HttpRequest *, const char *, size_t);
int NegotiateHttpRequest(int, const char *, uint32_t *, char *, uint32_t *, int NegotiateHttpRequest(int, const char *, uint32_t *, char *, uint32_t *,
uint32_t *, bool, long double); uint32_t *, bool, long double);
@ -107,6 +120,7 @@ bool ParseHttpRange(const char *, size_t, long, long *, long *);
unsigned ParseHttpVersion(const char *, size_t); 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);
COSMOPOLITAN_C_END_ COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */

View File

@ -31,6 +31,7 @@ NET_HTTP_A_DIRECTDEPS = \
LIBC_FMT \ LIBC_FMT \
LIBC_INTRIN \ LIBC_INTRIN \
LIBC_LOG \ LIBC_LOG \
LIBC_MEM \
LIBC_NEXGEN32E \ LIBC_NEXGEN32E \
LIBC_RUNTIME \ LIBC_RUNTIME \
LIBC_SOCK \ LIBC_SOCK \

View File

@ -20,6 +20,7 @@
#include "libc/alg/arraylist.internal.h" #include "libc/alg/arraylist.internal.h"
#include "libc/limits.h" #include "libc/limits.h"
#include "libc/macros.internal.h" #include "libc/macros.internal.h"
#include "libc/mem/mem.h"
#include "libc/stdio/stdio.h" #include "libc/stdio/stdio.h"
#include "libc/str/str.h" #include "libc/str/str.h"
#include "libc/sysv/errfuns.h" #include "libc/sysv/errfuns.h"
@ -37,11 +38,19 @@ void InitHttpRequest(struct HttpRequest *r) {
memset(r, 0, sizeof(*r)); memset(r, 0, sizeof(*r));
} }
/**
* Destroys HTTP request parser.
*/
void DestroyHttpRequest(struct HttpRequest *r) {
free(r->xheaders.p);
}
/** /**
* Parses HTTP request. * Parses HTTP request.
*/ */
int ParseHttpRequest(struct HttpRequest *r, const char *p, size_t n) { int ParseHttpRequest(struct HttpRequest *r, const char *p, size_t n) {
int c; int c, h;
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;
switch (r->t) { switch (r->t) {
@ -97,12 +106,12 @@ int ParseHttpRequest(struct HttpRequest *r, const char *p, size_t n) {
} else if (c == ' ' || c == '\t') { } else if (c == ' ' || c == '\t') {
return ebadmsg(); /* RFC7230 § 3.2.4 */ return ebadmsg(); /* RFC7230 § 3.2.4 */
} }
r->a = r->i; r->k.a = r->i;
r->t = HKEY; r->t = HKEY;
break; break;
case HKEY: case HKEY:
if (c == ':') { if (c == ':') {
r->h = GetHttpHeader(p + r->a, r->i - r->a); r->k.b = r->i;
r->t = HSEP; r->t = HSEP;
} }
break; break;
@ -113,9 +122,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') {
if (r->h != -1) { if ((h = GetHttpHeader(p + r->k.a, r->k.b - r->k.a)) != -1) {
r->headers[r->h].a = r->a; r->headers[h].a = r->a;
r->headers[r->h].b = r->i; r->headers[h].b = r->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;
r->xheaders.p = x;
++r->xheaders.n;
} }
r->t = c == '\r' ? CR1 : LF1; r->t = c == '\r' ? CR1 : LF1;
} }

View File

@ -31,7 +31,8 @@ char *escapeparam(const char *s) {
} }
TEST(escapeparam, test) { TEST(escapeparam, test) {
EXPECT_STREQ("abc+%26%3C%3E%22%27%01%02", gc(escapeparam("abc &<>\"'\1\2"))); EXPECT_STREQ("abc%20%26%3C%3E%22%27%01%02",
gc(escapeparam("abc &<>\"'\1\2")));
} }
TEST(escapeparam, testLargeGrowth) { TEST(escapeparam, testLargeGrowth) {

View File

@ -41,19 +41,24 @@ static unsigned version(const char *m) {
return ParseHttpVersion(m + req->version.a, req->version.b - req->version.a); return ParseHttpVersion(m + req->version.a, req->version.b - req->version.a);
} }
TEST(ParseHttpRequest, testEmpty_tooShort) { void SetUp(void) {
InitHttpRequest(req); InitHttpRequest(req);
}
void TearDown(void) {
DestroyHttpRequest(req);
}
TEST(ParseHttpRequest, testEmpty_tooShort) {
EXPECT_EQ(0, ParseHttpRequest(req, "", 0)); EXPECT_EQ(0, ParseHttpRequest(req, "", 0));
} }
TEST(ParseHttpRequest, testTooShort) { TEST(ParseHttpRequest, testTooShort) {
InitHttpRequest(req);
EXPECT_EQ(0, ParseHttpRequest(req, "\r\n", 2)); EXPECT_EQ(0, ParseHttpRequest(req, "\r\n", 2));
} }
TEST(ParseHttpRequest, testNoHeaders) { TEST(ParseHttpRequest, testNoHeaders) {
static const char m[] = "GET /foo HTTP/1.0\r\n\r\n"; static const char m[] = "GET /foo HTTP/1.0\r\n\r\n";
InitHttpRequest(req);
EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m))); EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m)));
EXPECT_EQ(kHttpGet, req->method); EXPECT_EQ(kHttpGet, req->method);
EXPECT_STREQ("/foo", gc(slice(m, req->uri))); EXPECT_STREQ("/foo", gc(slice(m, req->uri)));
@ -66,7 +71,6 @@ POST /foo?bar%20hi HTTP/1.0\r\n\
Host: foo.example\r\n\ Host: foo.example\r\n\
Content-Length: 0\r\n\ Content-Length: 0\r\n\
\r\n"; \r\n";
InitHttpRequest(req);
EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m))); EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m)));
EXPECT_EQ(kHttpPost, req->method); EXPECT_EQ(kHttpPost, req->method);
EXPECT_STREQ("/foo?bar%20hi", gc(slice(m, req->uri))); EXPECT_STREQ("/foo?bar%20hi", gc(slice(m, req->uri)));
@ -78,7 +82,6 @@ Content-Length: 0\r\n\
TEST(ParseHttpRequest, testHttp101) { TEST(ParseHttpRequest, testHttp101) {
static const char m[] = "GET / HTTP/1.1\r\n\r\n"; static const char m[] = "GET / HTTP/1.1\r\n\r\n";
InitHttpRequest(req);
EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m))); EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m)));
EXPECT_EQ(kHttpGet, req->method); EXPECT_EQ(kHttpGet, req->method);
EXPECT_STREQ("/", gc(slice(m, req->uri))); EXPECT_STREQ("/", gc(slice(m, req->uri)));
@ -88,7 +91,6 @@ TEST(ParseHttpRequest, testHttp101) {
TEST(ParseHttpRequest, testHttp100) { TEST(ParseHttpRequest, testHttp100) {
static const char m[] = "GET / HTTP/1.0\r\n\r\n"; static const char m[] = "GET / HTTP/1.0\r\n\r\n";
InitHttpRequest(req);
EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m))); EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m)));
EXPECT_EQ(kHttpGet, req->method); EXPECT_EQ(kHttpGet, req->method);
EXPECT_STREQ("/", gc(slice(m, req->uri))); EXPECT_STREQ("/", gc(slice(m, req->uri)));
@ -98,7 +100,6 @@ TEST(ParseHttpRequest, testHttp100) {
TEST(ParseHttpRequest, testHttp009) { TEST(ParseHttpRequest, testHttp009) {
static const char m[] = "GET /\r\n\r\n"; static const char m[] = "GET /\r\n\r\n";
InitHttpRequest(req);
EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m))); EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m)));
EXPECT_EQ(kHttpGet, req->method); EXPECT_EQ(kHttpGet, req->method);
EXPECT_STREQ("/", gc(slice(m, req->uri))); EXPECT_STREQ("/", gc(slice(m, req->uri)));
@ -112,7 +113,6 @@ TEST(ParseHttpRequest, testLeadingLineFeeds_areIgnored) {
GET /foo?bar%20hi HTTP/1.0\r\n\ GET /foo?bar%20hi HTTP/1.0\r\n\
User-Agent: hi\r\n\ User-Agent: hi\r\n\
\r\n"; \r\n";
InitHttpRequest(req);
EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m))); EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m)));
EXPECT_STREQ("/foo?bar%20hi", gc(slice(m, req->uri))); EXPECT_STREQ("/foo?bar%20hi", gc(slice(m, req->uri)));
} }
@ -123,7 +123,6 @@ GET /foo?bar%20hi HTTP/1.0\r\n\
User-Agent: hi\r\n\ User-Agent: hi\r\n\
there\r\n\ there\r\n\
\r\n"; \r\n";
InitHttpRequest(req);
EXPECT_EQ(-1, ParseHttpRequest(req, m, strlen(m))); EXPECT_EQ(-1, ParseHttpRequest(req, m, strlen(m)));
EXPECT_EQ(EBADMSG, errno); EXPECT_EQ(EBADMSG, errno);
} }
@ -134,7 +133,6 @@ GET /foo?bar%20hi HTTP/1.0\r\n\
User-Agent: hi\r\n\ User-Agent: hi\r\n\
: hi\r\n\ : hi\r\n\
\r\n"; \r\n";
InitHttpRequest(req);
EXPECT_EQ(-1, ParseHttpRequest(req, m, strlen(m))); EXPECT_EQ(-1, ParseHttpRequest(req, m, strlen(m)));
EXPECT_EQ(EBADMSG, errno); EXPECT_EQ(EBADMSG, errno);
} }
@ -146,7 +144,6 @@ Host: foo.example\n\
Content-Length: 0\n\ Content-Length: 0\n\
\n\ \n\
\n"; \n";
InitHttpRequest(req);
EXPECT_EQ(strlen(m) - 1, ParseHttpRequest(req, m, strlen(m))); EXPECT_EQ(strlen(m) - 1, ParseHttpRequest(req, m, strlen(m)));
EXPECT_EQ(kHttpPost, req->method); EXPECT_EQ(kHttpPost, req->method);
EXPECT_STREQ("/foo?bar%20hi", gc(slice(m, req->uri))); EXPECT_STREQ("/foo?bar%20hi", gc(slice(m, req->uri)));
@ -155,3 +152,37 @@ Content-Length: 0\n\
EXPECT_STREQ("0", gc(slice(m, req->headers[kHttpContentLength]))); EXPECT_STREQ("0", gc(slice(m, req->headers[kHttpContentLength])));
EXPECT_STREQ("", gc(slice(m, req->headers[kHttpEtag]))); EXPECT_STREQ("", gc(slice(m, req->headers[kHttpEtag])));
} }
TEST(ParseHttpRequest, testChromeMessage) {
static const char m[] = "\
GET /tool/net/redbean.png HTTP/1.1\r\n\
Host: 10.10.10.124:8080\r\n\
Connection: keep-alive\r\n\
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36\r\n\
DNT: \t1\r\n\
Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8\r\n\
Referer: http://10.10.10.124:8080/\r\n\
Accept-Encoding: gzip, deflate\r\n\
Accept-Language: en-US,en;q=0.9\r\n\
\r\n";
EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m)));
EXPECT_EQ(kHttpGet, req->method);
EXPECT_STREQ("/tool/net/redbean.png", gc(slice(m, req->uri)));
EXPECT_STREQ("HTTP/1.1", gc(slice(m, req->version)));
EXPECT_STREQ("10.10.10.124:8080", gc(slice(m, req->headers[kHttpHost])));
EXPECT_STREQ("1", gc(slice(m, req->headers[kHttpDnt])));
EXPECT_STREQ("", gc(slice(m, req->headers[kHttpExpect])));
EXPECT_STREQ("", gc(slice(m, req->headers[kHttpContentLength])));
EXPECT_STREQ("", gc(slice(m, req->headers[kHttpExpect])));
}
TEST(ParseHttpRequest, testExtendedHeaders) {
static const char m[] = "\
GET /foo?bar%20hi HTTP/1.0\r\n\
X-User-Agent: hi\r\n\
\r\n";
EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m)));
ASSERT_EQ(1, req->xheaders.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)));
}

View File

@ -67,6 +67,9 @@ o/$(MODE)/tool/net/redbean.com.dbg: \
o/$(MODE)/tool/net/redbean.css.zip.o \ o/$(MODE)/tool/net/redbean.css.zip.o \
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/.init.lua.zip.o \
o/$(MODE)/tool/net/.reload.lua.zip.o \
o/$(MODE)/tool/net/net.pkg \ o/$(MODE)/tool/net/net.pkg \
$(CRT) \ $(CRT) \
$(APE) $(APE)

70
tool/net/redbean-form.lua Normal file
View File

@ -0,0 +1,70 @@
-- redbean post request handler demo
local function main()
if GetMethod() ~= 'POST' then
ServeError(405)
SetHeader('Allow', 'POST')
return
end
SetStatus(200)
SetHeader('Content-Type', 'text/html; charset=utf-8')
Write('<!doctype html>\n')
Write('<title>redbean</title>\n')
Write('<h3>POST Request HTML Form Handler Demo</h3>\n')
Write('<p>')
firstname = GetParam('firstname')
lastname = GetParam('lastname')
if firstname and lastname then
Write('Hello ')
Write(EscapeHtml(firstname))
Write(' ')
Write(EscapeHtml(lastname))
Write('!<br>')
Write('Thank you for using redbean.')
end
Write('<dl>\n')
Write('<dt>Params\n')
Write('<dd>\n')
Write('<dl>\n')
params = GetParams()
for i = 1,#params do
Write('<dt>')
Write(EscapeHtml(params[i][1]))
Write('\n')
if params[i][2] then
Write('<dd>')
Write(EscapeHtml(params[i][2]))
Write('\n')
end
end
Write('</dl>\n')
Write('<dt>Headers\n')
Write('<dd>\n')
Write('<dl>\n')
for k,v in pairs(GetHeaders()) do
Write('<dt>')
Write(EscapeHtml(k))
Write('\n')
Write('<dd>')
Write(EscapeHtml(v))
Write('\n')
end
Write('</dl>\n')
Write('<dt>Payload\n')
Write('<dd><p>')
Write(EscapeHtml(GetPayload()))
Write('\n')
Write('</dl>\n')
Write('<p>')
Write('<a href="/tool/net/redbean.lua">Click here</a> ')
Write('to return to the previous page.\n')
end
main()

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,8 @@
<!doctype html> <!doctype html>
<meta charset="utf-8"> <meta charset="utf-8">
<title>redbean</title> <title>redbean</title>
<link rel="stylesheet" href="redbean.css"> <link rel="stylesheet" href="/tool/net/redbean.css">
<img src="redbean.png" class="logo" width="84" height="84"> <img src="/tool/net/redbean.png" class="logo" width="84" height="84">
<h2> <h2>
<big>redbean</big><br> <big>redbean</big><br>

View File

@ -1,8 +1,7 @@
-- redbean lua server page demo -- redbean lua server page demo
local function main() local function main()
-- This check is optional. -- This check is pedantic but might be good to have.
-- We do this by default if you don't call GetMethod().
if GetMethod() ~= 'GET' and GetMethod() ~= 'HEAD' then if GetMethod() ~= 'GET' and GetMethod() ~= 'HEAD' then
ServeError(405) ServeError(405)
SetHeader('Allow', 'GET, HEAD') SetHeader('Allow', 'GET, HEAD')
@ -40,9 +39,35 @@ local function main()
Write('<p>\n') Write('<p>\n')
Write('<em>none</em><br>\n') Write('<em>none</em><br>\n')
Write('ProTip: Try <a href="') Write('ProTip: Try <a href="')
Write(EscapeHtml(EscapePath(GetPath()) .. '?x=hi+there&y&z&z=' .. EscapeParam('&'))) Write(EscapeHtml(EscapePath(GetPath()) .. '?x=hi%20there&y&z&z=' .. EscapeParam('&')))
Write('">clicking here</a>!\n') Write('">clicking here</a>!\n')
end end
-- Access redbean command line arguments.
-- These are the ones that come *after* the redbean server arguments.
Write('<h3>command line arguments</h3>\n')
if #argv > 0 then
Write('<ul>\n')
for i = 1,#argv do
Write('<li>')
Write(EscapeHtml(argv[i]))
Write('\n')
end
Write('</ul>\n')
else
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')
end end
main() main()