diff --git a/net/http/gethttpmethod.gperf b/net/http/gethttpmethod.gperf deleted file mode 100644 index bc7e90a3f..000000000 --- a/net/http/gethttpmethod.gperf +++ /dev/null @@ -1,30 +0,0 @@ -%{ -#include "libc/str/str.h" -#include "net/http/http.h" -#define GPERF_DOWNCASE -%} -%compare-strncmp -%ignore-case -%language=ANSI-C -%readonly-tables -%struct-type -%define lookup-function-name LookupHttpMethod -struct HttpMethodSlot { char name[8]; char code; }; -%% -DELETE, kHttpDelete -GET, kHttpGet -HEAD, kHttpHead -POST, kHttpPost -PUT, kHttpPut -OPTIONS, kHttpOptions -CONNECT, kHttpConnect -TRACE, kHttpTrace -COPY, kHttpCopy -LOCK, kHttpLock -MERGE, kHttpMerge -MKCOL, kHttpMkcol -MOVE, kHttpMove -NOTIFY, kHttpNotify -PATCH, kHttpPatch -REPORT, kHttpReport -UNLOCK, kHttpUnlock diff --git a/net/http/gethttpmethod.inc b/net/http/gethttpmethod.inc deleted file mode 100644 index 2c475fef2..000000000 --- a/net/http/gethttpmethod.inc +++ /dev/null @@ -1,196 +0,0 @@ -/* ANSI-C code produced by gperf version 3.1 */ -/* Command-line: gperf gethttpmethod.gperf */ -/* Computed positions: -k'1-2' */ -/* clang-format off */ - -#if !((' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \ - && ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \ - && (')' == 41) && ('*' == 42) && ('+' == 43) && (',' == 44) \ - && ('-' == 45) && ('.' == 46) && ('/' == 47) && ('0' == 48) \ - && ('1' == 49) && ('2' == 50) && ('3' == 51) && ('4' == 52) \ - && ('5' == 53) && ('6' == 54) && ('7' == 55) && ('8' == 56) \ - && ('9' == 57) && (':' == 58) && (';' == 59) && ('<' == 60) \ - && ('=' == 61) && ('>' == 62) && ('?' == 63) && ('A' == 65) \ - && ('B' == 66) && ('C' == 67) && ('D' == 68) && ('E' == 69) \ - && ('F' == 70) && ('G' == 71) && ('H' == 72) && ('I' == 73) \ - && ('J' == 74) && ('K' == 75) && ('L' == 76) && ('M' == 77) \ - && ('N' == 78) && ('O' == 79) && ('P' == 80) && ('Q' == 81) \ - && ('R' == 82) && ('S' == 83) && ('T' == 84) && ('U' == 85) \ - && ('V' == 86) && ('W' == 87) && ('X' == 88) && ('Y' == 89) \ - && ('Z' == 90) && ('[' == 91) && ('\\' == 92) && (']' == 93) \ - && ('^' == 94) && ('_' == 95) && ('a' == 97) && ('b' == 98) \ - && ('c' == 99) && ('d' == 100) && ('e' == 101) && ('f' == 102) \ - && ('g' == 103) && ('h' == 104) && ('i' == 105) && ('j' == 106) \ - && ('k' == 107) && ('l' == 108) && ('m' == 109) && ('n' == 110) \ - && ('o' == 111) && ('p' == 112) && ('q' == 113) && ('r' == 114) \ - && ('s' == 115) && ('t' == 116) && ('u' == 117) && ('v' == 118) \ - && ('w' == 119) && ('x' == 120) && ('y' == 121) && ('z' == 122) \ - && ('{' == 123) && ('|' == 124) && ('}' == 125) && ('~' == 126)) -/* The character set is not based on ISO-646. */ -#error "gperf generated tables don't work with this execution character set. Please report a bug to ." -#endif - -#line 1 "gethttpmethod.gperf" - -#include "libc/str/str.h" -#include "libc/str/tab.internal.h" -#include "net/http/http.h" -#define GPERF_DOWNCASE -#line 12 "gethttpmethod.gperf" -struct HttpMethodSlot { char name[8]; char code; }; - -#define TOTAL_KEYWORDS 17 -#define MIN_WORD_LENGTH 3 -#define MAX_WORD_LENGTH 7 -#define MIN_HASH_VALUE 3 -#define MAX_HASH_VALUE 25 -/* maximum key range = 23, duplicates = 0 */ - -#ifndef GPERF_DOWNCASE -#define GPERF_DOWNCASE 1 -static unsigned char gperf_downcase[256] = - { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, - 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, - 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, - 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, - 60, 61, 62, 63, 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, - 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, - 122, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, - 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, - 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, - 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, - 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, - 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, - 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, - 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, - 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, - 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, - 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, - 255 - }; -#endif - -#ifndef GPERF_CASE_STRNCMP -#define GPERF_CASE_STRNCMP 1 -static inline int -gperf_case_strncmp (register const char *s1, register const char *s2, register size_t n) -{ - for (; n > 0;) - { - unsigned char c1 = gperf_downcase[(unsigned char)*s1++]; - unsigned char c2 = gperf_downcase[(unsigned char)*s2++]; - if (c1 != 0 && c1 == c2) - { - n--; - continue; - } - return (int)c1 - (int)c2; - } - return 0; -} -#endif - -#ifdef __GNUC__ -__inline -#else -#ifdef __cplusplus -inline -#endif -#endif -static unsigned int -hash (register const char *str, register size_t len) -{ - static const unsigned char asso_values[] = - { - 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, - 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, - 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, - 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, - 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, - 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, - 26, 26, 26, 26, 26, 0, 26, 5, 15, 0, - 26, 5, 0, 26, 26, 10, 15, 10, 0, 5, - 0, 26, 10, 26, 5, 0, 26, 26, 26, 26, - 26, 26, 26, 26, 26, 26, 26, 0, 26, 5, - 15, 0, 26, 5, 0, 26, 26, 10, 15, 10, - 0, 5, 0, 26, 10, 26, 5, 0, 26, 26, - 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, - 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, - 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, - 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, - 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, - 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, - 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, - 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, - 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, - 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, - 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, - 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, - 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, - 26, 26, 26, 26, 26, 26 - }; - return len + asso_values[(unsigned char)str[1]] + asso_values[(unsigned char)str[0]]; -} - -static inline const struct HttpMethodSlot * -LookupHttpMethod (register const char *str, register size_t len) -{ - static const struct HttpMethodSlot wordlist[] = - { - {""}, {""}, {""}, -#line 18 "gethttpmethod.gperf" - {"PUT", kHttpPut}, -#line 16 "gethttpmethod.gperf" - {"HEAD", kHttpHead}, -#line 28 "gethttpmethod.gperf" - {"PATCH", kHttpPatch}, -#line 30 "gethttpmethod.gperf" - {"UNLOCK", kHttpUnlock}, - {""}, -#line 15 "gethttpmethod.gperf" - {"GET", kHttpGet}, -#line 17 "gethttpmethod.gperf" - {"POST", kHttpPost}, - {""}, -#line 27 "gethttpmethod.gperf" - {"NOTIFY", kHttpNotify}, -#line 19 "gethttpmethod.gperf" - {"OPTIONS", kHttpOptions}, - {""}, -#line 22 "gethttpmethod.gperf" - {"COPY", kHttpCopy}, -#line 24 "gethttpmethod.gperf" - {"MERGE", kHttpMerge}, -#line 29 "gethttpmethod.gperf" - {"REPORT", kHttpReport}, -#line 20 "gethttpmethod.gperf" - {"CONNECT", kHttpConnect}, - {""}, -#line 26 "gethttpmethod.gperf" - {"MOVE", kHttpMove}, -#line 21 "gethttpmethod.gperf" - {"TRACE", kHttpTrace}, -#line 14 "gethttpmethod.gperf" - {"DELETE", kHttpDelete}, - {""}, {""}, -#line 23 "gethttpmethod.gperf" - {"LOCK", kHttpLock}, -#line 25 "gethttpmethod.gperf" - {"MKCOL", kHttpMkcol} - }; - - if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH) - { - register unsigned int key = hash (str, len); - - if (key <= MAX_HASH_VALUE) - { - register const char *s = wordlist[key].name; - - if ((((unsigned char)*str ^ (unsigned char)*s) & ~32) == 0 && !gperf_case_strncmp (str, s, len) && s[len] == '\0') - return &wordlist[key]; - } - } - return 0; -} diff --git a/net/http/http.h b/net/http/http.h index 0e09b4a15..a673a4c3a 100644 --- a/net/http/http.h +++ b/net/http/http.h @@ -1,27 +1,19 @@ #ifndef COSMOPOLITAN_LIBC_HTTP_HTTP_H_ #define COSMOPOLITAN_LIBC_HTTP_HTTP_H_ +#include "libc/serialize.h" #include "libc/time/struct/tm.h" #define kHttpRequest 0 #define kHttpResponse 1 -#define kHttpGet 1 -#define kHttpHead 2 -#define kHttpPost 3 -#define kHttpPut 4 -#define kHttpDelete 5 -#define kHttpOptions 6 -#define kHttpConnect 7 -#define kHttpTrace 8 -#define kHttpCopy 9 -#define kHttpLock 10 -#define kHttpMerge 11 -#define kHttpMkcol 12 -#define kHttpMove 13 -#define kHttpNotify 14 -#define kHttpPatch 15 -#define kHttpReport 16 -#define kHttpUnlock 17 +#define kHttpGet READ32LE("GET") +#define kHttpHead READ32LE("HEAD") +#define kHttpPost READ32LE("POST") +#define kHttpPut READ32LE("PUT") +#define kHttpDelete READ64LE("DELETE\0") +#define kHttpOptions READ64LE("OPTIONS") +#define kHttpConnect READ64LE("CONNECT") +#define kHttpTrace READ64LE("TRACE\0\0") #define kHttpStateStart 0 #define kHttpStateMethod 1 @@ -168,14 +160,13 @@ struct HttpMessage { int i, a, status; unsigned char t; unsigned char type; - unsigned char method; unsigned char version; + uint64_t method; struct HttpSlice k; struct HttpSlice uri; struct HttpSlice scratch; struct HttpSlice message; struct HttpSlice headers[kHttpHeadersMax]; - struct HttpSlice xmethod; struct HttpHeaders xheaders; }; @@ -187,13 +178,11 @@ struct HttpUnchunker { }; extern const char kHttpToken[256]; -extern const char kHttpMethod[18][8]; extern const bool kHttpRepeatable[kHttpHeadersMax]; const char *GetHttpReason(int); const char *GetHttpHeaderName(int); int GetHttpHeader(const char *, size_t); -int GetHttpMethod(const char *, size_t); void InitHttpMessage(struct HttpMessage *, int); void DestroyHttpMessage(struct HttpMessage *); int ParseHttpMessage(struct HttpMessage *, const char *, size_t); @@ -202,6 +191,7 @@ int64_t ParseContentLength(const char *, size_t); char *FormatHttpDateTime(char[hasatleast 30], struct tm *); bool ParseHttpRange(const char *, size_t, long, long *, long *); int64_t ParseHttpDateTime(const char *, size_t); +uint64_t ParseHttpMethod(const char *, size_t); bool IsValidHttpToken(const char *, size_t); bool IsValidCookieValue(const char *, size_t); bool IsAcceptablePath(const char *, size_t); diff --git a/net/http/khttpmethod.c b/net/http/khttpmethod.c deleted file mode 100644 index 6fec68a16..000000000 --- a/net/http/khttpmethod.c +++ /dev/null @@ -1,40 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│ vi: set et 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 "net/http/http.h" - -const char kHttpMethod[18][8] = { - "WUT", // - "GET", // - "HEAD", // - "POST", // - "PUT", // - "DELETE", // - "OPTIONS", // - "CONNECT", // - "TRACE", // - "COPY", // - "LOCK", // - "MERGE", // - "MKCOL", // - "MOVE", // - "NOTIFY", // - "PATCH", // - "REPORT", // - "UNLOCK", // -}; diff --git a/net/http/parsehttpmessage.c b/net/http/parsehttpmessage.c index 56eb5d342..b95869d69 100644 --- a/net/http/parsehttpmessage.c +++ b/net/http/parsehttpmessage.c @@ -17,14 +17,15 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" -#include "libc/serialize.h" #include "libc/limits.h" #include "libc/macros.internal.h" #include "libc/mem/alg.h" #include "libc/mem/arraylist.internal.h" #include "libc/mem/mem.h" +#include "libc/serialize.h" #include "libc/stdio/stdio.h" #include "libc/str/str.h" +#include "libc/str/tab.internal.h" #include "libc/sysv/errfuns.h" #include "libc/x/x.h" #include "net/http/http.h" @@ -90,23 +91,29 @@ int ParseHttpMessage(struct HttpMessage *r, const char *p, size_t n) { c = p[r->i] & 0xff; switch (r->t) { case kHttpStateStart: - if (c == '\r' || c == '\n') break; /* RFC7230 § 3.5 */ + if (c == '\r' || c == '\n') break; // RFC7230 § 3.5 if (!kHttpToken[c]) return ebadmsg(); - r->t = r->type == kHttpRequest ? kHttpStateMethod : kHttpStateVersion; - r->a = r->i; + if (r->type == kHttpRequest) { + r->t = kHttpStateMethod; + r->method = kToUpper[c]; + r->a = 8; + } else { + r->t = kHttpStateVersion; + r->a = r->i; + } break; case kHttpStateMethod: for (;;) { if (c == ' ') { - r->method = GetHttpMethod(p + r->a, r->i - r->a); - r->xmethod.a = r->a; - r->xmethod.b = r->i; r->a = r->i + 1; r->t = kHttpStateUri; break; - } else if (!kHttpToken[c]) { + } else if (r->a == 64 || !kHttpToken[c]) { return ebadmsg(); } + c = kToUpper[c]; + r->method |= (uint64_t)c << r->a; + r->a += 8; if (++r->i == n) break; c = p[r->i] & 0xff; } @@ -195,10 +202,8 @@ int ParseHttpMessage(struct HttpMessage *r, const char *p, size_t n) { } else if (c == '\n') { return ++r->i; } else if (!kHttpToken[c]) { - /* - * 1. Forbid empty header name (RFC2616 §2.2) - * 2. Forbid line folding (RFC7230 §3.2.4) - */ + // 1. Forbid empty header name (RFC2616 §2.2) + // 2. Forbid line folding (RFC7230 §3.2.4) return ebadmsg(); } r->k.a = r->i; @@ -221,7 +226,7 @@ int ParseHttpMessage(struct HttpMessage *r, const char *p, size_t n) { if (c == ' ' || c == '\t') break; r->a = r->i; r->t = kHttpStateValue; - /* fallthrough */ + // fallthrough case kHttpStateValue: for (;;) { if (c == '\r' || c == '\n') { diff --git a/net/http/gethttpmethod.c b/net/http/parsehttpmethod.c similarity index 79% rename from net/http/gethttpmethod.c rename to net/http/parsehttpmethod.c index 4fd5d362b..6354eb397 100644 --- a/net/http/gethttpmethod.c +++ b/net/http/parsehttpmethod.c @@ -16,21 +16,28 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "net/http/gethttpmethod.inc" +#include "libc/str/str.h" +#include "libc/str/tab.internal.h" #include "net/http/http.h" /** - * Converts HTTP method string into internal index + * Converts HTTP method to word encoding. + * + * For example, `ParseHttpMethod("GET", -1)` will return `kHttpGet`. * * @param len if -1 implies strlen - * @return small number for HTTP method, or 0 if not found. + * @return word encoded method, or 0 if invalid */ -int GetHttpMethod(const char *str, size_t len) { - const struct HttpMethodSlot *slot; +uint64_t ParseHttpMethod(const char *str, size_t len) { + int s = 0; + uint64_t w = 0; if (len == -1) len = str ? strlen(str) : 0; - if ((slot = LookupHttpMethod(str, len))) { - return slot->code; - } else { - return 0; + for (size_t i = 0; i < len; ++i) { + int c = kToUpper[str[i] & 255]; + if (!kHttpToken[c]) return 0; + if (s == 64) return 0; + w |= (uint64_t)c << s; + s += 8; } + return w; } diff --git a/net/turfwar/turfwar.c b/net/turfwar/turfwar.c index f50da64b4..41e7d3b30 100644 --- a/net/turfwar/turfwar.c +++ b/net/turfwar/turfwar.c @@ -936,8 +936,9 @@ void *HttpWorker(void *arg) { } // access log - LOG("%6P %16s %.*s %.*s %.*s %.*s %#.*s\n", ipbuf, - msg->xmethod.b - msg->xmethod.a, inbuf + msg->xmethod.a, + char method[9] = {0}; + WRITE64LE(method, msg->method); + LOG("%6P %16s %s %.*s %.*s %.*s %#.*s\n", ipbuf, method, msg->uri.b - msg->uri.a, inbuf + msg->uri.a, HeaderLength(kHttpCfIpcountry), HeaderData(kHttpCfIpcountry), HeaderLength(kHttpSecChUaPlatform), HeaderData(kHttpSecChUaPlatform), diff --git a/test/net/http/parsehttpmessage_test.c b/test/net/http/parsehttpmessage_test.c index f5b9e3449..8a308a150 100644 --- a/test/net/http/parsehttpmessage_test.c +++ b/test/net/http/parsehttpmessage_test.c @@ -20,6 +20,7 @@ #include "libc/log/check.h" #include "libc/mem/gc.h" #include "libc/mem/mem.h" +#include "libc/serialize.h" #include "libc/str/str.h" #include "libc/testlib/ezbench.h" #include "libc/testlib/testlib.h" @@ -40,6 +41,20 @@ void TearDown(void) { DestroyHttpMessage(req); } +char *method(void) { + static char s[9]; + WRITE64LE(s, req->method); + return s; +} + +TEST(ParseHttpMethod, test) { + ASSERT_EQ(0, ParseHttpMethod(" ", -1)); + ASSERT_EQ(0, ParseHttpMethod("aaaaaaaaa", -1)); + ASSERT_EQ(kHttpGet, ParseHttpMethod("get", -1)); + ASSERT_EQ(kHttpGet, ParseHttpMethod("GET", -1)); + ASSERT_EQ(kHttpDelete, ParseHttpMethod("DELETE", -1)); +} + TEST(ParseHttpMessage, soLittleState) { InitHttpMessage(req, kHttpRequest); ASSERT_LE(sizeof(struct HttpMessage), 512); @@ -59,7 +74,7 @@ TEST(ParseHttpMessage, testNoHeaders) { static const char m[] = "GET /foo HTTP/1.0\r\n\r\n"; InitHttpMessage(req, kHttpRequest); EXPECT_EQ(strlen(m), ParseHttpMessage(req, m, strlen(m))); - EXPECT_EQ(kHttpGet, req->method); + EXPECT_STREQ("GET", method()); EXPECT_STREQ("/foo", gc(slice(m, req->uri))); EXPECT_EQ(10, req->version); } @@ -72,7 +87,7 @@ Content-Length: 0\r\n\ \r\n"; InitHttpMessage(req, kHttpRequest); EXPECT_EQ(strlen(m), ParseHttpMessage(req, m, strlen(m))); - EXPECT_EQ(kHttpPost, req->method); + EXPECT_STREQ("POST", method()); EXPECT_STREQ("/foo?bar%20hi", gc(slice(m, req->uri))); EXPECT_EQ(10, req->version); EXPECT_STREQ("foo.example", gc(slice(m, req->headers[kHttpHost]))); @@ -84,7 +99,7 @@ TEST(ParseHttpMessage, testHttp101) { static const char m[] = "GET / HTTP/1.1\r\n\r\n"; InitHttpMessage(req, kHttpRequest); EXPECT_EQ(strlen(m), ParseHttpMessage(req, m, strlen(m))); - EXPECT_EQ(kHttpGet, req->method); + EXPECT_STREQ("GET", method()); EXPECT_STREQ("/", gc(slice(m, req->uri))); EXPECT_EQ(11, req->version); } @@ -93,7 +108,7 @@ TEST(ParseHttpMessage, testHttp100) { static const char m[] = "GET / HTTP/1.0\r\n\r\n"; InitHttpMessage(req, kHttpRequest); EXPECT_EQ(strlen(m), ParseHttpMessage(req, m, strlen(m))); - EXPECT_EQ(kHttpGet, req->method); + EXPECT_STREQ("GET", method()); EXPECT_STREQ("/", gc(slice(m, req->uri))); EXPECT_EQ(10, req->version); } @@ -102,45 +117,40 @@ TEST(ParseHttpMessage, testUnknownMethod_canBeUsedIfYouWant) { static const char m[] = "#%*+_^ / HTTP/1.0\r\n\r\n"; InitHttpMessage(req, kHttpRequest); EXPECT_EQ(strlen(m), ParseHttpMessage(req, m, strlen(m))); - EXPECT_FALSE(req->method); - EXPECT_STREQ("WUT", kHttpMethod[req->method]); - EXPECT_STREQ("#%*+_^", gc(slice(m, req->xmethod))); + EXPECT_STREQ("#%*+_^", method()); } TEST(ParseHttpMessage, testIllegalMethod) { static const char m[] = "ehd@oruc / HTTP/1.0\r\n\r\n"; InitHttpMessage(req, kHttpRequest); EXPECT_EQ(-1, ParseHttpMessage(req, m, strlen(m))); - EXPECT_STREQ("WUT", kHttpMethod[req->method]); } -TEST(ParseHttpMessage, testIllegalMethodCasing_weAllowItAndPreserveIt) { +TEST(ParseHttpMessage, testIllegalMethodCasing_weUpperCaseIt) { static const char m[] = "get / HTTP/1.0\r\n\r\n"; InitHttpMessage(req, kHttpRequest); EXPECT_EQ(strlen(m), ParseHttpMessage(req, m, strlen(m))); - EXPECT_STREQ("GET", kHttpMethod[req->method]); - EXPECT_STREQ("get", gc(slice(m, req->xmethod))); + EXPECT_STREQ("GET", method()); } TEST(ParseHttpMessage, testEmptyMethod_isntAllowed) { static const char m[] = " / HTTP/1.0\r\n\r\n"; InitHttpMessage(req, kHttpRequest); EXPECT_EQ(-1, ParseHttpMessage(req, m, strlen(m))); - EXPECT_STREQ("WUT", kHttpMethod[req->method]); } TEST(ParseHttpMessage, testEmptyUri_isntAllowed) { static const char m[] = "GET HTTP/1.0\r\n\r\n"; InitHttpMessage(req, kHttpRequest); EXPECT_EQ(-1, ParseHttpMessage(req, m, strlen(m))); - EXPECT_STREQ("GET", kHttpMethod[req->method]); + EXPECT_STREQ("GET", method()); } TEST(ParseHttpMessage, testHttp09) { static const char m[] = "GET /\r\n\r\n"; InitHttpMessage(req, kHttpRequest); EXPECT_EQ(strlen(m), ParseHttpMessage(req, m, strlen(m))); - EXPECT_EQ(kHttpGet, req->method); + EXPECT_STREQ("GET", method()); EXPECT_STREQ("/", gc(slice(m, req->uri))); EXPECT_EQ(9, req->version); } @@ -195,7 +205,7 @@ Content-Length: 0\n\ \n"; InitHttpMessage(req, kHttpRequest); EXPECT_EQ(strlen(m) - 1, ParseHttpMessage(req, m, strlen(m))); - EXPECT_EQ(kHttpPost, req->method); + EXPECT_STREQ("POST", method()); EXPECT_STREQ("/foo?bar%20hi", gc(slice(m, req->uri))); EXPECT_EQ(10, req->version); EXPECT_STREQ("foo.example", gc(slice(m, req->headers[kHttpHost]))); @@ -217,7 +227,7 @@ Accept-Language: en-US,en;q=0.9\r\n\ \r\n"; InitHttpMessage(req, kHttpRequest); EXPECT_EQ(strlen(m), ParseHttpMessage(req, m, strlen(m))); - EXPECT_EQ(kHttpGet, req->method); + EXPECT_STREQ("GET", method()); EXPECT_STREQ("/tool/net/redbean.png", gc(slice(m, req->uri))); EXPECT_EQ(11, req->version); EXPECT_STREQ("10.10.10.124:8080", gc(slice(m, req->headers[kHttpHost]))); @@ -541,14 +551,14 @@ transfer-encoding: chunked\r\n\ } BENCH(ParseHttpMessage, bench) { - EZBENCH2("DoTiniestHttpRequest", donothing, DoTiniestHttpRequest()); + EZBENCH2("DoTiniestHttpReque", donothing, DoTiniestHttpRequest()); EZBENCH2("DoTinyHttpRequest", donothing, DoTinyHttpRequest()); - EZBENCH2("DoStandardChromeRequest", donothing, DoStandardChromeRequest()); - EZBENCH2("DoUnstandardChromeRequest", donothing, DoUnstandardChromeRequest()); - EZBENCH2("DoTiniestHttpResponse", donothing, DoTiniestHttpResponse()); + EZBENCH2("DoStandardChromeRe", donothing, DoStandardChromeRequest()); + EZBENCH2("DoUnstandardChrome", donothing, DoUnstandardChromeRequest()); + EZBENCH2("DoTiniestHttpRespo", donothing, DoTiniestHttpResponse()); EZBENCH2("DoTinyHttpResponse", donothing, DoTinyHttpResponse()); - EZBENCH2("DoStandardHttpResponse", donothing, DoStandardHttpResponse()); - EZBENCH2("DoUnstandardHttpResponse", donothing, DoUnstandardHttpResponse()); + EZBENCH2("DoStandardHttpResp", donothing, DoStandardHttpResponse()); + EZBENCH2("DoUnstandardHttpRe", donothing, DoUnstandardHttpResponse()); } BENCH(HeaderHas, bench) { @@ -563,7 +573,7 @@ ACCEPT-encoding: bzip2\r\n\ \r\n"; InitHttpMessage(req, kHttpRequest); EXPECT_EQ(strlen(m), ParseHttpMessage(req, m, strlen(m))); - EZBENCH2("HeaderHas text/plain", donothing, + EZBENCH2("HeaderHas txt/pln", donothing, HeaderHas(req, m, kHttpAccept, "text/plain", 7)); EZBENCH2("HeaderHas deflate", donothing, HeaderHas(req, m, kHttpAcceptEncoding, "deflate", 7)); diff --git a/tool/curl/curl.c b/tool/curl/curl.c index ed5c494f8..fd9935de8 100644 --- a/tool/curl/curl.c +++ b/tool/curl/curl.c @@ -162,7 +162,7 @@ int _curl(int argc, char *argv[]) { size_t n; char **p; } headers = {0}; - int method = 0; + uint64_t method = 0; int authmode = MBEDTLS_SSL_VERIFY_REQUIRED; int ciphersuite = MBEDTLS_SSL_PRESET_SUITEC; bool includeheaders = false; @@ -193,7 +193,7 @@ int _curl(int argc, char *argv[]) { postdata = optarg; break; case 'X': - if (!(method = GetHttpMethod(optarg, strlen(optarg)))) { + if (!(method = ParseHttpMethod(optarg, -1))) { tinyprint(2, prog, ": bad http method: ", optarg, "\n", NULL); exit(1); } @@ -280,11 +280,13 @@ int _curl(int argc, char *argv[]) { } char *request = 0; + char methodstr[9] = {0}; + WRITE64LE(methodstr, method); appendf(&request, "%s %s HTTP/1.1\r\n" "Connection: close\r\n" "User-Agent: %s\r\n", - kHttpMethod[method], gc(EncodeUrl(&url, 0)), agent); + methodstr, gc(EncodeUrl(&url, 0)), agent); bool senthost = false; bool sentcontenttype = false; diff --git a/tool/net/BUILD.mk b/tool/net/BUILD.mk index 116c7a091..a8d1d9c89 100644 --- a/tool/net/BUILD.mk +++ b/tool/net/BUILD.mk @@ -99,7 +99,8 @@ TOOL_NET_REDBEAN_LUA_MODULES = \ o/$(MODE)/tool/net/ljson.o \ o/$(MODE)/tool/net/lmaxmind.o \ o/$(MODE)/tool/net/lsqlite3.o \ - o/$(MODE)/tool/net/largon2.o + o/$(MODE)/tool/net/largon2.o \ + o/$(MODE)/tool/net/launch.o o/$(MODE)/tool/net/redbean.com.dbg: \ $(TOOL_NET_DEPS) \ diff --git a/tool/net/fetch.inc b/tool/net/fetch.inc index 5665d085f..37dfb02de 100644 --- a/tool/net/fetch.inc +++ b/tool/net/fetch.inc @@ -15,7 +15,7 @@ static int LuaFetch(lua_State *L) { bool usingssl; uint32_t ip; struct Url url; - int t, ret, sock = -1, methodidx, hdridx; + int t, ret, sock = -1, hdridx; const char *host, *port; char *request; struct TlsBio *bio; @@ -34,7 +34,9 @@ static int LuaFetch(lua_State *L) { size_t urlarglen, requestlen, paylen, bodylen; size_t i, g, hdrsize; int keepalive = kaNONE; - int imethod, numredirects = 0, maxredirects = 5; + char canmethod[9] = {0}; + uint64_t imethod; + int numredirects = 0, maxredirects = 5; bool followredirect = true; struct addrinfo hints = {.ai_family = AF_INET, .ai_socktype = SOCK_STREAM, @@ -54,9 +56,10 @@ static int LuaFetch(lua_State *L) { body = luaL_optlstring(L, -1, "", &bodylen); lua_getfield(L, 2, "method"); // use GET by default if no method is provided - method = luaL_optstring(L, -1, kHttpMethod[kHttpGet]); - if ((imethod = GetHttpMethod(method, -1))) { - method = kHttpMethod[imethod]; + method = luaL_optstring(L, -1, "GET"); + if ((imethod = ParseHttpMethod(method, -1))) { + WRITE64LE(canmethod, imethod); + method = canmethod; } else { return LuaNilError(L, "bad method"); } @@ -123,16 +126,16 @@ static int LuaFetch(lua_State *L) { } else if (lua_isnoneornil(L, 2)) { body = ""; bodylen = 0; - method = kHttpMethod[kHttpGet]; + method = "GET"; } else { body = luaL_checklstring(L, 2, &bodylen); - method = kHttpMethod[kHttpPost]; + method = "POST"; } // provide Content-Length header unless it's zero and not expected - methodidx = GetHttpMethod(method, -1); - if (bodylen > 0 || !(methodidx == kHttpGet || methodidx == kHttpHead || - methodidx == kHttpTrace || methodidx == kHttpDelete || - methodidx == kHttpConnect)) { + imethod = ParseHttpMethod(method, -1); + if (bodylen > 0 || + !(imethod == kHttpGet || imethod == kHttpHead || imethod == kHttpTrace || + imethod == kHttpDelete || imethod == kHttpConnect)) { conlenhdr = gc(xasprintf("Content-Length: %zu\r\n", bodylen)); } @@ -142,8 +145,8 @@ static int LuaFetch(lua_State *L) { gc(ParseUrl(urlarg, urlarglen, &url, true)); gc(url.params.p); DEBUGF("(ftch) client fetching %`'s (host=%`'.*s, port=%.*s, path=%`'.*s)", - urlarg, url.host.n, url.host.p, url.port.n, url.port.p, - url.path.n, url.path.p); + urlarg, url.host.n, url.host.p, url.port.n, url.port.p, url.path.n, + url.path.p); usingssl = false; if (url.scheme.n) { @@ -488,7 +491,7 @@ Finished: if (msg.status == 303) { body = ""; bodylen = 0; - method = kHttpMethod[kHttpGet]; + method = "GET"; } // create table if needed if (!lua_istable(L, 2)) { @@ -512,8 +515,8 @@ Finished: VERBOSEF("(ftch) client redirecting %`'.*s " "(scheme=%`'.*s, host=%`'.*s, port=%.*s, path=%`'.*s)", FetchHeaderLength(kHttpLocation), FetchHeaderData(kHttpLocation), - url.scheme.n, url.scheme.p, url.host.n, url.host.p, - url.port.n, url.port.p, url.path.n, url.path.p); + url.scheme.n, url.scheme.p, url.host.n, url.host.p, url.port.n, + url.port.p, url.path.n, url.path.p); // while it's possible to check for IsAcceptableHost/IsAcceptablePort // it's not clear what to do if they are not; // if they are invalid, redirect returns "invalid host" message @@ -530,7 +533,7 @@ Finished: if (FetchHeaderData(kHttpLocation)[0] == '/') { // if the path is absolute, then use it // so `/redir/more` -> `/less` becomes `/less` - url.path.n = 0; // replace the path + url.path.n = 0; // replace the path } else { // if the path is relative, then merge it, // so `/redir/more` -> `less` becomes `/redir/less` @@ -539,8 +542,8 @@ Finished: } } url.path.p = gc(xasprintf("%.*s%.*s", url.path.n, url.path.p, - FetchHeaderLength(kHttpLocation), - FetchHeaderData(kHttpLocation))); + FetchHeaderLength(kHttpLocation), + FetchHeaderData(kHttpLocation))); url.path.n = strlen(url.path.p); lua_pushstring(L, gc(EncodeUrl(&url, 0))); } diff --git a/tool/net/launch.c b/tool/net/launch.c new file mode 100644 index 000000000..2fa348709 --- /dev/null +++ b/tool/net/launch.c @@ -0,0 +1,120 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2024 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/calls/calls.h" +#include "libc/calls/struct/sigaction.h" +#include "libc/calls/struct/sigset.h" +#include "libc/dce.h" +#include "libc/errno.h" +#include "libc/log/log.h" +#include "libc/proc/posix_spawn.h" +#include "libc/runtime/runtime.h" +#include "libc/stdio/stdio.h" +#include "libc/str/str.h" +#include "libc/sysv/consts/sig.h" + +static volatile bool g_timed_out; + +static void finish(void) { + if (!IsWindows()) { + _exit(0); + } +} + +static void timeout(int sig) { + g_timed_out = true; +} + +static void failure(const char *url, const char *cmd, const char *reason) { + WARNF("(srvr) failed to open %s in a browser tab using %s: %s", url, cmd, + reason); +} + +/** + * Opens browser tab on host system. + */ +void launch_browser(const char *url) { + + // perform this task from a subprocess so it doesn't block server + if (!IsWindows()) { + switch (fork()) { + case 0: + break; + default: + return; + case -1: + perror("fork"); + return; + } + } + + // determine which command opens browser tab + const char *cmd; + if (IsWindows()) { + cmd = "/c/windows/explorer.exe"; + } else if (IsXnu()) { + cmd = "open"; + } else { + cmd = "xdg-open"; + } + + // spawn process + // set process group so ctrl-c won't kill browser + int pid, err; + posix_spawnattr_t sa; + char *args[] = {(char *)cmd, (char *)url, 0}; + posix_spawnattr_init(&sa); + posix_spawnattr_setflags(&sa, POSIX_SPAWN_SETPGROUP); + err = posix_spawnp(&pid, cmd, 0, &sa, args, environ); + posix_spawnattr_destroy(&sa); + if (err) { + failure(url, cmd, strerror(err)); + return finish(); + } + + // kill command if it takes more than three seconds + // we need it because xdg-open acts weird on headless systems + struct sigaction hand; + hand.sa_flags = 0; + sigemptyset(&hand.sa_mask); + hand.sa_handler = timeout; + sigaction(SIGALRM, &hand, 0); + alarm(3); + + // wait for tab to return finish opening + // the browser will still be running after this completes + int ws; + while (waitpid(pid, &ws, 0) == -1) { + if (errno != EINTR) { + failure(url, cmd, strerror(errno)); + kill(pid, SIGKILL); + return finish(); + } + if (g_timed_out) { + failure(url, cmd, "process timed out"); + kill(pid, SIGKILL); + return finish(); + } + } + if (ws) { + failure(url, cmd, "process exited with non-zero status"); + } + + // we're done + return finish(); +} diff --git a/tool/net/lfuncs.h b/tool/net/lfuncs.h index 17e1fbcba..c5fcf8796 100644 --- a/tool/net/lfuncs.h +++ b/tool/net/lfuncs.h @@ -95,5 +95,7 @@ int LuaVisualizeControlCodes(lua_State *); void LuaPushUrlView(lua_State *, struct UrlView *); char *FormatUnixHttpDateTime(char *, int64_t); +void launch_browser(const char *); + COSMOPOLITAN_C_END_ #endif /* COSMOPOLITAN_TOOL_NET_LFUNCS_H_ */ diff --git a/tool/net/redbean.c b/tool/net/redbean.c index 047ec72d0..a5146e290 100644 --- a/tool/net/redbean.c +++ b/tool/net/redbean.c @@ -2534,7 +2534,7 @@ img { vertical-align: middle; }\r\n\ } static char *ServeErrorImplDefault(unsigned code, const char *reason, - const char *details) { + const char *details) { size_t n; char *p, *s; struct Asset *a; @@ -2590,7 +2590,6 @@ static char *ServeErrorImpl(unsigned code, const char *reason, } else { return ServeErrorImplDefault(code, reason, details); } - } static char *ServeErrorWithPath(unsigned code, const char *reason, @@ -2610,9 +2609,10 @@ static char *ServeError(unsigned code, const char *reason) { } static char *ServeFailure(unsigned code, const char *reason) { - ERRORF("(srvr) failure: %d %s %s HTTP%02d %.*s %`'.*s %`'.*s %`'.*s %`'.*s", - code, reason, DescribeClient(), cpm.msg.version, - cpm.msg.xmethod.b - cpm.msg.xmethod.a, inbuf.p + cpm.msg.xmethod.a, + char method[9] = {0}; + WRITE64LE(method, cpm.msg.method); + ERRORF("(srvr) failure: %d %s %s HTTP%02d %s %`'.*s %`'.*s %`'.*s %`'.*s", + code, reason, DescribeClient(), cpm.msg.version, method, HeaderLength(kHttpHost), HeaderData(kHttpHost), cpm.msg.uri.b - cpm.msg.uri.a, inbuf.p + cpm.msg.uri.a, HeaderLength(kHttpReferer), HeaderData(kHttpReferer), @@ -2921,12 +2921,8 @@ static const char *GetSystemUrlLauncherCommand(void) { } static void LaunchBrowser(const char *path) { - int pid, ws; - struct in_addr addr; - const char *u, *prog; - sigset_t chldmask, savemask; - struct sigaction ignore, saveint, savequit; uint16_t port = 80; + struct in_addr addr; path = firstnonnull(path, "/"); // use the first server address if there is at least one server if (servers.n) { @@ -2936,42 +2932,7 @@ static void LaunchBrowser(const char *path) { // assign a loopback address if no server or unknown server address if (!servers.n || !addr.s_addr) addr.s_addr = htonl(INADDR_LOOPBACK); if (*path != '/') path = gc(xasprintf("/%s", path)); - if ((prog = commandv(GetSystemUrlLauncherCommand(), gc(malloc(PATH_MAX)), - PATH_MAX))) { - u = gc(xasprintf("http://%s:%d%s", inet_ntoa(addr), port, path)); - DEBUGF("(srvr) opening browser with command %`'s %s", prog, u); - ignore.sa_flags = 0; - ignore.sa_handler = SIG_IGN; - sigemptyset(&ignore.sa_mask); - sigaction(SIGINT, &ignore, &saveint); - sigaction(SIGQUIT, &ignore, &savequit); - sigemptyset(&chldmask); - sigaddset(&chldmask, SIGCHLD); - sigprocmask(SIG_BLOCK, &chldmask, &savemask); - CHECK_NE(-1, (pid = fork())); - if (!pid) { - setpgrp(); // ctrl-c'ing redbean shouldn't kill browser - sigaction(SIGINT, &saveint, 0); - sigaction(SIGQUIT, &savequit, 0); - sigprocmask(SIG_SETMASK, &savemask, 0); - execv(prog, (char *const[]){(char *)prog, (char *)u, 0}); - _Exit(127); - } - while (wait4(pid, &ws, 0, 0) == -1) { - CHECK_EQ(EINTR, errno); - errno = 0; - } - sigaction(SIGINT, &saveint, 0); - sigaction(SIGQUIT, &savequit, 0); - sigprocmask(SIG_SETMASK, &savemask, 0); - if (!(WIFEXITED(ws) && WEXITSTATUS(ws) == 0)) { - WARNF("(srvr) command %`'s exited with %d", GetSystemUrlLauncherCommand(), - WIFEXITED(ws) ? WEXITSTATUS(ws) : 128 + WEXITSTATUS(ws)); - } - } else { - WARNF("(srvr) can't launch browser because %`'s isn't installed", - GetSystemUrlLauncherCommand()); - } + launch_browser(gc(xasprintf("http://%s:%d%s", inet_ntoa(addr), port, path))); } static char *BadMethod(void) { @@ -3971,12 +3932,9 @@ static int LuaGetRedbeanVersion(lua_State *L) { static int LuaGetMethod(lua_State *L) { OnlyCallDuringRequest(L, "GetMethod"); - if (cpm.msg.method) { - lua_pushstring(L, kHttpMethod[cpm.msg.method]); - } else { - lua_pushlstring(L, inbuf.p + cpm.msg.xmethod.a, - cpm.msg.xmethod.b - cpm.msg.xmethod.a); - } + char method[9] = {0}; + WRITE64LE(method, cpm.msg.method); + lua_pushstring(L, method); return 1; } @@ -4870,6 +4828,9 @@ static int LuaBlackhole(lua_State *L) { return 1; } +static void BlockSignals(void) { +} + wontreturn static void Replenisher(void) { struct timespec ts; VERBOSEF("(token) replenish worker started"); @@ -6052,9 +6013,10 @@ static char *HandleRequest(void) { LockInc(&shared->c.urisrefused); return ServeFailure(400, "Bad URI"); } - INFOF("(req) received %s HTTP%02d %.*s %s %`'.*s %`'.*s", DescribeClient(), - cpm.msg.version, cpm.msg.xmethod.b - cpm.msg.xmethod.a, - inbuf.p + cpm.msg.xmethod.a, FreeLater(EncodeUrl(&url, 0)), + char method[9] = {0}; + WRITE64LE(method, cpm.msg.method); + INFOF("(req) received %s HTTP%02d %s %s %`'.*s %`'.*s", DescribeClient(), + cpm.msg.version, method, FreeLater(EncodeUrl(&url, 0)), HeaderLength(kHttpReferer), HeaderData(kHttpReferer), HeaderLength(kHttpUserAgent), HeaderData(kHttpUserAgent)); if (HasHeader(kHttpContentType) &&