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

@ -25,29 +25,29 @@
char *escapeparam(const char *s) {
struct EscapeResult r;
r = EscapeUrlParam(s, -1);
r = EscapeParam(s, -1);
ASSERT_EQ(strlen(r.data), r.size);
return r.data;
}
TEST(EscapeUrlParam, test) {
TEST(EscapeParam, test) {
EXPECT_STREQ("abc%20%26%3C%3E%22%27%01%02",
gc(escapeparam("abc &<>\"'\1\2")));
}
TEST(EscapeUrlParam, testLargeGrowth) {
TEST(EscapeParam, testLargeGrowth) {
EXPECT_STREQ("%22%22%22", gc(escapeparam("\"\"\"")));
}
TEST(EscapeUrlParam, testEmpty) {
TEST(EscapeParam, testEmpty) {
EXPECT_STREQ("", gc(escapeparam("")));
}
TEST(EscapeUrlParam, testAstralPlanes_usesUtf8HexEncoding) {
TEST(EscapeParam, testAstralPlanes_usesUtf8HexEncoding) {
EXPECT_STREQ("%F0%90%8C%B0", escapeparam("𐌰"));
}
BENCH(EscapeUrlParam, bench) {
EZBENCH2("EscapeUrlParam", donothing,
free(EscapeUrlParam(kHyperion, kHyperionSize).data));
BENCH(EscapeParam, bench) {
EZBENCH2("EscapeParam", donothing,
free(EscapeParam(kHyperion, kHyperionSize).data));
}

View file

@ -0,0 +1,96 @@
/*-*- 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/testlib.h"
#include "net/http/http.h"
TEST(IsAcceptableHost, test) {
EXPECT_TRUE(IsAcceptableHost("", -1));
EXPECT_TRUE(IsAcceptableHost("0.0.0.0", -1));
EXPECT_FALSE(IsAcceptableHost("1.2.3", -1));
EXPECT_TRUE(IsAcceptableHost("1.2.3.4", -1));
EXPECT_FALSE(IsAcceptableHost("1.2.3.4.5", -1));
EXPECT_TRUE(IsAcceptableHost("1.2.3.4.5.arpa", -1));
EXPECT_TRUE(IsAcceptableHost("255.255.255.255", -1));
EXPECT_FALSE(IsAcceptableHost("255.255.255", -1));
EXPECT_FALSE(IsAcceptableHost("256.255.255.255", -1));
EXPECT_TRUE(IsAcceptableHost("hello.example", -1));
EXPECT_FALSE(IsAcceptableHost("hello..example", -1));
EXPECT_TRUE(IsAcceptableHost("hello", -1));
EXPECT_FALSE(IsAcceptableHost("hello\177", -1));
EXPECT_FALSE(IsAcceptableHost("hello.example\300\200", -1));
EXPECT_FALSE(IsAcceptableHost(".", -1));
EXPECT_FALSE(IsAcceptableHost(".e", -1));
EXPECT_FALSE(IsAcceptableHost("e.", -1));
EXPECT_FALSE(IsAcceptableHost(".hi.example", -1));
EXPECT_FALSE(IsAcceptableHost("hi..example", -1));
EXPECT_TRUE(IsAcceptableHost("hi-there.example", -1));
EXPECT_TRUE(IsAcceptableHost("_there.example", -1));
EXPECT_TRUE(IsAcceptableHost("-there.example", -1));
EXPECT_TRUE(IsAcceptableHost("there-.example", -1));
EXPECT_FALSE(IsAcceptableHost("ther#e.example", -1));
EXPECT_TRUE(IsAcceptableHost("localhost", -1));
}
TEST(IsAcceptablePort, test) {
EXPECT_TRUE(IsAcceptablePort("", -1));
EXPECT_TRUE(IsAcceptablePort("0", -1));
EXPECT_TRUE(IsAcceptablePort("65535", -1));
EXPECT_FALSE(IsAcceptablePort("65536", -1));
EXPECT_FALSE(IsAcceptablePort("-1", -1));
EXPECT_FALSE(IsAcceptablePort("http", -1));
}
TEST(ParseIp, test) {
EXPECT_EQ(-1, ParseIp("", -1));
EXPECT_EQ(0x00000000, ParseIp("0.0.0.0", -1));
EXPECT_EQ(0x01020304, ParseIp("1.2.3.4", -1));
EXPECT_EQ(0x80020304, ParseIp("128.2.3.4", -1));
EXPECT_EQ(0xFFFFFFFF, ParseIp("255.255.255.255", -1));
EXPECT_EQ(0xcb007100, ParseIp("203.0.113.0", -1));
EXPECT_EQ(0x00000000, ParseIp("...", -1)); /* meh */
EXPECT_EQ(-1, ParseIp("128.2..3.4", -1));
EXPECT_EQ(-1, ParseIp("1.2.3", -1));
EXPECT_EQ(-1, ParseIp("256.255.255.255", -1));
EXPECT_EQ(-1, ParseIp("1.2.3.4.5", -1));
EXPECT_EQ(-1, ParseIp("1.2.3.4.5.arpa", -1));
EXPECT_EQ(-1, ParseIp("255.255.255", -1));
EXPECT_EQ(-1, ParseIp("hello", -1));
EXPECT_EQ(-1, ParseIp("hello\177", -1));
EXPECT_EQ(-1, ParseIp("hello.example\300\200", -1));
EXPECT_EQ(-1, ParseIp(".", -1));
EXPECT_EQ(-1, ParseIp(".e", -1));
EXPECT_EQ(-1, ParseIp("e.", -1));
EXPECT_EQ(-1, ParseIp(".hi.example", -1));
EXPECT_EQ(-1, ParseIp("hi..example", -1));
EXPECT_EQ(-1, ParseIp("hi-there.example", -1));
EXPECT_EQ(-1, ParseIp("_there.example", -1));
EXPECT_EQ(-1, ParseIp("-there.example", -1));
EXPECT_EQ(-1, ParseIp("there-.example", -1));
EXPECT_EQ(-1, ParseIp("ther#e.example", -1));
EXPECT_EQ(-1, ParseIp("localhost", -1));
EXPECT_EQ(-1, ParseIp("hello.example", -1));
EXPECT_EQ(-1, ParseIp("hello..example", -1));
}
BENCH(IsAcceptableHost, bench) {
EZBENCH2("IsAcceptableHost 127.0.0.1", donothing,
IsAcceptableHost("127.0.0.1", 9));
EZBENCH2("IsAcceptablePort 80", donothing, IsAcceptablePort("80", 2));
}

View file

@ -1,59 +0,0 @@
/*-*- 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"
TEST(IsAcceptableHostPort, test) {
EXPECT_FALSE(IsAcceptableHostPort("", -1));
EXPECT_FALSE(IsAcceptableHostPort(":", -1));
EXPECT_FALSE(IsAcceptableHostPort(":80", -1));
EXPECT_TRUE(IsAcceptableHostPort("0.0.0.0", -1));
EXPECT_FALSE(IsAcceptableHostPort("1.2.3", -1));
EXPECT_TRUE(IsAcceptableHostPort("1.2.3.4", -1));
EXPECT_FALSE(IsAcceptableHostPort("1.2.3.4.5", -1));
EXPECT_TRUE(IsAcceptableHostPort("1.2.3.4.5.arpa", -1));
EXPECT_TRUE(IsAcceptableHostPort("255.255.255.255", -1));
EXPECT_FALSE(IsAcceptableHostPort("255.255.255", -1));
EXPECT_FALSE(IsAcceptableHostPort("256.255.255.255", -1));
EXPECT_TRUE(IsAcceptableHostPort("hello.example", -1));
EXPECT_FALSE(IsAcceptableHostPort("hello..example", -1));
EXPECT_TRUE(IsAcceptableHostPort("hello.example:80", -1));
EXPECT_FALSE(IsAcceptableHostPort("hello.example:80:", -1));
EXPECT_FALSE(IsAcceptableHostPort("hello.example::80", -1));
EXPECT_FALSE(IsAcceptableHostPort("hello.example:-80", -1));
EXPECT_FALSE(IsAcceptableHostPort(":80", -1));
EXPECT_TRUE(IsAcceptableHostPort("hello.example:65535", -1));
EXPECT_FALSE(IsAcceptableHostPort("hello.example:65536", -1));
EXPECT_FALSE(IsAcceptableHostPort("hello.example:-80", -1));
EXPECT_FALSE(IsAcceptableHostPort(" hello .example:80", -1));
EXPECT_FALSE(IsAcceptableHostPort("hello.example:80h", -1));
EXPECT_TRUE(IsAcceptableHostPort("hello", -1));
EXPECT_FALSE(IsAcceptableHostPort("hello\177", -1));
EXPECT_FALSE(IsAcceptableHostPort("hello.example\300\200:80", -1));
EXPECT_FALSE(IsAcceptableHostPort(".", -1));
EXPECT_FALSE(IsAcceptableHostPort(".e", -1));
EXPECT_FALSE(IsAcceptableHostPort("e.", -1));
EXPECT_FALSE(IsAcceptableHostPort(".hi.example", -1));
EXPECT_FALSE(IsAcceptableHostPort("hi..example", -1));
EXPECT_TRUE(IsAcceptableHostPort("hi-there.example", -1));
EXPECT_TRUE(IsAcceptableHostPort("_there.example", -1));
EXPECT_TRUE(IsAcceptableHostPort("-there.example", -1));
EXPECT_TRUE(IsAcceptableHostPort("there-.example", -1));
EXPECT_FALSE(IsAcceptableHostPort("ther#e.example", -1));
}

View file

@ -20,13 +20,16 @@
#include "net/http/http.h"
TEST(ParseContentLength, test) {
EXPECT_EQ(0, ParseContentLength("", 0));
EXPECT_EQ(-1, ParseContentLength(0, 0));
EXPECT_EQ(-1, ParseContentLength("", 0));
EXPECT_EQ(-1, ParseContentLength("-1", 2));
EXPECT_EQ(-1, ParseContentLength("-2", 2));
EXPECT_EQ(-1, ParseContentLength("e", -1));
EXPECT_EQ(-1, ParseContentLength(",", -1));
EXPECT_EQ(-1, ParseContentLength("\0", 1));
EXPECT_EQ(0, ParseContentLength("0", 1));
EXPECT_EQ(1, ParseContentLength("1", 1));
EXPECT_EQ(0x7fffffff, ParseContentLength("2147483647", 10));
EXPECT_EQ(-1, ParseContentLength("2147483648", 10));
EXPECT_EQ(-1, ParseContentLength("9223372036854775808", 19));
EXPECT_EQ(-1, ParseContentLength("88223372036854775808", 20));
EXPECT_EQ(42, ParseContentLength("42, 42", -1)); /* RFC7230 § 3.3.2 */
EXPECT_EQ(0x000000ffffffffff, ParseContentLength("1099511627775", -1));
EXPECT_EQ(-1, ParseContentLength("1099511627776", -1));
}

View file

@ -20,11 +20,11 @@
#include "libc/testlib/testlib.h"
#include "net/http/http.h"
TEST(ParseHttpRange, testEmptyHack) {
TEST(ParseHttpRange, testEmptyHack_refusedBecauseItWontEncodeInContentRange) {
long start, length;
const char *s = "bytes=-0";
EXPECT_TRUE(ParseHttpRange(s, strlen(s), 100, &start, &length));
EXPECT_EQ(100, start);
EXPECT_FALSE(ParseHttpRange(s, strlen(s), 100, &start, &length));
EXPECT_EQ(0, start);
EXPECT_EQ(0, length);
}
@ -36,6 +36,22 @@ TEST(ParseHttpRange, testEmptyRange_isntEmpty) {
EXPECT_EQ(1, length);
}
TEST(ParseHttpRange, testEmptyRangeOfOneByteFile_itWorks) {
long start, length;
const char *s = "bytes=0-0";
EXPECT_TRUE(ParseHttpRange(s, strlen(s), 1, &start, &length));
EXPECT_EQ(0, start);
EXPECT_EQ(1, length);
}
TEST(ParseHttpRange, testEmptyRangeOfEmptyFile_outOfRange) {
long start, length;
const char *s = "bytes=0-0";
EXPECT_FALSE(ParseHttpRange(s, strlen(s), 0, &start, &length));
EXPECT_EQ(0, start);
EXPECT_EQ(0, length);
}
TEST(ParseHttpRange, testInclusiveIndexing) {
long start, length;
const char *s = "bytes=0-10";
@ -81,7 +97,7 @@ TEST(ParseHttpRange, testOutOfRange) {
const char *s = "bytes=0-100";
EXPECT_FALSE(ParseHttpRange(s, strlen(s), 100, &start, &length));
EXPECT_EQ(0, start);
EXPECT_EQ(101, length);
EXPECT_EQ(0, length);
}
TEST(ParseHttpRange, testInvalidRange) {
@ -104,6 +120,14 @@ TEST(ParseHttpRange, testOverflow_duringAddition_setsErrorRange) {
long start, length;
const char *s = "bytes=4611686018427387904-4611686018427387915";
EXPECT_FALSE(ParseHttpRange(s, strlen(s), 100, &start, &length));
EXPECT_EQ(4611686018427387904, start);
EXPECT_EQ(12, length);
EXPECT_EQ(0, start);
EXPECT_EQ(0, length);
}
TEST(ParseHttpRange, testMultipartRange_notImplemented) {
long start, length;
const char *s = "bytes=0-100,200-300";
EXPECT_FALSE(ParseHttpRange(s, strlen(s), 100, &start, &length));
EXPECT_EQ(0, start);
EXPECT_EQ(0, length);
}

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"));
}

View file

@ -24,122 +24,135 @@
#include "libc/testlib/testlib.h"
#include "net/http/url.h"
TEST(ParseRequestUri, testEmpty) {
TEST(ParseUrl, testEmpty) {
struct Url h;
gc(ParseRequestUri(0, 0, &h));
gc(ParseUrl(0, 0, &h));
gc(h.params.p);
ASSERT_EQ(0, h.params.n);
ASSERT_STREQ("", gc(EncodeUrl(&h, 0)));
}
TEST(ParseRequestUri, testFragment) {
TEST(ParseUrl, testFragment) {
struct Url h;
gc(ParseRequestUri("#x", -1, &h));
gc(ParseUrl("#x", -1, &h));
gc(h.params.p);
ASSERT_EQ(0, h.path.n);
ASSERT_EQ(1, h.fragment.n);
ASSERT_BINEQ(u"x", h.fragment.p);
ASSERT_STREQ("#x", gc(EncodeUrl(&h, 0)));
}
TEST(ParseRequestUri, testFragmentAbsent_isNull) {
TEST(ParseUrl, testFragmentAbsent_isNull) {
struct Url h;
gc(ParseRequestUri("", -1, &h));
gc(ParseUrl("", -1, &h));
gc(h.params.p);
ASSERT_EQ(0, h.fragment.p);
ASSERT_EQ(0, h.fragment.n);
ASSERT_STREQ("", gc(EncodeUrl(&h, 0)));
}
TEST(ParseRequestUri, testFragmentEmpty_isNonNull) {
TEST(ParseUrl, testFragmentEmpty_isNonNull) {
struct Url h;
gc(ParseRequestUri("#", -1, &h));
gc(ParseUrl("#", -1, &h)); /* python's uri parser is wrong here */
gc(h.params.p);
ASSERT_NE(0, h.fragment.p);
ASSERT_EQ(0, h.fragment.n);
ASSERT_STREQ("#", gc(EncodeUrl(&h, 0)));
}
TEST(ParseRequestUri, testPathFragment) {
TEST(ParseUrl, testPathFragment) {
struct Url h;
gc(ParseRequestUri("x#y", -1, &h));
gc(ParseUrl("x#y", -1, &h));
gc(h.params.p);
ASSERT_EQ(1, h.path.n);
ASSERT_EQ('x', h.path.p[0]);
ASSERT_EQ(1, h.fragment.n);
ASSERT_EQ('y', h.fragment.p[0]);
ASSERT_STREQ("x#y", gc(EncodeUrl(&h, 0)));
}
TEST(ParseRequestUri, testAbsolutePath) {
TEST(ParseUrl, testAbsolutePath) {
struct Url h;
gc(ParseRequestUri("/x/y", -1, &h));
gc(ParseUrl("/x/y", -1, &h));
gc(h.params.p);
ASSERT_EQ(4, h.path.n);
ASSERT_BINEQ(u"/x/y", h.path.p);
ASSERT_STREQ("/x/y", gc(EncodeUrl(&h, 0)));
}
TEST(ParseRequestUri, testRelativePath1) {
TEST(ParseUrl, testRelativePath1) {
struct Url h;
gc(ParseRequestUri("x", -1, &h));
gc(ParseUrl("x", -1, &h));
gc(h.params.p);
ASSERT_EQ(1, h.path.n);
ASSERT_EQ('x', h.path.p[0]);
ASSERT_STREQ("x", gc(EncodeUrl(&h, 0)));
}
TEST(ParseRequestUri, testOptions) {
TEST(ParseUrl, testOptions) {
struct Url h;
gc(ParseRequestUri("*", -1, &h));
gc(ParseUrl("*", -1, &h));
gc(h.params.p);
ASSERT_EQ(1, h.path.n);
ASSERT_EQ('*', h.path.p[0]);
ASSERT_STREQ("*", gc(EncodeUrl(&h, 0)));
}
TEST(ParseRequestUri, testRelativePath2) {
TEST(ParseUrl, testRelativePath2) {
struct Url h;
gc(ParseRequestUri("x/y", -1, &h));
gc(ParseUrl("x/y", -1, &h));
gc(h.params.p);
ASSERT_EQ(3, h.path.n);
ASSERT_BINEQ(u"x/y", h.path.p);
ASSERT_STREQ("x/y", gc(EncodeUrl(&h, 0)));
}
TEST(ParseRequestUri, testRoot) {
TEST(ParseUrl, testRoot) {
struct Url h;
gc(ParseRequestUri("/", -1, &h));
gc(ParseUrl("/", -1, &h));
gc(h.params.p);
ASSERT_EQ(1, h.path.n);
ASSERT_EQ('/', h.path.p[0]);
ASSERT_STREQ("/", gc(EncodeUrl(&h, 0)));
}
TEST(ParseRequestUri, testSchemePath) {
TEST(ParseUrl, testSchemePath) {
struct Url h;
gc(ParseRequestUri("x:y", -1, &h));
gc(ParseUrl("x:y", -1, &h));
gc(h.params.p);
ASSERT_EQ(1, h.scheme.n);
ASSERT_BINEQ(u"x", h.scheme.p);
ASSERT_EQ(1, h.path.n);
ASSERT_BINEQ(u"y", h.path.p);
ASSERT_STREQ("x:y", gc(EncodeUrl(&h, 0)));
}
TEST(ParseRequestUri, testSchemeAuthority) {
TEST(ParseUrl, testSchemeAuthority) {
struct Url h;
gc(ParseRequestUri("x://y", -1, &h));
gc(ParseUrl("x://y", -1, &h));
gc(h.params.p);
ASSERT_EQ(1, h.scheme.n);
ASSERT_EQ('x', h.scheme.p[0]);
ASSERT_EQ(1, h.host.n);
ASSERT_EQ('y', h.host.p[0]);
ASSERT_STREQ("x://y", gc(EncodeUrl(&h, 0)));
}
TEST(ParseRequestUri, testParamsQuestion_doesntTurnIntoSpace) {
TEST(ParseUrl, testParamsQuestion_doesntTurnIntoSpace) {
struct Url h;
gc(ParseRequestUri("x?+", -1, &h));
gc(ParseUrl("x?+", -1, &h));
gc(h.params.p);
ASSERT_EQ(1, h.path.n);
ASSERT_BINEQ(u"x", h.path.p);
ASSERT_EQ(1, h.params.n);
ASSERT_EQ(1, h.params.p[0].key.n);
ASSERT_EQ('+', h.params.p[0].key.p[0]);
ASSERT_STREQ("x?%2B", gc(EncodeUrl(&h, 0)));
}
TEST(ParseRequestUri, testUrl) {
TEST(ParseUrl, testUrl) {
struct Url h;
gc(ParseRequestUri("a://b:B@c:C/d?e#f", -1, &h));
gc(ParseUrl("a://b:B@c:C/d?e#f", -1, &h));
gc(h.params.p);
ASSERT_EQ(1, h.scheme.n);
ASSERT_EQ('a', h.scheme.p[0]);
@ -156,14 +169,40 @@ TEST(ParseRequestUri, testUrl) {
ASSERT_EQ(1, h.params.n);
ASSERT_EQ(1, h.params.p[0].key.n);
ASSERT_BINEQ(u"e", h.params.p[0].key.p);
ASSERT_EQ(SIZE_MAX, h.params.p[0].val.n);
ASSERT_EQ(0, h.params.p[0].val.n);
ASSERT_EQ(0, h.params.p[0].val.p);
ASSERT_EQ(1, h.fragment.n);
ASSERT_BINEQ(u"f", h.fragment.p);
ASSERT_STREQ("a://b:B@c:C/d?e#f", gc(EncodeUrl(&h, 0)));
}
TEST(ParseRequestUri, testUrlWithoutScheme) {
TEST(ParseUrl, testEmptyQueryKeyVal_decodesToEmptyStrings) {
struct Url h;
gc(ParseRequestUri("//b@c/d?e#f", -1, &h));
gc(ParseUrl("?=", -1, &h));
gc(h.params.p);
ASSERT_EQ(1, h.params.n);
ASSERT_EQ(0, h.params.p[0].key.n);
ASSERT_NE(0, h.params.p[0].key.p);
ASSERT_EQ(0, h.params.p[0].val.n);
ASSERT_NE(0, h.params.p[0].val.p);
ASSERT_STREQ("?=", gc(EncodeUrl(&h, 0)));
}
TEST(ParseUrl, testMultipleEquals_goesIntoValue) {
struct Url h;
gc(ParseUrl("?==", -1, &h));
gc(h.params.p);
ASSERT_EQ(1, h.params.n);
ASSERT_EQ(0, h.params.p[0].key.n);
ASSERT_NE(0, h.params.p[0].key.p);
ASSERT_EQ(1, h.params.p[0].val.n);
ASSERT_EQ('=', h.params.p[0].val.p[0]);
ASSERT_STREQ("?=%3D", gc(EncodeUrl(&h, 0)));
}
TEST(ParseUrl, testUrlWithoutScheme) {
struct Url h;
gc(ParseUrl("//b@c/d?e#f", -1, &h));
gc(h.params.p);
ASSERT_EQ(0, h.scheme.n);
ASSERT_EQ(1, h.user.n);
@ -175,14 +214,16 @@ TEST(ParseRequestUri, testUrlWithoutScheme) {
ASSERT_EQ(1, h.params.n);
ASSERT_EQ(1, h.params.p[0].key.n);
ASSERT_BINEQ(u"e", h.params.p[0].key.p);
ASSERT_EQ(SIZE_MAX, h.params.p[0].val.n);
ASSERT_EQ(0, h.params.p[0].val.n);
ASSERT_EQ(0, h.params.p[0].val.p);
ASSERT_EQ(1, h.fragment.n);
ASSERT_BINEQ(u"f", h.fragment.p);
ASSERT_STREQ("//b@c/d?e#f", gc(EncodeUrl(&h, 0)));
}
TEST(ParseRequestUri, testUrlWithoutUser) {
TEST(ParseUrl, testUrlWithoutUser) {
struct Url h;
gc(ParseRequestUri("a://c/d?e#f", -1, &h));
gc(ParseUrl("a://c/d?e#f", -1, &h));
gc(h.params.p);
ASSERT_EQ(1, h.scheme.n);
ASSERT_EQ('a', h.scheme.p[0]);
@ -196,24 +237,159 @@ TEST(ParseRequestUri, testUrlWithoutUser) {
ASSERT_EQ(1, h.params.n);
ASSERT_EQ(1, h.params.p[0].key.n);
ASSERT_EQ('e', h.params.p[0].key.p[0]);
ASSERT_EQ(SIZE_MAX, h.params.p[0].val.n);
ASSERT_EQ(0, h.params.p[0].val.n);
ASSERT_EQ(0, h.params.p[0].val.p);
ASSERT_EQ(1, h.fragment.n);
ASSERT_EQ('f', h.fragment.p[0]);
ASSERT_STREQ("a://c/d?e#f", gc(EncodeUrl(&h, 0)));
}
TEST(ParseRequestUri, testLolv6) {
TEST(ParseUrl, testEmptyParams_absentCanBeDiscerned) {
struct Url h;
gc(ParseRequestUri("//[::1]:31337", -1, &h));
gc(ParseUrl("", -1, &h));
gc(h.params.p);
ASSERT_EQ(0, h.params.n);
ASSERT_EQ(NULL, h.params.p);
gc(ParseUrl("?", -1, &h)); /* python's uri parser is wrong here */
gc(h.params.p);
ASSERT_EQ(0, h.params.n);
ASSERT_NE(NULL, h.params.p);
}
TEST(ParseUrl, testWeirdAmps_areReprodicible) {
struct Url h;
gc(ParseUrl("?&&", -1, &h));
gc(h.params.p);
ASSERT_EQ(3, h.params.n);
ASSERT_EQ(0, h.params.p[0].key.n);
ASSERT_NE(0, h.params.p[0].key.p);
ASSERT_EQ(0, h.params.p[0].val.n);
ASSERT_EQ(0, h.params.p[0].val.p);
ASSERT_EQ(0, h.params.p[1].key.n);
ASSERT_NE(0, h.params.p[1].key.p);
ASSERT_EQ(0, h.params.p[1].val.n);
ASSERT_EQ(0, h.params.p[1].val.p);
ASSERT_EQ(0, h.params.p[2].key.n);
ASSERT_NE(0, h.params.p[2].key.p);
ASSERT_EQ(0, h.params.p[2].val.n);
ASSERT_EQ(0, h.params.p[2].val.p);
ASSERT_STREQ("?&&", gc(EncodeUrl(&h, 0)));
}
TEST(ParseUrl, testOpaquePart_canLetQuestionMarkGoInPath) {
struct Url h; /* python's uri parser is wrong here */
gc(ParseUrl("s:o!$%&'()*+,-./09:;=?@AZ_az#fragged", -1, &h));
gc(h.params.p);
ASSERT_EQ(26, h.path.n);
ASSERT_EQ(0, memcmp(h.path.p, "o!$%&'()*+,-./09:;=?@AZ_az", 26));
ASSERT_EQ(0, h.params.n);
ASSERT_EQ(NULL, h.params.p);
ASSERT_STREQ("s:o!$%25&'()*+,-./09:;=%3F@AZ_az#fragged",
gc(EncodeUrl(&h, 0)));
}
TEST(ParseUrl, testSchemePathWithoutAuthority_paramsAreAllowed) {
struct Url h;
gc(ParseUrl("s:/o!$%&'()*+,-./09:;=?@AZ_az#fragged", -1, &h));
gc(h.params.p);
ASSERT_EQ(20, h.path.n);
ASSERT_EQ(0, memcmp(h.path.p, "/o!$%&'()*+,-./09:;=", 20));
ASSERT_EQ(1, h.params.n);
ASSERT_STREQ("s:/o!$%25&'()*+,-./09:;=?%40AZ_az#fragged",
gc(EncodeUrl(&h, 0)));
}
TEST(ParseUrl, testOpaquePart_permitsPercentEncoding) {
struct Url h;
gc(ParseUrl("s:%2Fo!$%&'()*+,-./09:;=?@AZ_az#fragged", -1, &h));
gc(h.params.p);
ASSERT_EQ(27, h.path.n);
ASSERT_EQ(0, memcmp(h.path.p, "/o!$%&'()*+,-./09:;=?@AZ_az", 27));
ASSERT_EQ(0, h.params.n);
ASSERT_STREQ("s:/o!$%25&\'()*+,-./09:;=%3F@AZ_az#fragged",
gc(EncodeUrl(&h, 0)));
}
TEST(ParseUrl, testTelephone) {
struct Url h;
gc(ParseUrl("tel:+1-212-867-5309", -1, &h));
gc(h.params.p);
ASSERT_EQ(15, h.path.n);
ASSERT_BINEQ(u"+1-212-867-5309", h.path.p);
ASSERT_STREQ("tel:+1-212-867-5309", gc(EncodeUrl(&h, 0)));
}
TEST(ParseUrl, testLolv6) {
struct Url h;
gc(ParseUrl("//[::1]:31337", -1, &h));
gc(h.params.p);
ASSERT_EQ(3, h.host.n);
ASSERT_BINEQ(u"::1", h.host.p);
ASSERT_EQ(5, h.port.n);
ASSERT_BINEQ(u"31337", h.port.p);
ASSERT_STREQ("//[::1]:31337", gc(EncodeUrl(&h, 0)));
}
TEST(ParseRequestUri, testUrlWithoutParams) {
TEST(ParseUrl, testLolV6_withoutPort) {
struct Url h;
gc(ParseRequestUri("a://b@c/d#f", -1, &h));
gc(ParseUrl("//[::1]", -1, &h));
gc(h.params.p);
ASSERT_STREQ("//[::1]", gc(EncodeUrl(&h, 0)));
}
TEST(ParseUrl, testLolv7) {
struct Url h;
gc(ParseUrl("//[vf.::1]", -1, &h));
gc(h.params.p);
ASSERT_EQ(6, h.host.n);
ASSERT_BINEQ(u"vf.::1", h.host.p);
ASSERT_EQ(0, h.port.n);
ASSERT_EQ(0, h.port.p);
ASSERT_STREQ("//[vf.::1]", gc(EncodeUrl(&h, 0)));
}
TEST(ParseUrl, testLolv7WithoutColon_weCantProduceLegalEncodingSadly) {
struct Url h;
gc(ParseUrl("//[v7.7.7.7]", -1, &h));
gc(h.params.p);
ASSERT_STREQ("//v7.7.7.7", gc(EncodeUrl(&h, 0)));
}
TEST(ParseUrl, testObviouslyIllegalIpLiteral_getsTreatedAsRegName) {
struct Url h;
gc(ParseUrl("//[vf.::1%00]", -1, &h));
gc(h.params.p);
ASSERT_STREQ("//vf.%3A%3A1%00", gc(EncodeUrl(&h, 0)));
}
TEST(ParseHost, test) {
struct Url h = {0};
gc(ParseHost("foo.example:80", -1, &h));
gc(h.params.p);
ASSERT_EQ(11, h.host.n);
ASSERT_BINEQ(u"foo.example", h.host.p);
ASSERT_EQ(2, h.port.n);
ASSERT_BINEQ(u"80", h.port.p);
ASSERT_STREQ("//foo.example:80", gc(EncodeUrl(&h, 0)));
}
TEST(ParseHost, testObviouslyIllegalIpLiteral_getsTreatedAsRegName) {
struct Url h = {0};
gc(ParseHost("[vf.::1%00]", -1, &h));
gc(h.params.p);
ASSERT_STREQ("//vf.%3A%3A1%00", gc(EncodeUrl(&h, 0)));
}
TEST(EncodeUrl, testHostPortPlacedInHostField_ungoodIdea) {
struct Url h = {0};
h.host.n = strlen("foo.example:80");
h.host.p = "foo.example:80";
ASSERT_STREQ("//foo.example%3A80", gc(EncodeUrl(&h, 0)));
}
TEST(ParseUrl, testUrlWithoutParams) {
struct Url h;
gc(ParseUrl("a://b@c/d#f", -1, &h));
gc(h.params.p);
ASSERT_EQ(1, h.scheme.n);
ASSERT_EQ('a', h.scheme.p[0]);
@ -226,6 +402,7 @@ TEST(ParseRequestUri, testUrlWithoutParams) {
ASSERT_EQ(0, h.params.n);
ASSERT_EQ(1, h.fragment.n);
ASSERT_EQ('f', h.fragment.p[0]);
ASSERT_STREQ("a://b@c/d#f", gc(EncodeUrl(&h, 0)));
}
TEST(ParseUrl, testLatin1_doesNothing) {
@ -235,6 +412,7 @@ TEST(ParseUrl, testLatin1_doesNothing) {
gc(h.params.p);
ASSERT_EQ(1, h.path.n);
ASSERT_EQ(0, memcmp("\377", h.path.p, 1));
ASSERT_STREQ("%FF", gc(EncodeUrl(&h, 0)));
}
TEST(ParseRequestUri, testLatin1_expandsMemoryToUtf8) {
@ -246,40 +424,202 @@ TEST(ParseRequestUri, testLatin1_expandsMemoryToUtf8) {
ASSERT_EQ(0, memcmp("\303\277", h.path.p, 2));
}
TEST(ParseRequestUri, testPercentShrinkingMemory) {
TEST(ParseUrl, testPercentShrinkingMemory) {
struct Url h;
gc(ParseRequestUri("%Ff", 3, &h));
gc(ParseUrl("%Ff", 3, &h));
gc(h.params.p);
ASSERT_EQ(1, h.path.n);
ASSERT_EQ(0, memcmp("\377", h.path.p, 1));
ASSERT_STREQ("%FF", gc(EncodeUrl(&h, 0)));
}
TEST(ParseRequestUri, testBadPercent_getsIgnored) {
TEST(ParseUrl, testEscapingWontOverrun) {
struct Url h;
gc(ParseRequestUri("%FZ", 3, &h));
char b[1] = {'%'};
gc(ParseUrl(b, 1, &h));
gc(h.params.p);
ASSERT_EQ(1, h.path.n);
ASSERT_EQ(0, memcmp("%", h.path.p, 1));
ASSERT_STREQ("%25", gc(EncodeUrl(&h, 0)));
}
TEST(ParseUrl, testBadPercent_getsIgnored) {
struct Url h;
gc(ParseUrl("%FZ", 3, &h));
gc(h.params.p);
ASSERT_EQ(3, h.path.n);
ASSERT_EQ(0, memcmp("%FZ", h.path.p, 3));
}
TEST(ParseRequestUri, testFileUrl) {
TEST(ParseUrl, testFileUrl) {
struct Url h;
gc(ParseRequestUri("file:///etc/passwd", -1, &h));
gc(ParseUrl("file:///etc/passwd", -1, &h));
gc(h.params.p);
ASSERT_EQ(4, h.scheme.n);
ASSERT_BINEQ(u"file", h.scheme.p);
ASSERT_EQ(0, h.host.n);
ASSERT_NE(0, h.host.p);
ASSERT_EQ(0, h.port.n);
ASSERT_EQ(0, h.port.p);
ASSERT_EQ(11, h.path.n);
ASSERT_BINEQ(u"/etc/passwd", h.path.p);
ASSERT_STREQ("file:///etc/passwd", gc(EncodeUrl(&h, 0)));
}
TEST(ParseRequestUri, testZipUri2) {
TEST(EncodeUrl, testModifyingParseResultAndReencoding_addsStructure) {
size_t n;
struct Url h;
gc(ParseRequestUri("zip:etc/passwd", -1, &h));
gc(ParseUrl("rel", -1, &h));
gc(h.params.p);
h.host.n = 7;
h.host.p = "justine";
ASSERT_STREQ("//justine/rel", gc(EncodeUrl(&h, &n)));
ASSERT_EQ(13, n);
}
TEST(EncodeUrl, testTortureCharacters_doesWhatYouAskItToDoButSchemeCantEscape) {
size_t n;
struct Url h;
memset(&h, 0, sizeof(h));
h.scheme.n = 1;
h.scheme.p = "/";
h.user.n = 1;
h.user.p = "";
h.pass.n = 1;
h.pass.p = "";
h.host.n = 1;
h.host.p = "";
h.port.n = 1;
h.port.p = "";
h.path.n = 1;
h.path.p = "";
h.params = (struct UrlParams){.n = 1,
.p = (struct UrlParam[]){{
.key = (struct UrlView){.n = 1, .p = ""},
.val = (struct UrlView){.n = 1, .p = ""},
}}};
h.fragment.n = 1;
h.fragment.p = "";
ASSERT_STREQ("/://%00:%00@%00:%00/%00?%00=%00#%00", gc(EncodeUrl(&h, &n)));
ASSERT_EQ(35, n);
}
TEST(EncodeUrl, testUserPassPort_allDependOnHostNonAbsence) {
size_t n;
struct Url h;
memset(&h, 0, sizeof(h));
h.scheme.n = 1;
h.scheme.p = "/";
h.user.n = 1;
h.user.p = "";
h.pass.n = 1;
h.pass.p = "";
h.host.n = 0;
h.host.p = 0;
h.port.n = 1;
h.port.p = "";
h.path.n = 1;
h.path.p = "";
h.params = (struct UrlParams){.n = 1,
.p = (struct UrlParam[]){{
.key = (struct UrlView){.n = 1, .p = ""},
.val = (struct UrlView){.n = 1, .p = ""},
}}};
h.fragment.n = 1;
h.fragment.p = "";
ASSERT_STREQ("/:%00?%00=%00#%00", gc(EncodeUrl(&h, 0)));
}
TEST(EncodeUrl, testEmptyRegName_isLegal) {
size_t n;
struct Url h;
memset(&h, 0, sizeof(h));
h.scheme.n = 1;
h.scheme.p = "/";
h.user.n = 1;
h.user.p = "";
h.pass.n = 1;
h.pass.p = "";
h.host.n = 0;
h.host.p = "";
h.port.n = 1;
h.port.p = "";
h.path.n = 1;
h.path.p = "";
h.params = (struct UrlParams){.n = 1,
.p = (struct UrlParam[]){{
.key = (struct UrlView){.n = 1, .p = ""},
.val = (struct UrlView){.n = 1, .p = ""},
}}};
h.fragment.n = 1;
h.fragment.p = "";
ASSERT_STREQ("/://%00:%00@:%00/%00?%00=%00#%00", gc(EncodeUrl(&h, 0)));
}
TEST(ParseUrl, testEmptyScheme_isNotPossible) {
struct Url h;
gc(ParseUrl(":", -1, &h));
gc(h.params.p);
ASSERT_EQ(0, h.scheme.n);
ASSERT_EQ(0, h.scheme.p);
ASSERT_EQ(1, h.path.n);
ASSERT_EQ(':', h.path.p[0]);
ASSERT_STREQ(":", gc(EncodeUrl(&h, 0)));
gc(ParseUrl("://hi", -1, &h));
gc(h.params.p);
ASSERT_EQ(0, h.scheme.n);
ASSERT_EQ(0, h.scheme.p);
ASSERT_EQ(5, h.path.n);
ASSERT_BINEQ(u"://hi", h.path.p);
ASSERT_STREQ("://hi", gc(EncodeUrl(&h, 0)));
}
TEST(ParseUrl, testZipUri2) {
struct Url h;
gc(ParseUrl("zip:etc/passwd", -1, &h));
gc(h.params.p);
ASSERT_EQ(3, h.scheme.n);
ASSERT_BINEQ(u"zip", h.scheme.p);
ASSERT_EQ(10, h.path.n);
ASSERT_BINEQ(u"etc/passwd", h.path.p);
ASSERT_STREQ("zip:etc/passwd", gc(EncodeUrl(&h, 0)));
}
TEST(ParseUrl, testZipUri3) {
struct Url h;
gc(ParseUrl("zip:/etc/passwd", -1, &h));
gc(h.params.p);
ASSERT_EQ(0, h.host.n);
ASSERT_EQ(0, h.host.p);
ASSERT_EQ(3, h.scheme.n);
ASSERT_BINEQ(u"zip", h.scheme.p);
ASSERT_EQ(11, h.path.n);
ASSERT_BINEQ(u"/etc/passwd", h.path.p);
ASSERT_STREQ("zip:/etc/passwd", gc(EncodeUrl(&h, 0)));
}
TEST(ParseUrl, testDataUri) {
struct Url h;
gc(ParseUrl("data:image/png;base64,09AZaz+/==", -1, &h));
gc(h.params.p);
ASSERT_EQ(0, h.host.n);
ASSERT_EQ(0, h.host.p);
ASSERT_EQ(4, h.scheme.n);
ASSERT_BINEQ(u"data", h.scheme.p);
ASSERT_EQ(27, h.path.n);
ASSERT_BINEQ(u"image/png;base64,09AZaz+/==", h.path.p);
ASSERT_STREQ("data:image/png;base64,09AZaz+/==", gc(EncodeUrl(&h, 0)));
}
TEST(ParseUrl, testBadSchemeCharacter_parserAssumesItsPath) {
struct Url h;
gc(ParseUrl("fil\e://hi", -1, &h));
gc(h.params.p);
ASSERT_EQ(0, h.scheme.n);
ASSERT_EQ(0, h.scheme.p);
ASSERT_EQ(9, h.path.n);
ASSERT_BINEQ(u"fil←://hi", h.path.p);
ASSERT_STREQ("fil%1B://hi", gc(EncodeUrl(&h, 0)));
}
TEST(ParseParams, testEmpty) {
@ -297,7 +637,9 @@ TEST(ParseParams, test) {
ASSERT_EQ(1, h.p[0].key.n);
ASSERT_EQ(1, h.p[0].val.n);
ASSERT_EQ(1, h.p[1].key.n);
ASSERT_EQ(SIZE_MAX, h.p[1].val.n);
ASSERT_NE(0, h.p[1].key.p);
ASSERT_EQ(0, h.p[1].val.n);
ASSERT_EQ(0, h.p[1].val.p);
ASSERT_EQ(4, h.p[2].key.n);
ASSERT_EQ(0, h.p[2].val.n);
EXPECT_EQ('a', h.p[0].key.p[0]);
@ -344,14 +686,28 @@ void A(void) {
free(h.p);
}
BENCH(url, bench) {
BENCH(ParseUrl, bench) {
struct Url h;
EZBENCH2("ParseParams", donothing, A());
EZBENCH2("URI a", donothing, free(ParseRequestUri("a", -1, &h)));
EZBENCH2("URI a://b@c/d#f", donothing,
free(ParseRequestUri("a://b@c/d#f", -1, &h)));
EZBENCH2("URI a://b@c/d?z#f", donothing, ({
free(ParseRequestUri("a://b@c/?zd#f", -1, &h));
EZBENCH2("ParseParams hyperion", donothing, A());
EZBENCH2("ParseUrl a", donothing, free(ParseUrl("a", -1, &h)));
EZBENCH2("ParseUrl a://b@c/d#f", donothing,
free(ParseUrl("a://b@c/d#f", -1, &h)));
EZBENCH2("ParseUrl a://b@c/d?z#f", donothing, ({
free(ParseUrl("a://b@c/?zd#f", -1, &h));
free(h.params.p);
}));
}
BENCH(EncodeUrl, bench) {
struct Url h;
gc(ParseUrl("a", -1, &h));
EZBENCH2("EncodeUrl a", donothing, free(EncodeUrl(&h, 0)));
gc(ParseUrl("a://b@c/d#f", -1, &h));
EZBENCH2("EncodeUrl a://b@c/d#f", donothing, free(EncodeUrl(&h, 0)));
gc(ParseUrl("a://b@c/?zd#f", -1, &h));
gc(h.params.p);
EZBENCH2("EncodeUrl a://b@c/d?z#f", donothing, free(EncodeUrl(&h, 0)));
gc(ParseUrl(kHyperion, kHyperionSize, &h));
gc(h.params.p);
EZBENCH2("EncodeUrl hyperion", donothing, free(EncodeUrl(&h, 0)));
}

View file

@ -1,59 +0,0 @@
/*-*- 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"
#include "libc/testlib/ezbench.h"
#include "libc/testlib/testlib.h"
#include "net/http/uri.h"
_Alignas(32) const char kWinsockIcoPngBase64[] = "\
base64,iVBORw0KGgoAAAANSUhEUgAAAJcAAACXCAYAAAAYn8l5AAAABmJLR0QA/\
wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4woLByMP6uwgW\
QAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAMeSURBVHja7\
d1BcuIwEAVQPMW94GbAzeBkZJepUXpw01ixDO+tE0PML+lHtsV0v9/vO+jgj1OAc\
CFcIFwIF8IFP+wrvzRNkzP3y7a4YmTkQrgQLnitc/XqA3GX+9SrU/+ei8vl8uMnT\
qeTkQvTIggXwoVC36mOJhZa3Upm5ALhQrjQuV6jT2HkQrgQLhAuNlLo+96Z6q5XI\
xcIF8KFzrXbWTDt0jTf4AkrIxfChXCBcCFcCBcIF8LFO9iP/gajx9jXMvrj80Yuh\
AuEC52r2q9G6jnRxWQX7Y1cCBfCBcLFxxb6tsBH5f12uz08xvV6Lb328Xh8+nfO5\
/NsyVfwjVwIF8IFa3auzALpXL96pRst0dWinta+loVWIxfChXCBcCFcCBcIF8LFe\
xn+6Z+5xc5oYTOzQJr5mcrFbYxcCBfCBcKFQv9AexdC9U7UueMueWwjFwgXwoVwO\
QUIF8IFwkV3e6dgfdETQ5knmIxcmBZBuBAuUOgH1Rb6LRZ8IxfChXDBt+le2N9nq\
a0a222VRn/aJrp5sO1CS22XlPkC9fa1R/tuIiMXwoVwgXDx5oV+ruCPJlrI7LXfa\
XsuMouo1YXWXv8IGLkwLSJcMGbnyrzWmqK/s31/Ue+pdJr2uNECbrvoXP0cen2eR\
i5MiwgXCBf9DX8n6ta+lCmzkFkp+FGhb89N9Yu52uMs9eVYRi5MiwgXbKdzba0TV\
h7NjzpY5i7Tpb78tD1OZrE408GMXJgWES4QLhT6zRf8qAxXFlqXKu+Vgp/5xyX6u\
41cmBYRLvg7dS5xJyqPzW2HFH0Ev9mxKjJ3wRq5MC0iXCBc9FdaRM38DzD6o/kjF\
frRy7uRC+FCuOBlpUVUnjzJhQvXo+8PaxEV0yLCBU9xs+Cg2ies1+5g0RPfRi5Mi\
wgXCBcK/UeYe3Ims6ia2RN1zfJu5MK0iHDBQy5cj/AhFLZd6inarskWSpgWES4QL\
sZkEXUAS227VJU5ti2UMC0iXKBzfUIPW3vbqrm96qP3Z+TCtIhwgXCh0POfAt1T5\
i6Nw+Ew+/6MXJgWES7Quejf74xcdPMFQQsgQ0YEZnUAAAAASUVORK5CYII=";
size_t size;
void SetUp(void) {
size = strlen(kWinsockIcoPngBase64);
}
BENCH(strlen, bench) {
EZBENCH(donothing, (size = strlen(kWinsockIcoPngBase64)));
}
TEST(uricspn, test) {
EXPECT_EQ(size, uricspn(kWinsockIcoPngBase64, size));
}
BENCH(uricspn, bench) {
EZBENCH(donothing, uricspn(kWinsockIcoPngBase64, size));
}

View file

@ -1,141 +0,0 @@
/*-*- 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/bits/bits.h"
#include "libc/bits/initializer.internal.h"
#include "libc/errno.h"
#include "libc/log/log.h"
#include "libc/macros.internal.h"
#include "libc/mem/mem.h"
#include "libc/runtime/gc.internal.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/testlib/ezbench.h"
#include "libc/testlib/testlib.h"
#include "libc/x/x.h"
#include "net/http/uri.h"
#define URIPARSE(URI) uriparse(&uri, (p = URI), (size = sizeof(URI) - 1))
static const char kHttpCosmopolitanVideoUrl[] =
"http://cosmopolitan.storage.googleapis.com/pub/vid/blankspace.mpg";
static const char kSipPriceIsTortureUri[] =
"sip:bob%20barker:priceisright@[dead:beef::666]:5060;isup-oli=00";
static const char kWinsockIcoPngBase64[] = "\
base64,iVBORw0KGgoAAAANSUhEUgAAAJcAAACXCAYAAAAYn8l5AAAABmJLR0QA/\
wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4woLByMP6uwgW\
QAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAMeSURBVHja7\
d1BcuIwEAVQPMW94GbAzeBkZJepUXpw01ixDO+tE0PML+lHtsV0v9/vO+jgj1OAc\
CFcIFwIF8IFP+wrvzRNkzP3y7a4YmTkQrgQLnitc/XqA3GX+9SrU/+ei8vl8uMnT\
qeTkQvTIggXwoVC36mOJhZa3Upm5ALhQrjQuV6jT2HkQrgQLhAuNlLo+96Z6q5XI\
xcIF8KFzrXbWTDt0jTf4AkrIxfChXCBcCFcCBcIF8LFO9iP/gajx9jXMvrj80Yuh\
AuEC52r2q9G6jnRxWQX7Y1cCBfCBcLFxxb6tsBH5f12uz08xvV6Lb328Xh8+nfO5\
/NsyVfwjVwIF8IFa3auzALpXL96pRst0dWinta+loVWIxfChXCBcCFcCBcIF8LFe\
xn+6Z+5xc5oYTOzQJr5mcrFbYxcCBfCBcKFQv9AexdC9U7UueMueWwjFwgXwoVwO\
QUIF8IFwkV3e6dgfdETQ5knmIxcmBZBuBAuUOgH1Rb6LRZ8IxfChXDBt+le2N9nq\
a0a222VRn/aJrp5sO1CS22XlPkC9fa1R/tuIiMXwoVwgXDx5oV+ruCPJlrI7LXfa\
XsuMouo1YXWXv8IGLkwLSJcMGbnyrzWmqK/s31/Ue+pdJr2uNECbrvoXP0cen2eR\
i5MiwgXCBf9DX8n6ta+lCmzkFkp+FGhb89N9Yu52uMs9eVYRi5MiwgXbKdzba0TV\
h7NjzpY5i7Tpb78tD1OZrE408GMXJgWES4QLhT6zRf8qAxXFlqXKu+Vgp/5xyX6u\
41cmBYRLvg7dS5xJyqPzW2HFH0Ev9mxKjJ3wRq5MC0iXCBc9FdaRM38DzD6o/kjF\
frRy7uRC+FCuOBlpUVUnjzJhQvXo+8PaxEV0yLCBU9xs+Cg2ies1+5g0RPfRi5Mi\
wgXCBcK/UeYe3Ims6ia2RN1zfJu5MK0iHDBQy5cj/AhFLZd6inarskWSpgWES4QL\
sZkEXUAS227VJU5ti2UMC0iXKBzfUIPW3vbqrm96qP3Z+TCtIhwgXCh0POfAt1T5\
i6Nw+Ew+/6MXJgWES7Quejf74xcdPMFQQsgQ0YEZnUAAAAASUVORK5CYII=";
static size_t size;
static const char *p;
static struct Uri uri;
static struct UriMem {
struct UriSlice segs[8];
struct UriRef paramsegs[8];
struct UriKeyval params[4], queries[4];
} urimem_;
static textstartup void init() {
uri.segs.n = ARRAYLEN(urimem_.segs);
uri.segs.p = urimem_.segs;
uri.params.n = ARRAYLEN(urimem_.params);
uri.params.p = urimem_.params;
uri.queries.n = ARRAYLEN(urimem_.queries);
uri.queries.p = urimem_.queries;
uri.paramsegs.n = ARRAYLEN(urimem_.paramsegs);
uri.paramsegs.p = urimem_.paramsegs;
}
const void *const g_name_ctor[] initarray = {init};
TEST(uriparse, sipPstnUri) {
EXPECT_NE(-1, URIPARSE("sip:+12125650666"));
EXPECT_STREQ("sip", gc(strndup(p + uri.scheme.i, uri.scheme.n)));
EXPECT_STREQ("+12125650666", gc(strndup(p + uri.host.i, uri.host.n)));
EXPECT_STREQ("", gc(strndup(p + uri.opaque.i, uri.opaque.n)));
}
TEST(uriparse, printVideoUrl) {
EXPECT_NE(-1, URIPARSE(kHttpCosmopolitanVideoUrl));
EXPECT_STREQ("http", gc(strndup(p + uri.scheme.i, uri.scheme.n)));
EXPECT_STREQ("cosmopolitan.storage.googleapis.com",
gc(strndup(p + uri.host.i, uri.host.n)));
EXPECT_STREQ("", gc(strndup(p + uri.port.i, uri.port.n)));
EXPECT_STREQ("/pub/vid/blankspace.mpg",
gc(strndup(p + uri.segs.p[0].i,
(uri.segs.p[uri.segs.i - 1].n +
(uri.segs.p[uri.segs.i - 1].i - uri.segs.p[0].i)))));
}
TEST(uriparse, localRelativeFile) {
EXPECT_NE(-1, URIPARSE("blankspace.mpg"));
EXPECT_STREQ("", gc(strndup(p + uri.scheme.i, uri.scheme.n)));
EXPECT_STREQ("", gc(strndup(p + uri.host.i, uri.host.n)));
EXPECT_STREQ("", gc(strndup(p + uri.port.i, uri.port.n)));
EXPECT_STREQ("blankspace.mpg",
gc(strndup(p + uri.segs.p[0].i,
(uri.segs.p[uri.segs.i - 1].n +
(uri.segs.p[uri.segs.i - 1].i - uri.segs.p[0].i)))));
}
TEST(uriparse, badPort_einval) {
EXPECT_EQ(-1, URIPARSE("http://hello.example:http/"));
EXPECT_EQ(EINVAL, errno);
}
TEST(uriparse, datauri) {
size = strlen((p = gc(xstrcat("data:image/png;", kWinsockIcoPngBase64))));
EXPECT_NE(-1, uriparse(&uri, p, size));
EXPECT_EQ(5, uri.opaque.i);
EXPECT_EQ(size - 5, uri.opaque.n);
}
////////////////////////////////////////////////////////////////////////////////
BENCH(uriparse, bench) {
EZBENCH(donothing, URIPARSE("sip:+12125650666"));
EZBENCH(donothing, URIPARSE("http://hello.example"));
EZBENCH(donothing, URIPARSE(kHttpCosmopolitanVideoUrl));
EZBENCH(donothing, URIPARSE(kSipPriceIsTortureUri));
}
BENCH(uriparse, bigWinsockIcoPngUri) {
const char *BigDataIconUri;
BigDataIconUri = gc(xstrcat("data:image/png;", kWinsockIcoPngBase64));
size = strlen(kWinsockIcoPngBase64);
EZBENCH(donothing, uriparse(&uri, BigDataIconUri, size));
}

View file

@ -1,29 +0,0 @@
/*-*- 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/testlib/ezbench.h"
#include "libc/testlib/testlib.h"
#include "net/http/uri.h"
TEST(urischeme, test) {
EXPECT_EQ(kUriSchemeSip, urischeme((struct UriSlice){0, 3}, "sips"));
}
BENCH(urischeme, bench) {
EZBENCH(donothing, urischeme((struct UriSlice){0, 3}, "sips"));
}