Make more major improvements to redbean

- POSIX regular expressions for Lua
- Improved protocol parsing and encoding
- Additional APIs for ZIP storage retrieval
- Fix st_mode issue on NT for regular files
- Generalized APIs for URL and Host handling
- Worked out the kinks in resource resolution
- Allow for custom error pages like /404.html
This commit is contained in:
Justine Tunney 2021-04-20 19:14:21 -07:00
parent 26ac6871da
commit 4effa23528
74 changed files with 3710 additions and 14246 deletions

View file

@ -27,7 +27,6 @@
#include "libc/testlib/testlib.h"
#include "libc/x/x.h"
#include "net/http/http.h"
#include "net/http/uri.h"
struct HttpRequest req[1];
@ -39,10 +38,6 @@ static char *slice(const char *m, struct HttpRequestSlice s) {
return p;
}
static unsigned version(const char *m) {
return ParseHttpVersion(m + req->version.a, req->version.b - req->version.a);
}
void SetUp(void) {
InitHttpRequest(req);
}
@ -51,9 +46,9 @@ void TearDown(void) {
DestroyHttpRequest(req);
}
/* TEST(ParseHttpRequest, soLittleState) { */
/* ASSERT_EQ(280, sizeof(struct HttpRequest)); */
/* } */
TEST(ParseHttpRequest, soLittleState) {
ASSERT_LE(sizeof(struct HttpRequest), 512);
}
TEST(ParseHttpRequest, testEmpty_tooShort) {
EXPECT_EQ(0, ParseHttpRequest(req, "", 0));
@ -68,7 +63,7 @@ TEST(ParseHttpRequest, testNoHeaders) {
EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m)));
EXPECT_EQ(kHttpGet, req->method);
EXPECT_STREQ("/foo", gc(slice(m, req->uri)));
EXPECT_STREQ("HTTP/1.0", gc(slice(m, req->version)));
EXPECT_EQ(10, req->version);
}
TEST(ParseHttpRequest, testSomeHeaders) {
@ -80,7 +75,7 @@ Content-Length: 0\r\n\
EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m)));
EXPECT_EQ(kHttpPost, req->method);
EXPECT_STREQ("/foo?bar%20hi", gc(slice(m, req->uri)));
EXPECT_STREQ("HTTP/1.0", gc(slice(m, req->version)));
EXPECT_EQ(10, req->version);
EXPECT_STREQ("foo.example", gc(slice(m, req->headers[kHttpHost])));
EXPECT_STREQ("0", gc(slice(m, req->headers[kHttpContentLength])));
EXPECT_STREQ("", gc(slice(m, req->headers[kHttpEtag])));
@ -91,8 +86,7 @@ TEST(ParseHttpRequest, testHttp101) {
EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m)));
EXPECT_EQ(kHttpGet, req->method);
EXPECT_STREQ("/", gc(slice(m, req->uri)));
EXPECT_STREQ("HTTP/1.1", gc(slice(m, req->version)));
EXPECT_EQ(101, version(m));
EXPECT_EQ(11, req->version);
}
TEST(ParseHttpRequest, testHttp100) {
@ -100,17 +94,48 @@ TEST(ParseHttpRequest, testHttp100) {
EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m)));
EXPECT_EQ(kHttpGet, req->method);
EXPECT_STREQ("/", gc(slice(m, req->uri)));
EXPECT_STREQ("HTTP/1.0", gc(slice(m, req->version)));
EXPECT_EQ(100, version(m));
EXPECT_EQ(10, req->version);
}
TEST(ParseHttpRequest, testHttp009) {
TEST(ParseHttpRequest, testUnknownMethod_canBeUsedIfYouWant) {
static const char m[] = "#%*+_^ / HTTP/1.0\r\n\r\n";
EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m)));
EXPECT_FALSE(req->method);
EXPECT_STREQ("WUT", kHttpMethod[req->method]);
EXPECT_STREQ("#%*+_^", gc(slice(m, req->xmethod)));
}
TEST(ParseHttpRequest, testIllegalMethod) {
static const char m[] = "ehd@oruc / HTTP/1.0\r\n\r\n";
EXPECT_EQ(-1, ParseHttpRequest(req, m, strlen(m)));
EXPECT_STREQ("WUT", kHttpMethod[req->method]);
}
TEST(ParseHttpRequest, testIllegalMethodCasing_weAllowItAndPreserveIt) {
static const char m[] = "get / HTTP/1.0\r\n\r\n";
EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m)));
EXPECT_STREQ("GET", kHttpMethod[req->method]);
EXPECT_STREQ("get", gc(slice(m, req->xmethod)));
}
TEST(ParseHttpRequest, testEmptyMethod_isntAllowed) {
static const char m[] = " / HTTP/1.0\r\n\r\n";
EXPECT_EQ(-1, ParseHttpRequest(req, m, strlen(m)));
EXPECT_STREQ("WUT", kHttpMethod[req->method]);
}
TEST(ParseHttpRequest, testEmptyUri_isntAllowed) {
static const char m[] = "GET HTTP/1.0\r\n\r\n";
EXPECT_EQ(-1, ParseHttpRequest(req, m, strlen(m)));
EXPECT_STREQ("GET", kHttpMethod[req->method]);
}
TEST(ParseHttpRequest, testHttp09) {
static const char m[] = "GET /\r\n\r\n";
EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m)));
EXPECT_EQ(kHttpGet, req->method);
EXPECT_STREQ("/", gc(slice(m, req->uri)));
EXPECT_STREQ("", gc(slice(m, req->version)));
EXPECT_EQ(9, version(m));
EXPECT_EQ(9, req->version);
}
TEST(ParseHttpRequest, testLeadingLineFeeds_areIgnored) {
@ -153,7 +178,7 @@ Content-Length: 0\n\
EXPECT_EQ(strlen(m) - 1, ParseHttpRequest(req, m, strlen(m)));
EXPECT_EQ(kHttpPost, req->method);
EXPECT_STREQ("/foo?bar%20hi", gc(slice(m, req->uri)));
EXPECT_STREQ("HTTP/1.0", gc(slice(m, req->version)));
EXPECT_EQ(10, req->version);
EXPECT_STREQ("foo.example", gc(slice(m, req->headers[kHttpHost])));
EXPECT_STREQ("0", gc(slice(m, req->headers[kHttpContentLength])));
EXPECT_STREQ("", gc(slice(m, req->headers[kHttpEtag])));
@ -174,7 +199,7 @@ Accept-Language: en-US,en;q=0.9\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_EQ(11, 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])));
@ -193,6 +218,54 @@ X-User-Agent: hi\r\n\
EXPECT_STREQ("hi", gc(slice(m, req->xheaders.p[0].v)));
}
TEST(ParseHttpRequest, testNormalHeaderOnMultipleLines_getsOverwritten) {
static const char m[] = "\
GET / HTTP/1.1\r\n\
Content-Type: text/html\r\n\
Content-Type: text/plain\r\n\
\r\n";
EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m)));
EXPECT_STREQ("text/plain", gc(slice(m, req->headers[kHttpContentType])));
ASSERT_EQ(0, req->xheaders.n);
}
TEST(ParseHttpRequest, testCommaSeparatedOnMultipleLines_becomesLinear) {
static const char m[] = "\
GET / HTTP/1.1\r\n\
Accept: text/html\r\n\
Accept: text/plain\r\n\
\r\n";
EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m)));
EXPECT_STREQ("text/html", gc(slice(m, req->headers[kHttpAccept])));
ASSERT_EQ(1, req->xheaders.n);
EXPECT_STREQ("Accept", gc(slice(m, req->xheaders.p[0].k)));
EXPECT_STREQ("text/plain", gc(slice(m, req->xheaders.p[0].v)));
}
TEST(HeaderHasSubstring, testHeaderSpansMultipleLines) {
static const char m[] = "\
GET / HTTP/1.1\r\n\
Accept-Encoding: deflate\r\n\
ACCEPT-ENCODING: gzip\r\n\
ACCEPT-encoding: bzip2\r\n\
\r\n";
EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m)));
EXPECT_TRUE(HeaderHasSubstring(req, m, kHttpAcceptEncoding, "gzip", -1));
EXPECT_TRUE(HeaderHasSubstring(req, m, kHttpAcceptEncoding, "deflate", -1));
EXPECT_FALSE(HeaderHasSubstring(req, m, kHttpAcceptEncoding, "funzip", -1));
}
TEST(HeaderHasSubstring, testHeaderOnSameLIne) {
static const char m[] = "\
GET / HTTP/1.1\r\n\
Accept-Encoding: deflate, gzip, bzip2\r\n\
\r\n";
EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m)));
EXPECT_TRUE(HeaderHasSubstring(req, m, kHttpAcceptEncoding, "gzip", -1));
EXPECT_TRUE(HeaderHasSubstring(req, m, kHttpAcceptEncoding, "deflate", -1));
EXPECT_FALSE(HeaderHasSubstring(req, m, kHttpAcceptEncoding, "funzip", -1));
}
TEST(ParseHttpRequest, testHeaderValuesWithWhitespace_getsTrimmed) {
static const char m[] = "\
OPTIONS * HTTP/1.0\r\n\
@ -231,6 +304,15 @@ Host: \r\n\
EXPECT_EQ(req->headers[kHttpHost].a, req->headers[kHttpHost].b);
}
TEST(IsMimeType, test) {
ASSERT_TRUE(IsMimeType("text/plain", -1, "text/plain"));
ASSERT_TRUE(IsMimeType("TEXT/PLAIN", -1, "text/plain"));
ASSERT_TRUE(IsMimeType("TEXT/PLAIN ", -1, "text/plain"));
ASSERT_TRUE(IsMimeType("text/plain; charset=utf-8", -1, "text/plain"));
ASSERT_FALSE(IsMimeType("TEXT/PLAI ", -1, "text/plain"));
ASSERT_FALSE(IsMimeType("", -1, "text/plain"));
}
void DoTiniestHttpRequest(void) {
static const char m[] = "\
GET /\r\n\
@ -290,3 +372,24 @@ BENCH(ParseHttpRequest, bench) {
EZBENCH2("DoStandardChromeRequest", donothing, DoStandardChromeRequest());
EZBENCH2("DoUnstandardChromeRequest", donothing, DoUnstandardChromeRequest());
}
BENCH(HeaderHasSubstring, bench) {
static const char m[] = "\
GET / HTTP/1.1\r\n\
X-In-Your-Way-A: a\r\n\
X-In-Your-Way-B: b\r\n\
X-In-Your-Way-C: b\r\n\
Accept-Encoding: deflate\r\n\
ACCEPT-ENCODING: gzip\r\n\
ACCEPT-encoding: bzip2\r\n\
\r\n";
EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m)));
EZBENCH2("HeaderHasSubstring text/plain", donothing,
HeaderHasSubstring(req, m, kHttpAccept, "text/plain", 7));
EZBENCH2("HeaderHasSubstring deflate", donothing,
HeaderHasSubstring(req, m, kHttpAcceptEncoding, "deflate", 7));
EZBENCH2("HeaderHasSubstring gzip", donothing,
HeaderHasSubstring(req, m, kHttpAcceptEncoding, "gzip", 4));
EZBENCH2("IsMimeType", donothing,
IsMimeType("text/plain; charset=utf-8", -1, "text/plain"));
}