diff --git a/net/http/http.h b/net/http/http.h index 93c672772..761eaff17 100644 --- a/net/http/http.h +++ b/net/http/http.h @@ -125,6 +125,8 @@ char *DecodeLatin1(const char *, size_t, size_t *); bool IsValidHttpToken(const char *, size_t); char *EncodeHttpHeaderValue(const char *, size_t, size_t *); char *VisualizeControlCodes(const char *, size_t, size_t *); +char *IndentLines(const char *, size_t, size_t *, size_t); +bool IsAcceptableHttpRequestPath(const char *, size_t); COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ diff --git a/net/http/indentlines.c b/net/http/indentlines.c new file mode 100644 index 000000000..2bd9aa1fa --- /dev/null +++ b/net/http/indentlines.c @@ -0,0 +1,57 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2021 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/str/str.h" +#include "libc/x/x.h" +#include "net/http/http.h" + +/** + * Inserts spaces before lines. + * + * @param data is input value + * @param size if -1 implies strlen + * @param out_size if non-NULL receives output length on success + * @param amt is number of spaces to use + * @return allocated NUL-terminated buffer, or NULL w/ errno + */ +char *IndentLines(const char *data, size_t size, size_t *out_size, size_t amt) { + char *r; + const char *p; + size_t i, n, m, a; + if (size == -1) size = strlen(data); + r = 0; + n = 0; + do { + if ((p = memchr(data, '\n', size))) { + m = p + 1 - data; + a = *data != '\r' && *data != '\n' ? amt : 0; + } else { + m = size; + a = size ? amt : 0; + } + r = xrealloc(r, n + a + m + 1); + memset(r + n, ' ', a); + memcpy(r + n + a, data, m); + n += a + m; + data += m; + size -= m; + } while (p); + if (out_size) *out_size = n; + r[n] = '\0'; + return r; +} diff --git a/net/http/isacceptablehttprequestpath.c b/net/http/isacceptablehttprequestpath.c new file mode 100644 index 000000000..c093a7b89 --- /dev/null +++ b/net/http/isacceptablehttprequestpath.c @@ -0,0 +1,75 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2021 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/str/thompike.h" +#include "net/http/http.h" + +/** + * Returns true if request path seems legit. + * + * 1. Request path must start with '/'. + * 2. The substring "//" is disallowed. + * 3. We won't serve hidden files (segment starts with '.'). + * 4. We won't serve paths with segments equal to "." or "..". + * + * It is assumed that the URI parser already took care of percent + * escape decoding as well as ISO-8859-1 decoding. The input needs + * to be a UTF-8 string. + */ +bool IsAcceptableHttpRequestPath(const char *data, size_t size) { + bool t; + size_t i; + unsigned n; + wint_t x, y, a, b; + const char *p, *e; + if (!size || *data != '/') return false; + t = 0; + p = data; + e = p + size; + while (p < e) { + x = *p++ & 0xff; + if (x >= 0300) { + a = ThomPikeByte(x); + n = ThomPikeLen(x) - 1; + if (p + n <= e) { + for (i = 0;;) { + b = p[i] & 0xff; + if (!ThomPikeCont(b)) break; + a = ThomPikeMerge(a, b); + if (++i == n) { + x = a; + p += i; + break; + } + } + } + } + if (x == '\\') { + x = '/'; + } + if (!t) { + t = true; + } else { + if ((x == '/' || x == '.') && y == '/') { + return false; + } + } + y = x; + } + return true; +} diff --git a/test/net/http/indentlines_test.c b/test/net/http/indentlines_test.c new file mode 100644 index 000000000..106bf9215 --- /dev/null +++ b/test/net/http/indentlines_test.c @@ -0,0 +1,61 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2021 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/mem/mem.h" +#include "libc/runtime/gc.internal.h" +#include "libc/testlib/ezbench.h" +#include "libc/testlib/hyperion.h" +#include "libc/testlib/testlib.h" +#include "net/http/http.h" + +TEST(IndentLines, testEmpty) { + char *p; + size_t n; + static const char kInput[] = ""; + static const char kOutput[] = ""; + p = gc(IndentLines(kInput, -1, &n, 2)); + EXPECT_STREQ(kOutput, p); + EXPECT_EQ(strlen(kOutput), n); +} + +TEST(IndentLines, test) { + char *p; + size_t n; + static const char kInput[] = "\ +HTTP/1.1 405 Method Not Allowed\r\n\ +Content-Type: text/plain; charset=utf-8\r\n\ +Date: Sun, 28 Mar 2021 10:47:47 GMT\r\n\ +Server: redbean/0.2\r\n\ +Content-Length: 20\r\n\ +\r\n"; + static const char kOutput[] = "\ + HTTP/1.1 405 Method Not Allowed\r\n\ + Content-Type: text/plain; charset=utf-8\r\n\ + Date: Sun, 28 Mar 2021 10:47:47 GMT\r\n\ + Server: redbean/0.2\r\n\ + Content-Length: 20\r\n\ +\r\n"; + p = gc(IndentLines(kInput, -1, &n, 2)); + EXPECT_STREQ(kOutput, p); + EXPECT_EQ(strlen(kOutput), n); +} + +BENCH(IndentLines, bench) { + EZBENCH2("IndentLines", donothing, + free(IndentLines(kHyperion, kHyperionSize, 0, 2))); +} diff --git a/test/net/http/isacceptablehttprequestpath_test.c b/test/net/http/isacceptablehttprequestpath_test.c new file mode 100644 index 000000000..a256bdf55 --- /dev/null +++ b/test/net/http/isacceptablehttprequestpath_test.c @@ -0,0 +1,61 @@ +/*-*- 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/runtime/gc.internal.h" +#include "libc/testlib/ezbench.h" +#include "libc/testlib/testlib.h" +#include "net/http/escape.h" +#include "net/http/http.h" + +TEST(IsAcceptableHttpRequestPath, test) { + EXPECT_TRUE(IsAcceptableHttpRequestPath("/", 1)); + EXPECT_TRUE(IsAcceptableHttpRequestPath("/index.html", 11)); +} + +TEST(IsAcceptableHttpRequestPath, testDoubleSlash_notAllowed) { + EXPECT_FALSE(IsAcceptableHttpRequestPath("//", 2)); + EXPECT_FALSE(IsAcceptableHttpRequestPath("/foo//bar", 9)); +} + +TEST(IsAcceptableHttpRequestPath, testDoesntStartWithSlash_notAllowed) { + EXPECT_FALSE(IsAcceptableHttpRequestPath(NULL, 0)); + EXPECT_FALSE(IsAcceptableHttpRequestPath("*", 1)); +} + +TEST(IsAcceptableHttpRequestPath, testNoncanonicalDirectories_areForbidden) { + EXPECT_FALSE(IsAcceptableHttpRequestPath("/.", 2)); + EXPECT_FALSE(IsAcceptableHttpRequestPath("/./", 3)); + EXPECT_FALSE(IsAcceptableHttpRequestPath("/../", 4)); +} + +TEST(IsAcceptableHttpRequestPath, testNoncanonicalWindowsDirs_areForbidden) { + EXPECT_FALSE(IsAcceptableHttpRequestPath("\\.", 2)); + EXPECT_FALSE(IsAcceptableHttpRequestPath("\\.\\", 3)); + EXPECT_FALSE(IsAcceptableHttpRequestPath("\\..\\", 4)); +} + +TEST(IsAcceptableHttpRequestPath, testOverlongSlashDot_isDetected) { + EXPECT_FALSE(IsAcceptableHttpRequestPath("/\300\256", 3)); + EXPECT_FALSE(IsAcceptableHttpRequestPath("/\300\257", 3)); + EXPECT_FALSE(IsAcceptableHttpRequestPath("\300\256\300\256", 4)); +} + +BENCH(IsAcceptableHttpRequestPath, bench) { + EZBENCH2("IsAcceptableHttpRequestPath", donothing, + IsAcceptableHttpRequestPath("/index.html", 11)); +} diff --git a/tool/net/redbean.c b/tool/net/redbean.c index fc7fe4215..3cf0d8cb4 100644 --- a/tool/net/redbean.c +++ b/tool/net/redbean.c @@ -98,6 +98,8 @@ FLAGS\n\ -u uniprocess\n\ -z print port\n\ -m log messages\n\ + -b log message bodies\n\ + -D DIR serve assets from directory\n\ -c INT cache seconds\n\ -r /X=/Y redirect X to Y\n\ -l ADDR listen ip [default 0.0.0.0]\n\ @@ -215,6 +217,8 @@ static const struct ContentTypeExtension { {"js", "application/javascript"}, // {"json", "application/json"}, // {"m4a", "audio/mpeg"}, // + {"markdown", "text/plain"}, // + {"md", "text/plain"}, // {"mp2", "audio/mpeg"}, // {"mp3", "audio/mpeg"}, // {"mp4", "video/mp4"}, // @@ -239,6 +243,11 @@ struct Buffer { char *p; }; +struct Strings { + size_t n; + char **p; +}; + struct Parser { int i; int c; @@ -285,6 +294,10 @@ static struct Assets { uint32_t hash; int64_t lastmodified; char *lastmodifiedstr; + struct File { + char *path; + struct stat st; + } * file; } * p; } assets; @@ -318,14 +331,15 @@ static unsigned httpversion; static uint32_t clientaddrsize; static lua_State *L; +static size_t zsize; static void *content; static uint8_t *zdir; static uint8_t *zmap; static size_t hdrsize; static size_t msgsize; static size_t amtread; -static size_t zsize; static char *luaheaderp; +struct Strings stagedirs; static const char *pidpath; static const char *logpath; static int64_t programtime; @@ -514,9 +528,27 @@ static wontreturn void PrintUsage(FILE *f, int rc) { exit(rc); } +static char *RemoveTrailingSlashes(char *s) { + size_t n; + n = strlen(s); + while (n && (s[n - 1] == '/' || s[n - 1] == '\\')) s[--n] = '\0'; + return s; +} + +static void AddStagingDirectory(const char *dirpath) { + char *s; + s = RemoveTrailingSlashes(strdup(dirpath)); + if (!isdirectory(s)) { + fprintf(stderr, "error: not a directory: %s\n", s); + exit(1); + } + stagedirs.p = xrealloc(stagedirs.p, ++stagedirs.n * sizeof(*stagedirs.p)); + stagedirs.p[stagedirs.n - 1] = s; +} + static void GetOpts(int argc, char *argv[]) { int opt; - while ((opt = getopt(argc, argv, "zhduvmbl:p:w:r:c:L:P:U:G:B:")) != -1) { + while ((opt = getopt(argc, argv, "zhduvmbl:p:w:r:c:L:P:U:G:B:D:")) != -1) { switch (opt) { case 'v': __log_level++; @@ -539,6 +571,9 @@ static void GetOpts(int argc, char *argv[]) { case 'r': AddRedirect(optarg); break; + case 'D': + AddStagingDirectory(optarg); + break; case 'c': cacheseconds = strtol(optarg, NULL, 0); break; @@ -662,7 +697,9 @@ static ssize_t WritevAll(int fd, struct iovec *iov, int iovlen) { } } while (wrote); } else if (errno == EINTR) { - if (killed) return -1; + if (killed || (meltdown && nowl() - startread > 2)) { + return -1; + } } else { return -1; } @@ -735,7 +772,8 @@ static int64_t GetLastModifiedZip(const uint8_t *cfile) { } static bool IsCompressed(struct Asset *a) { - return ZIP_LFILE_COMPRESSIONMETHOD(zmap + a->lf) == kZipCompressionDeflate; + return !a->file && + ZIP_LFILE_COMPRESSIONMETHOD(zmap + a->lf) == kZipCompressionDeflate; } static bool IsNotModified(struct Asset *a) { @@ -754,6 +792,22 @@ static char *FormatUnixHttpDateTime(char *s, int64_t t) { return s; } +static void *FreeLater(void *p) { + if (p) { + freelist.p = xrealloc(freelist.p, ++freelist.n * sizeof(*freelist.p)); + freelist.p[freelist.n - 1] = p; + } + return p; +} + +static void CollectGarbage(void) { + size_t i; + for (i = 0; i < freelist.n; ++i) free(freelist.p[i]); + freelist.n = 0; + free(request.params.p); + DestroyHttpRequest(&msg); +} + static void IndexAssets(void) { int64_t lm; struct Asset *p; @@ -812,7 +866,7 @@ static struct Asset *FindAsset(const char *path, size_t pathlen) { } } -static struct Asset *LocateAsset(const char *path, size_t pathlen) { +static struct Asset *LocateAssetZip(const char *path, size_t pathlen) { char *path2; struct Asset *a; if (pathlen && path[0] == '/') ++path, --pathlen; @@ -830,20 +884,43 @@ static struct Asset *LocateAsset(const char *path, size_t pathlen) { return a; } -static void *FreeLater(void *p) { - if (p) { - freelist.p = xrealloc(freelist.p, ++freelist.n * sizeof(*freelist.p)); - freelist.p[freelist.n - 1] = p; +static struct Asset *LocateAssetFile(const char *path, size_t pathlen) { + size_t i; + char *path2; + struct Asset *a; + if (stagedirs.n) { + a = FreeLater(xcalloc(1, sizeof(struct Asset))); + a->file = FreeLater(xmalloc(sizeof(struct File))); + for (i = 0; i < stagedirs.n; ++i) { + if (stat((a->file->path = FreeLater(xasprintf( + "%s%.*s", stagedirs.p[i], request.path.n, request.path.p))), + &a->file->st) != -1 && + (S_ISREG(a->file->st.st_mode) || + (S_ISDIR(a->file->st.st_mode) && + ((stat((a->file->path = FreeLater( + xasprintf("%s%s", a->file->path, "index.lua"))), + &a->file->st) != -1 && + S_ISREG(a->file->st.st_mode)) || + (stat((a->file->path = FreeLater( + xasprintf("%s%s", a->file->path, "index.html"))), + &a->file->st) != -1 && + S_ISREG(a->file->st.st_mode)))))) { + a->lastmodifiedstr = FormatUnixHttpDateTime( + FreeLater(xmalloc(30)), + (a->lastmodified = a->file->st.st_mtim.tv_sec)); + return a; + } + } } - return p; + return NULL; } -static void CollectGarbage(void) { - size_t i; - for (i = 0; i < freelist.n; ++i) free(freelist.p[i]); - freelist.n = 0; - free(request.params.p); - DestroyHttpRequest(&msg); +static struct Asset *LocateAsset(const char *path, size_t pathlen) { + struct Asset *a; + if (!(a = LocateAssetFile(path, pathlen))) { + a = LocateAssetZip(path, pathlen); + } + return a; } static void EmitParamKey(struct Parser *u, struct Params *h) { @@ -944,25 +1021,21 @@ static void ParseFragment(struct Parser *u, struct Buffer *h) { u->q = u->p; } -static bool IsForbiddenPath(struct Buffer *b) { - return !!memmem(b->p, b->n, "/.", 2); -} - static bool ParseRequestUri(void) { struct Parser u; u.i = 0; - u.c = '/'; + u.c = 0; u.isform = false; u.islatin1 = true; u.data = inbuf.p + msg.uri.a; u.size = msg.uri.b - msg.uri.a; memset(&request, 0, sizeof(request)); - if (!u.size || *u.data != '/') return false; u.q = u.p = FreeLater(xmalloc(u.size * 2)); - if (u.c == '/') ParsePath(&u, &request.path); + ParsePath(&u, &request.path); if (u.c == '?') ParseParams(&u, &request.params); if (u.c == '#') ParseFragment(&u, &request.fragment); - return u.i == u.size && !IsForbiddenPath(&request.path); + return u.i == u.size && + IsAcceptableHttpRequestPath(request.path.p, request.path.n); } static void ParseFormParams(void) { @@ -1054,8 +1127,7 @@ static char *AppendExpires(char *p, int64_t t) { struct tm tm; gmtime_r(&t, &tm); p = AppendHeaderName(p, "Expires"); - FormatHttpDateTime(p, &tm); - p += 29; + p = FormatHttpDateTime(p, &tm); return AppendCrlf(p); } @@ -1144,6 +1216,7 @@ static void *Deflate(const void *data, size_t size, size_t *out_size) { static void *LoadAsset(struct Asset *a, size_t *out_size) { size_t size; uint8_t *data; + if (a->file) return FreeLater(xslurp(a->file->path, out_size)); size = ZIP_LFILE_UNCOMPRESSEDSIZE(zmap + a->lf); data = xmalloc(size + 1); if (ZIP_LFILE_COMPRESSIONMETHOD(zmap + a->lf) == kZipCompressionDeflate) { @@ -1178,8 +1251,13 @@ static char *ServeAsset(struct Asset *a, const char *path, size_t pathlen) { pathlen, path); p = SetStatus(304, "Not Modified"); } else { - content = ZIP_LFILE_CONTENT(zmap + a->lf); - contentlength = ZIP_LFILE_COMPRESSEDSIZE(zmap + a->lf); + if (!a->file) { + content = ZIP_LFILE_CONTENT(zmap + a->lf); + contentlength = ZIP_LFILE_COMPRESSEDSIZE(zmap + a->lf); + } else { + /* TODO(jart): Use sendfile(). */ + content = FreeLater(xslurp(a->file->path, &contentlength)); + } if (IsCompressed(a)) { if (ClientAcceptsGzip()) { gzipped = true; @@ -1283,12 +1361,13 @@ static int LuaLoadAsset(lua_State *L) { const char *path; size_t size, pathlen; path = luaL_checklstring(L, 1, &pathlen); - if (!(a = LocateAsset(path, pathlen))) { - return luaL_argerror(L, 1, "not found"); + if ((a = LocateAsset(path, pathlen))) { + data = LoadAsset(a, &size); + lua_pushlstring(L, data, size); + free(data); + } else { + lua_pushnil(L); } - data = LoadAsset(a, &size); - lua_pushlstring(L, data, size); - free(data); return 1; } @@ -1317,22 +1396,26 @@ static int LuaGetFragment(lua_State *L) { return 1; } +static void LuaPushLatin1(lua_State *L, const char *s, size_t n) { + char *t; + size_t m; + t = DecodeLatin1(s, n, &m); + lua_pushlstring(L, t, m); + free(t); +} + static int LuaGetUri(lua_State *L) { - lua_pushlstring(L, inbuf.p + msg.uri.a, msg.uri.b - msg.uri.a); + LuaPushLatin1(L, inbuf.p + msg.uri.a, msg.uri.b - msg.uri.a); return 1; } -static int LuaFormatDate(lua_State *L) { - int64_t t; +static int LuaFormatHttpDateTime(lua_State *L) { char buf[30]; - struct tm tm; - t = luaL_checkinteger(L, 1); - gmtime_r(&t, &tm); - lua_pushstring(L, FormatHttpDateTime(buf, &tm)); + lua_pushstring(L, FormatUnixHttpDateTime(buf, luaL_checkinteger(L, 1))); return 1; } -static int LuaParseDate(lua_State *L) { +static int LuaParseHttpDateTime(lua_State *L) { size_t n; const char *s; s = luaL_checklstring(L, 1, &n); @@ -1355,14 +1438,6 @@ static int LuaGetPayload(lua_State *L) { return 1; } -static void LuaPushLatin1(lua_State *L, const char *s, size_t n) { - char *t; - size_t m; - t = DecodeLatin1(s, n, &m); - lua_pushlstring(L, t, m); - free(t); -} - static int LuaGetHeader(lua_State *L) { int h; const char *key; @@ -1579,17 +1654,6 @@ static int LuaDecodeBase64(lua_State *L) { return 1; } -static int LuaVisualizeControlCodes(lua_State *L) { - char *p; - size_t size, n; - const char *data; - data = luaL_checklstring(L, 1, &size); - p = VisualizeControlCodes(data, size, &n); - lua_pushlstring(L, p, n); - free(p); - return 1; -} - static int LuaPopcnt(lua_State *L) { lua_pushinteger(L, popcnt(luaL_checkinteger(L, 1))); return 1; @@ -1615,6 +1679,26 @@ static int LuaBsf(lua_State *L) { } } +static int LuaCrc32(lua_State *L) { + long i; + size_t n; + const char *p; + i = luaL_checkinteger(L, 1); + p = luaL_checklstring(L, 2, &n); + lua_pushinteger(L, crc32_z(i, p, n)); + return 1; +} + +static int LuaCrc32c(lua_State *L) { + long i; + size_t n; + const char *p; + i = luaL_checkinteger(L, 1); + p = luaL_checklstring(L, 2, &n); + lua_pushinteger(L, crc32c(i, p, n)); + return 1; +} + static void LuaRun(const char *path) { struct Asset *a; const char *code; @@ -1630,39 +1714,41 @@ static void LuaRun(const char *path) { } static const luaL_Reg kLuaFuncs[] = { - {"DecodeBase64", LuaDecodeBase64}, // - {"EncodeBase64", LuaEncodeBase64}, // - {"EscapeFragment", LuaEscapeFragment}, // - {"EscapeHtml", LuaEscapeHtml}, // - {"EscapeLiteral", LuaEscapeLiteral}, // - {"EscapeParam", LuaEscapeParam}, // - {"EscapePath", LuaEscapePath}, // - {"EscapeSegment", LuaEscapeSegment}, // - {"FormatDate", LuaFormatDate}, // - {"GetClientAddr", LuaGetClientAddr}, // - {"GetDate", LuaGetDate}, // - {"GetFragment", LuaGetFragment}, // - {"GetHeader", LuaGetHeader}, // - {"GetHeaders", LuaGetHeaders}, // - {"GetMethod", LuaGetMethod}, // - {"GetParam", LuaGetParam}, // - {"GetParams", LuaGetParams}, // - {"GetPath", LuaGetPath}, // - {"GetPayload", LuaGetPayload}, // - {"GetServerAddr", LuaGetServerAddr}, // - {"GetUri", LuaGetUri}, // - {"GetVersion", LuaGetVersion}, // - {"HasParam", LuaHasParam}, // - {"LoadAsset", LuaLoadAsset}, // - {"ParseDate", LuaParseDate}, // - {"ServeAsset", LuaServeAsset}, // - {"ServeError", LuaServeError}, // - {"SetHeader", LuaSetHeader}, // - {"SetStatus", LuaSetStatus}, // - {"Write", LuaWrite}, // - {"bsf", LuaBsf}, // - {"bsr", LuaBsr}, // - {"popcnt", LuaPopcnt}, // + {"DecodeBase64", LuaDecodeBase64}, // + {"EncodeBase64", LuaEncodeBase64}, // + {"EscapeFragment", LuaEscapeFragment}, // + {"EscapeHtml", LuaEscapeHtml}, // + {"EscapeLiteral", LuaEscapeLiteral}, // + {"EscapeParam", LuaEscapeParam}, // + {"EscapePath", LuaEscapePath}, // + {"EscapeSegment", LuaEscapeSegment}, // + {"FormatHttpDateTime", LuaFormatHttpDateTime}, // + {"GetClientAddr", LuaGetClientAddr}, // + {"GetDate", LuaGetDate}, // + {"GetFragment", LuaGetFragment}, // + {"GetHeader", LuaGetHeader}, // + {"GetHeaders", LuaGetHeaders}, // + {"GetMethod", LuaGetMethod}, // + {"GetParam", LuaGetParam}, // + {"GetParams", LuaGetParams}, // + {"GetPath", LuaGetPath}, // + {"GetPayload", LuaGetPayload}, // + {"GetServerAddr", LuaGetServerAddr}, // + {"GetUri", LuaGetUri}, // + {"GetVersion", LuaGetVersion}, // + {"HasParam", LuaHasParam}, // + {"LoadAsset", LuaLoadAsset}, // + {"ParseHttpDateTime", LuaParseHttpDateTime}, // + {"ServeAsset", LuaServeAsset}, // + {"ServeError", LuaServeError}, // + {"SetHeader", LuaSetHeader}, // + {"SetStatus", LuaSetStatus}, // + {"Write", LuaWrite}, // + {"bsf", LuaBsf}, // + {"bsr", LuaBsr}, // + {"crc32", LuaCrc32}, // + {"crc32c", LuaCrc32c}, // + {"popcnt", LuaPopcnt}, // }; static void LuaSetArgv(void) { @@ -1723,6 +1809,7 @@ static char *ServeLua(struct Asset *a) { } static bool IsLua(struct Asset *a) { + if (a->file) return endswith(a->file->path, ".lua"); return ZIP_LFILE_NAMESIZE(zmap + a->lf) >= 4 && !memcmp(ZIP_LFILE_NAME(zmap + a->lf) + ZIP_LFILE_NAMESIZE(zmap + a->lf) - 4, @@ -1764,13 +1851,16 @@ static char *HandleRedirect(struct Redirect *r) { } static void LogMessage(const char *d, const char *s, size_t n) { - char *s2, *s3; - size_t n2, n3; + size_t n2, n3, n4; + char *s2, *s3, *s4; if (!logmessages) return; while (n && (s[n - 1] == '\r' || s[n - 1] == '\n')) --n; if ((s2 = DecodeLatin1(s, n, &n2))) { if ((s3 = VisualizeControlCodes(s2, n2, &n3))) { - LOGF("%s %s %,ld byte message\n%.*s", clientaddrstr, d, n, n3, s3); + if ((s4 = IndentLines(s3, n3, &n4, 1))) { + LOGF("%s %s %,ld byte message\n%.*s", clientaddrstr, d, n, n4, s4); + free(s4); + } free(s3); } free(s2); @@ -1778,12 +1868,15 @@ static void LogMessage(const char *d, const char *s, size_t n) { } static void LogBody(const char *d, const char *s, size_t n) { - char *s2; - size_t n2; + char *s2, *s3; + size_t n2, n3; if (!n || !logbodies) return; while (n && (s[n - 1] == '\r' || s[n - 1] == '\n')) --n; if ((s2 = VisualizeControlCodes(s, n, &n2))) { - LOGF("%s %s %,ld byte payload\n%.*s", clientaddrstr, d, n, n2, s2); + if ((s3 = IndentLines(s2, n2, &n3, 1))) { + LOGF("%s %s %,ld byte payload\n%.*s", clientaddrstr, d, n, n3, s3); + free(s3); + } free(s2); } } diff --git a/tool/net/redbean.lua b/tool/net/redbean.lua index b08461163..ba042023e 100644 --- a/tool/net/redbean.lua +++ b/tool/net/redbean.lua @@ -74,8 +74,8 @@ local function main() Write([[

xmlhttprequest request demo

-
-
+
+
]]) + + Write('

extra information

\n') + Write('
GetClientAddr()\n') + Write('
') + Write(GetClientAddr()) + Write('\n') + Write('
GetServerAddr()\n') + Write('
') + Write(GetServerAddr()) + Write('\n') + Write('
FormatHttpDateTime(GetDate())\n') + Write('
') + Write(FormatHttpDateTime(GetDate())) + Write('\n') + Write('\n') end main()