diff --git a/libc/fmt/stoa.c b/libc/fmt/stoa.c index d189dffa4..6040c1e08 100644 --- a/libc/fmt/stoa.c +++ b/libc/fmt/stoa.c @@ -53,10 +53,8 @@ static int __fmt_stoa_bing(out_f out, void *a, uint64_t w) { static int __fmt_stoa_quoted(out_f out, void *a, uint64_t w) { char buf[8]; - if (w <= 0x7F) { - if (w < 0x20 || w == 0x7F) { - w = cescapec(w); - } + if (isascii(w)) { + w = cescapec(w); } else { w = tpenc(w); } diff --git a/tool/net/demo/.init.lua b/tool/net/demo/.init.lua new file mode 100644 index 000000000..077d60f7d --- /dev/null +++ b/tool/net/demo/.init.lua @@ -0,0 +1,17 @@ +-- /.init.lua is loaded at startup in redbean's main process + +HidePath('/usr/share/zoneinfo/') + +function OnHttpRequest() + if HasParam('magic') then + Write('

\r\n') + Write('OnHttpRequest() has intercepted your request
\r\n') + Write('because you specified the magic parameter\r\n') + Write('

\r\n')
+      Write(EscapeHtml(LoadAsset('/.init.lua')))
+      Write('
\r\n') + else + Route() + end + SetHeader('Server', 'redbean!') +end diff --git a/tool/net/.reload.lua b/tool/net/demo/.reload.lua similarity index 100% rename from tool/net/.reload.lua rename to tool/net/demo/.reload.lua diff --git a/tool/net/404.html b/tool/net/demo/404.html similarity index 100% rename from tool/net/404.html rename to tool/net/demo/404.html diff --git a/tool/net/demo/hello.lua b/tool/net/demo/hello.lua new file mode 100644 index 000000000..602458869 --- /dev/null +++ b/tool/net/demo/hello.lua @@ -0,0 +1 @@ +Write('hello world\r\n') diff --git a/tool/net/index.html b/tool/net/demo/index.html similarity index 100% rename from tool/net/index.html rename to tool/net/demo/index.html diff --git a/tool/net/redbean-form.lua b/tool/net/demo/redbean-form.lua similarity index 100% rename from tool/net/redbean-form.lua rename to tool/net/demo/redbean-form.lua diff --git a/tool/net/redbean-xhr.lua b/tool/net/demo/redbean-xhr.lua similarity index 100% rename from tool/net/redbean-xhr.lua rename to tool/net/demo/redbean-xhr.lua diff --git a/tool/net/redbean.css b/tool/net/demo/redbean.css similarity index 100% rename from tool/net/redbean.css rename to tool/net/demo/redbean.css diff --git a/tool/net/redbean.lua b/tool/net/demo/redbean.lua similarity index 95% rename from tool/net/redbean.lua rename to tool/net/demo/redbean.lua index 41727d0eb..3c9650fbd 100644 --- a/tool/net/redbean.lua +++ b/tool/net/demo/redbean.lua @@ -33,10 +33,10 @@ local function main() SetHeader('Expires', FormatHttpDateTime(GetDate())) SetHeader('Cache-Control', 'no-cache, must-revalidate, max-age=0') - -- Roundtripping information can make it safer. - Write('

Thank you for visiting ') - Write(EscapeHtml(EncodeUrl(ParseUrl(GetUrl())))) - Write('\r\n') + -- GetUrl() is the resolved Request-URI (TODO: Maybe change API to return a URL object?) + Write('

Thank you for visiting ') + Write(GetUrl()) -- redbean encoded this value so it doesn't need html entity escaping + Write('\r\n') -- GetParam(NAME) is the fastest easiest way to get URL and FORM params -- If you want the RequestURL query params specifically in full do this @@ -55,6 +55,12 @@ local function main() end end Write('\r\n') + Write("

Whatever you do, don't click on ") + Write('') + Write(EscapeHtml(VisualizeControlCodes(GetPath()))) + Write('?magic\r\n') else Write('

\r\n') Write('none
\r\n') diff --git a/tool/net/seekable.txt b/tool/net/demo/seekable.txt similarity index 100% rename from tool/net/seekable.txt rename to tool/net/demo/seekable.txt diff --git a/tool/net/net.mk b/tool/net/net.mk index 46a53a7e3..5fafc16ba 100644 --- a/tool/net/net.mk +++ b/tool/net/net.mk @@ -72,13 +72,12 @@ o/$(MODE)/tool/net/redbean.com.dbg: \ o/$(MODE)/tool/net/redbean.com: \ o/$(MODE)/tool/net/redbean.com.dbg \ tool/net/net.mk \ - tool/net/favicon.ico \ - tool/net/redbean.png \ tool/net/.init.lua \ - tool/net/.reload.lua + tool/net/favicon.ico \ + tool/net/redbean.png @$(COMPILE) -AOBJCOPY -T$@ $(OBJCOPY) -S -O binary $< $@ @$(COMPILE) -ADD -T$@ dd if=$@ of=o/$(MODE)/tool/net/.ape bs=64 count=11 conv=notrunc 2>/dev/null - @$(COMPILE) -AZIP -T$@ zip -qj $@ o/$(MODE)/tool/net/.ape tool/net/.init.lua tool/net/.reload.lua tool/net/favicon.ico tool/net/redbean.png + @$(COMPILE) -AZIP -T$@ zip -qj $@ o/$(MODE)/tool/net/.ape tool/net/.init.lua tool/net/favicon.ico tool/net/redbean.png o/$(MODE)/tool/net/redbean-demo.com.dbg: \ o/$(MODE)/tool/net/redbean.com.dbg @@ -87,17 +86,18 @@ o/$(MODE)/tool/net/redbean-demo.com.dbg: \ o/$(MODE)/tool/net/redbean-demo.com: \ o/$(MODE)/tool/net/redbean-demo.com.dbg \ tool/net/net.mk \ - tool/net/.init.lua \ - tool/net/.reload.lua \ - tool/net/404.html \ tool/net/favicon.ico \ tool/net/redbean.png \ - tool/net/index.html \ - tool/net/redbean.css \ - tool/net/redbean.lua \ - tool/net/redbean-form.lua \ - tool/net/redbean-xhr.lua \ - tool/net/seekable.txt \ + tool/net/demo/.init.lua \ + tool/net/demo/.reload.lua \ + tool/net/demo/404.html \ + tool/net/demo/hello.lua \ + tool/net/demo/index.html \ + tool/net/demo/redbean.css \ + tool/net/demo/redbean.lua \ + tool/net/demo/redbean-form.lua \ + tool/net/demo/redbean-xhr.lua \ + tool/net/demo/seekable.txt \ tool/net/redbean.c \ net/http/parsehttprequest.c \ net/http/parseurl.c \ @@ -105,18 +105,20 @@ o/$(MODE)/tool/net/redbean-demo.com: \ test/net/http/parsehttprequest_test.c \ test/net/http/parseurl_test.c @$(COMPILE) -AOBJCOPY -T$@ $(OBJCOPY) -S -O binary $< $@ - @$(COMPILE) -ADD -T$@ dd if=$@ of=o/$(MODE)/tool/net/.ape bs=64 count=11 conv=notrunc 2>/dev/null - @$(COMPILE) -AZIP -T$@ zip -qj $@ o/$(MODE)/tool/net/.ape tool/net/.init.lua tool/net/.reload.lua tool/net/redbean.lua tool/net/404.html tool/net/favicon.ico tool/net/redbean.png tool/net/redbean-form.lua tool/net/redbean-xhr.lua - @$(COMPILE) -AZIP -T$@ zip -qj0 $@ tool/net/seekable.txt - @$(COMPILE) -AZIP -T$@ zip -q $@ tool/net tool/net/index.html tool/net/redbean.css tool/net/redbean.c net/http/parsehttprequest.c net/http/parseurl.c net/http/encodeurl.c test/net/http/parsehttprequest_test.c test/net/http/parseurl_test.c + @$(COMPILE) -AMKDIR -T$@ mkdir -p o/$(MODE)/tool/net/.redbean-demo + @$(COMPILE) -ADD -T$@ dd if=$@ of=o/$(MODE)/tool/net/.redbean-demo/.ape bs=64 count=11 conv=notrunc 2>/dev/null + @$(COMPILE) -AZIP -T$@ zip -qj $@ o/$(MODE)/tool/net/.redbean-demo/.ape tool/net/demo/.init.lua tool/net/demo/.reload.lua tool/net/demo/hello.lua tool/net/demo/redbean.lua tool/net/demo/404.html tool/net/favicon.ico tool/net/redbean.png tool/net/demo/redbean-form.lua tool/net/demo/redbean-xhr.lua + @$(COMPILE) -AZIP -T$@ zip -qj0 $@ tool/net/demo/seekable.txt + @$(COMPILE) -AZIP -T$@ zip -q $@ tool/net/ tool/net/demo/ tool/net/demo/index.html tool/net/demo/redbean.css tool/net/redbean.c net/http/parsehttprequest.c net/http/parseurl.c net/http/encodeurl.c test/net/http/parsehttprequest_test.c test/net/http/parseurl_test.c o/$(MODE)/tool/net/redbean-static.com: \ o/$(MODE)/tool/net/redbean-static.com.dbg \ tool/net/favicon.ico \ tool/net/redbean.png @$(COMPILE) -AOBJCOPY -T$@ $(OBJCOPY) -S -O binary $< $@ - @$(COMPILE) -ADD -T$@ dd if=$@ of=o/$(MODE)/tool/net/.ape bs=64 count=11 conv=notrunc 2>/dev/null - @$(COMPILE) -AZIP -T$@ zip -qj $@ o/$(MODE)/tool/net/.ape tool/net/favicon.ico tool/net/redbean.png + @$(COMPILE) -AMKDIR -T$@ mkdir -p o/$(MODE)/tool/net/.redbean-static + @$(COMPILE) -ADD -T$@ dd if=$@ of=o/$(MODE)/tool/net/.redbean-static/.ape bs=64 count=11 conv=notrunc 2>/dev/null + @$(COMPILE) -AZIP -T$@ zip -qj $@ o/$(MODE)/tool/net/.redbean-static/.ape tool/net/favicon.ico tool/net/redbean.png o/$(MODE)/tool/net/redbean-static.com.dbg: \ $(TOOL_NET_DEPS) \ diff --git a/tool/net/redbean.c b/tool/net/redbean.c index 34dc1fa73..c7ebcc6a7 100644 --- a/tool/net/redbean.c +++ b/tool/net/redbean.c @@ -350,12 +350,12 @@ static bool connectionclose; static bool keyboardinterrupt; static bool encouragekeepalive; static bool loggednetworkorigin; +static bool hasluaglobalhandler; static int frags; static int gmtoff; static int server; static int client; -static int warmups; static int daemonuid; static int daemongid; static int statuscode; @@ -375,6 +375,7 @@ static char *luaheaderp; static const char *brand; static const char *pidpath; static const char *logpath; +static struct Strings loops; static size_t contentlength; static int64_t cacheseconds; static uint8_t gzip_footer[8]; @@ -954,6 +955,16 @@ static void AddString(struct Strings *l, char *s) { l->p[l->n - 1] = s; } +static bool HasString(struct Strings *l, char *s) { + size_t i; + for (i = 0; i < l->n; ++i) { + if (!strcmp(l->p[i], s)) { + return true; + } + } + return false; +} + static void AddStagingDirectory(const char *dirpath) { char *s; s = RemoveTrailingSlashes(strdup(dirpath)); @@ -1977,32 +1988,575 @@ static void LaunchBrowser() { system(openbrowsercommand); } +static char *BadMethod(void) { + LockInc(&shared->badmethods); + return stpcpy(ServeError(405, "Method Not Allowed"), "Allow: GET, HEAD\r\n"); +} + +static int GetDecimalWidth(int x) { + int w = x ? ceil(log10(x)) : 1; + return w + (w - 1) / 3; +} + +static int GetOctalWidth(int x) { + return !x ? 1 : x < 8 ? 2 : 1 + bsr(x) / 3; +} + +static const char *DescribeCompressionRatio(char rb[8], uint64_t lf) { + long percent; + if (ZIP_LFILE_COMPRESSIONMETHOD(zmap + lf) == kZipCompressionNone) { + return "n/a"; + } else { + percent = lround(100 - (double)GetZipLfileCompressedSize(zmap + lf) / + GetZipLfileUncompressedSize(zmap + lf) * 100); + sprintf(rb, "%ld%%", MIN(999, MAX(-999, percent))); + return rb; + } +} + +static char *ServeListing(void) { + long x; + ldiv_t y; + int w[3]; + struct tm tm; + const char *and; + int64_t lastmod; + uint64_t cf, lf; + struct rusage ru; + char *p, *q, *path; + char rb[8], tb[64], *rp[6]; + size_t i, n, pathlen, rn[6]; + LockInc(&shared->listingrequests); + if (msg.method != kHttpGet && msg.method != kHttpHead) return BadMethod(); + Append("\ +\r\n\ +\r\n\ +redbean zip listing\r\n\ +\r\n\ +

\r\n"); + AppendLogo(); + rp[0] = EscapeHtml(brand, -1, &rn[0]); + AppendData(rp[0], rn[0]); + free(rp[0]); + Append("


\r\n");
+  memset(w, 0, sizeof(w));
+  n = GetZipCdirRecords(cdir);
+  for (cf = GetZipCdirOffset(cdir); n--; cf += ZIP_CFILE_HDRSIZE(zmap + cf)) {
+    CHECK_EQ(kZipCfileHdrMagic, ZIP_CFILE_MAGIC(zmap + cf));
+    lf = GetZipCfileOffset(zmap + cf);
+    path = GetAssetPath(cf, &pathlen);
+    if (!IsHiddenPath(path)) {
+      w[0] = min(80, max(w[0], strwidth(path, 0) + 2));
+      w[1] = max(w[1], GetOctalWidth(GetZipCfileMode(zmap + cf)));
+      w[2] = max(w[2], GetDecimalWidth(GetZipLfileUncompressedSize(zmap + lf)));
+    }
+    free(path);
+  }
+  n = GetZipCdirRecords(cdir);
+  for (cf = GetZipCdirOffset(cdir); n--; cf += ZIP_CFILE_HDRSIZE(zmap + cf)) {
+    CHECK_EQ(kZipCfileHdrMagic, ZIP_CFILE_MAGIC(zmap + cf));
+    lf = GetZipCfileOffset(zmap + cf);
+    path = GetAssetPath(cf, &pathlen);
+    if (!IsHiddenPath(path)) {
+      rp[0] = VisualizeControlCodes(path, pathlen, &rn[0]);
+      rp[1] = EscapePath(path, pathlen, &rn[1]);
+      rp[2] = EscapeHtml(rp[1], rn[1], &rn[2]);
+      rp[3] = VisualizeControlCodes(ZIP_CFILE_COMMENT(zmap + cf),
+                                    strnlen(ZIP_CFILE_COMMENT(zmap + cf),
+                                            ZIP_CFILE_COMMENTSIZE(zmap + cf)),
+                                    &rn[3]);
+      rp[4] = EscapeHtml(rp[0], rn[0], &rn[4]);
+      rp[5] = EscapeHtml(rp[3], rn[3], &rn[0]);
+      lastmod = GetZipCfileLastModified(zmap + cf);
+      localtime_r(&lastmod, &tm);
+      strftime(tb, sizeof(tb), "%Y-%m-%d %H:%M:%S %Z", &tm);
+      if (IsCompressionMethodSupported(
+              ZIP_LFILE_COMPRESSIONMETHOD(zmap + lf)) &&
+          IsAcceptablePath(path, pathlen)) {
+        Append("%-*.*s %s  %0*o %4s  %,*ld  %'s\r\n",
+               rn[2], rp[2], w[0], rn[4], rp[4], tb, w[1],
+               GetZipCfileMode(zmap + cf), DescribeCompressionRatio(rb, lf),
+               w[2], GetZipLfileUncompressedSize(zmap + lf), rp[5]);
+      } else {
+        Append("%-*.*s %s  %0*o %4s  %,*ld  %'s\r\n", w[0], rn[4], rp[4], tb,
+               w[1], GetZipCfileMode(zmap + cf),
+               DescribeCompressionRatio(rb, lf), w[2],
+               GetZipLfileUncompressedSize(zmap + lf), rp[5]);
+      }
+      free(rp[5]);
+      free(rp[4]);
+      free(rp[3]);
+      free(rp[2]);
+      free(rp[1]);
+      free(rp[0]);
+    }
+    free(path);
+  }
+  Append("
\r\n"); + p = SetStatus(200, "OK"); + p = AppendContentType(p, "text/html"); + p = AppendCache(p, 0); + return CommitOutput(p); +} + +static const char *MergeNames(const char *a, const char *b) { + return FreeLater(xasprintf("%s.ru_utime", a)); +} + +static void AppendLong1(const char *a, long x) { + if (x) Append("%s: %ld\r\n", a, x); +} + +static void AppendLong2(const char *a, const char *b, long x) { + if (x) Append("%s.%s: %ld\r\n", a, b, x); +} + +static void AppendTimeval(const char *a, struct timeval *tv) { + AppendLong2(a, "tv_sec", tv->tv_sec); + AppendLong2(a, "tv_usec", tv->tv_usec); +} + +static void AppendRusage(const char *a, struct rusage *ru) { + AppendTimeval(MergeNames(a, "ru_utime"), &ru->ru_utime); + AppendTimeval(MergeNames(a, "ru_stime"), &ru->ru_stime); + AppendLong2(a, "ru_maxrss", ru->ru_maxrss); + AppendLong2(a, "ru_ixrss", ru->ru_ixrss); + AppendLong2(a, "ru_idrss", ru->ru_idrss); + AppendLong2(a, "ru_isrss", ru->ru_isrss); + AppendLong2(a, "ru_minflt", ru->ru_minflt); + AppendLong2(a, "ru_majflt", ru->ru_majflt); + AppendLong2(a, "ru_nswap", ru->ru_nswap); + AppendLong2(a, "ru_inblock", ru->ru_inblock); + AppendLong2(a, "ru_oublock", ru->ru_oublock); + AppendLong2(a, "ru_msgsnd", ru->ru_msgsnd); + AppendLong2(a, "ru_msgrcv", ru->ru_msgrcv); + AppendLong2(a, "ru_nsignals", ru->ru_nsignals); + AppendLong2(a, "ru_nvcsw", ru->ru_nvcsw); + AppendLong2(a, "ru_nivcsw", ru->ru_nivcsw); +} + +static char *ServeStatusz(void) { + char *p; + LockInc(&shared->statuszrequests); + if (msg.method != kHttpGet && msg.method != kHttpHead) return BadMethod(); + AppendLong1("pid", getpid()); + AppendLong1("ppid", getppid()); + AppendLong1("now", nowl()); + AppendLong1("nowish", shared->nowish); + AppendLong1("heartless", heartless); + AppendLong1("gmtoff", gmtoff); + AppendLong1("CLK_TCK", CLK_TCK); + AppendLong1("startserver", startserver); + AppendLong1("lastmeltdown", shared->lastmeltdown); + AppendLong1("workers", shared->workers); + AppendLong1("assets.n", assets.n); + AppendLong1("accepterrors", shared->accepterrors); + AppendLong1("acceptinterrupts", shared->acceptinterrupts); + AppendLong1("acceptresets", shared->acceptresets); + AppendLong1("badlengths", shared->badlengths); + AppendLong1("badmessages", shared->badmessages); + AppendLong1("badmethods", shared->badmethods); + AppendLong1("badranges", shared->badranges); + AppendLong1("closeerrors", shared->closeerrors); + AppendLong1("compressedresponses", shared->compressedresponses); + AppendLong1("connectionshandled", shared->connectionshandled); + AppendLong1("connectsrefused", shared->connectsrefused); + AppendLong1("continues", shared->continues); + AppendLong1("decompressedresponses", shared->decompressedresponses); + AppendLong1("deflates", shared->deflates); + AppendLong1("dropped", shared->dropped); + AppendLong1("dynamicrequests", shared->dynamicrequests); + AppendLong1("emfiles", shared->emfiles); + AppendLong1("enetdowns", shared->enetdowns); + AppendLong1("enfiles", shared->enfiles); + AppendLong1("enobufs", shared->enobufs); + AppendLong1("enomems", shared->enomems); + AppendLong1("enonets", shared->enonets); + AppendLong1("errors", shared->errors); + AppendLong1("expectsrefused", shared->expectsrefused); + AppendLong1("failedchildren", shared->failedchildren); + AppendLong1("forbiddens", shared->forbiddens); + AppendLong1("forkerrors", shared->forkerrors); + AppendLong1("frags", shared->frags); + AppendLong1("fumbles", shared->fumbles); + AppendLong1("http09", shared->http09); + AppendLong1("http10", shared->http10); + AppendLong1("http11", shared->http11); + AppendLong1("http12", shared->http12); + AppendLong1("hugepayloads", shared->hugepayloads); + AppendLong1("identityresponses", shared->identityresponses); + AppendLong1("inflates", shared->inflates); + AppendLong1("listingrequests", shared->listingrequests); + AppendLong1("loops", shared->loops); + AppendLong1("mapfails", shared->mapfails); + AppendLong1("maps", shared->maps); + AppendLong1("meltdowns", shared->meltdowns); + AppendLong1("messageshandled", shared->messageshandled); + AppendLong1("missinglengths", shared->missinglengths); + AppendLong1("netafrinic", shared->netafrinic); + AppendLong1("netanonymous", shared->netanonymous); + AppendLong1("netapnic", shared->netapnic); + AppendLong1("netapple", shared->netapple); + AppendLong1("netarin", shared->netarin); + AppendLong1("netatt", shared->netatt); + AppendLong1("netcogent", shared->netcogent); + AppendLong1("netcomcast", shared->netcomcast); + AppendLong1("netdod", shared->netdod); + AppendLong1("netford", shared->netford); + AppendLong1("netlacnic", shared->netlacnic); + AppendLong1("netloopback", shared->netloopback); + AppendLong1("netother", shared->netother); + AppendLong1("netprivate", shared->netprivate); + AppendLong1("netprudential", shared->netprudential); + AppendLong1("netripe", shared->netripe); + AppendLong1("nettestnet", shared->nettestnet); + AppendLong1("netusps", shared->netusps); + AppendLong1("notfounds", shared->notfounds); + AppendLong1("notmodifieds", shared->notmodifieds); + AppendLong1("openfails", shared->openfails); + AppendLong1("partialresponses", shared->partialresponses); + AppendLong1("payloaddisconnects", shared->payloaddisconnects); + AppendLong1("pipelinedrequests", shared->pipelinedrequests); + AppendLong1("precompressedresponses", shared->precompressedresponses); + AppendLong1("readerrors", shared->readerrors); + AppendLong1("readinterrupts", shared->readinterrupts); + AppendLong1("readresets", shared->readresets); + AppendLong1("readtimeouts", shared->readtimeouts); + AppendLong1("redirects", shared->redirects); + AppendLong1("reloads", shared->reloads); + AppendLong1("rewrites", shared->rewrites); + AppendLong1("serveroptions", shared->serveroptions); + AppendLong1("shutdowns", shared->shutdowns); + AppendLong1("slowloris", shared->slowloris); + AppendLong1("slurps", shared->slurps); + AppendLong1("statfails", shared->statfails); + AppendLong1("staticrequests", shared->staticrequests); + AppendLong1("stats", shared->stats); + AppendLong1("statuszrequests", shared->statuszrequests); + AppendLong1("synchronizationfailures", shared->synchronizationfailures); + AppendLong1("terminatedchildren", shared->terminatedchildren); + AppendLong1("thiscorruption", shared->thiscorruption); + AppendLong1("transfersrefused", shared->transfersrefused); + AppendLong1("urisrefused", shared->urisrefused); + AppendLong1("verifies", shared->verifies); + AppendLong1("writeerrors", shared->writeerrors); + AppendLong1("writeinterruputs", shared->writeinterruputs); + AppendLong1("writeresets", shared->writeresets); + AppendLong1("writetimeouts", shared->writetimeouts); + AppendRusage("server", &shared->server); + AppendRusage("children", &shared->children); + p = SetStatus(200, "OK"); + p = AppendContentType(p, "text/plain"); + p = AppendCache(p, 0); + return CommitOutput(p); +} + +static char *RedirectSlash(void) { + size_t n; + char *p, *e; + LockInc(&shared->redirects); + p = SetStatus(307, "Temporary Redirect"); + p = stpcpy(p, "Location: "); + e = EscapePath(url.path.p, url.path.n, &n); + p = mempcpy(p, e, n); + p = stpcpy(p, "/\r\n"); + free(e); + return p; +} + +static char *RoutePath(const char *, size_t); +static char *ServeIndex(const char *path, size_t pathlen) { + size_t i, n; + char *p, *q; + p = NULL; + for (i = 0; !p && i < ARRAYLEN(kIndexPaths); ++i) { + q = MergePaths(path, pathlen, kIndexPaths[i], strlen(kIndexPaths[i]), &n); + p = RoutePath(q, n); + free(q); + } + return p; +} + +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, + ".lua", 4); +} + +static char *GetLuaResponse(void) { + char *p; + if (!(p = luaheaderp)) { + p = SetStatus(200, "OK"); + p = AppendContentType(p, "text/html"); + } + return p; +} + +static char *ServeLua(struct Asset *a, const char *path, size_t pathlen) { + char *code; + effectivepath.p = path; + effectivepath.n = pathlen; + if ((code = FreeLater(LoadAsset(a, NULL)))) { + if (luaL_dostring(L, code) == LUA_OK) { + return CommitOutput(GetLuaResponse()); + } else { + WARNF("%s", lua_tostring(L, -1)); + lua_pop(L, 1); + } + } + return ServeError(500, "Internal Server Error"); +} + +static char *HandleAsset(struct Asset *a, const char *path, size_t pathlen) { +#ifndef STATIC + if (IsLua(a)) { + LockInc(&shared->dynamicrequests); + return ServeLua(a, path, pathlen); + } +#endif + if (msg.method == kHttpGet || msg.method == kHttpHead) { + LockInc(&shared->staticrequests); + return stpcpy(ServeAsset(a, path, pathlen), + "X-Content-Type-Options: nosniff\r\n"); + } else { + return BadMethod(); + } +} + +static char *HandleRedirect(struct Redirect *r) { + int code; + struct Asset *a; + if (!r->code && (a = GetAsset(r->location, strlen(r->location)))) { + LockInc(&shared->rewrites); + DEBUGF("rewriting to %`'s", r->location); + if (!HasString(&loops, r->location)) { + AddString(&loops, r->location); + return RoutePath(r->location, strlen(r->location)); + } else { + LockInc(&shared->loops); + return SetStatus(508, "Loop Detected"); + } + } else if (msg.version < 10) { + return ServeError(505, "HTTP Version Not Supported"); + } else { + LockInc(&shared->redirects); + code = r->code; + if (!code) code = 307; + DEBUGF("%d redirecting %`'s", code, r->location); + return AppendHeader(SetStatus(code, GetHttpReason(code)), "Location", + FreeLater(EncodeHttpHeaderValue(r->location, -1, 0))); + } +} + +static char *HandleFolder(const char *path, size_t pathlen) { + char *p; + if (url.path.n && url.path.p[url.path.n - 1] != '/' && + SlicesEqual(path, pathlen, url.path.p, url.path.n)) { + return RedirectSlash(); + } + if ((p = ServeIndex(path, pathlen))) { + return p; + } else { + LockInc(&shared->forbiddens); + LOGF("directory %`'.*s lacks index page", pathlen, path); + return ServeError(403, "Forbidden"); + } +} + +static char *RoutePath(const char *path, size_t pathlen) { + int m; + long r; + char *p; + struct Asset *a; + DEBUGF("RoutePath(%`'.*s)", pathlen, path); + if ((a = GetAsset(path, pathlen))) { + if ((m = GetMode(a)) & 0004) { + if (!S_ISDIR(m)) { + return HandleAsset(a, path, pathlen); + } else { + return HandleFolder(path, pathlen); + } + } else { + LockInc(&shared->forbiddens); + LOGF("asset %`'.*s %#o isn't readable", pathlen, path, m); + return ServeError(403, "Forbidden"); + } + } else if ((r = FindRedirect(path, pathlen)) != -1) { + return HandleRedirect(redirects.p + r); + } else { + return NULL; + } +} + +static char *RouteHost(const char *host, size_t hostlen, const char *path, + size_t pathlen) { + size_t hn; + char *hp, *p; + hn = 1 + hostlen + url.path.n; + hp = FreeLater(xmalloc(3 + 1 + hn)); + hp[0] = '/'; + mempcpy(mempcpy(hp + 1, host, hostlen), path, pathlen); + if ((p = RoutePath(hp, hn))) return p; + if (ParseIp(host, hostlen) == -1) { + if (hostlen > 4 && !memcmp(host, "www.", 4)) { + mempcpy(mempcpy(hp + 1, host + 4, hostlen - 4), path, pathlen); + if ((p = RoutePath(hp, hn - 4))) return p; + } else { + mempcpy(mempcpy(mempcpy(hp + 1, "www.", 4), host, hostlen), path, + pathlen); + if ((p = RoutePath(hp, hn + 4))) return p; + } + } + return NULL; +} + +static char *Route(const char *host, size_t hostlen, const char *path, + size_t pathlen) { + char *p; + if (hostlen && (p = RouteHost(host, hostlen, path, pathlen))) { + return p; + } + if (SlicesEqual(path, pathlen, "/", 1)) { + if ((p = ServeIndex("/", 1))) return p; + return ServeListing(); + } else if ((p = RoutePath(path, pathlen))) { + return p; + } else if (SlicesEqual(path, pathlen, "/statusz", 8)) { + return ServeStatusz(); + } else { + LockInc(&shared->notfounds); + return ServeError(404, "Not Found"); + } +} + static const char *LuaCheckPath(lua_State *L, int idx, size_t *pathlen) { const char *path; - path = luaL_checklstring(L, idx, pathlen); - if (!IsReasonablePath(path, *pathlen)) { - WARNF("bad path %`'.*s", *pathlen, path); - luaL_argerror(L, idx, "bad path"); - unreachable; + if (lua_isnoneornil(L, idx)) { + path = url.path.p; + *pathlen = url.path.n; + } else { + path = luaL_checklstring(L, idx, pathlen); + if (!IsReasonablePath(path, *pathlen)) { + WARNF("bad path %`'.*s", *pathlen, path); + luaL_argerror(L, idx, "bad path"); + unreachable; + } } return path; } +static const char *LuaCheckHost(lua_State *L, int idx, size_t *hostlen) { + const char *host; + if (lua_isnoneornil(L, idx)) { + host = url.host.p; + *hostlen = url.host.n; + } else { + host = luaL_checklstring(L, idx, hostlen); + if (!IsAcceptableHost(host, *hostlen)) { + WARNF("bad host %`'.*s", *hostlen, host); + luaL_argerror(L, idx, "bad host"); + unreachable; + } + } + return host; +} + +static int LuaServeListing(lua_State *L) { + luaheaderp = ServeListing(); + return 0; +} + +static int LuaServeStatusz(lua_State *L) { + luaheaderp = ServeStatusz(); + return 0; +} + static int LuaServeAsset(lua_State *L) { size_t pathlen; struct Asset *a; const char *path; path = LuaCheckPath(L, 1, &pathlen); - if (!(a = GetAsset(path, pathlen))) { - luaL_argerror(L, 1, "not found"); - unreachable; + if ((a = GetAsset(path, pathlen)) && !S_ISDIR(GetMode(a))) { + luaheaderp = ServeAsset(a, path, pathlen); + lua_pushboolean(L, true); + } else { + lua_pushboolean(L, false); } - if (S_ISDIR(GetMode(a))) { - luaL_argerror(L, 1, "is a directory"); - unreachable; - } - luaheaderp = ServeAsset(a, path, pathlen); - return 0; + return 1; +} + +static int LuaServeIndex(lua_State *L) { + size_t pathlen; + const char *path; + path = LuaCheckPath(L, 1, &pathlen); + lua_pushboolean(L, !!(luaheaderp = ServeIndex(path, pathlen))); + return 1; +} + +static int LuaRoutePath(lua_State *L) { + size_t pathlen; + const char *path; + path = LuaCheckPath(L, 1, &pathlen); + lua_pushboolean(L, !!(luaheaderp = RoutePath(path, pathlen))); + return 1; +} + +static int LuaRouteHost(lua_State *L) { + size_t hostlen, pathlen; + const char *host, *path; + host = LuaCheckHost(L, 1, &hostlen); + path = LuaCheckPath(L, 2, &pathlen); + lua_pushboolean(L, !!(luaheaderp = RouteHost(host, hostlen, path, pathlen))); + return 1; +} + +static int LuaRoute(lua_State *L) { + size_t hostlen, pathlen; + const char *host, *path; + host = LuaCheckHost(L, 1, &hostlen); + path = LuaCheckPath(L, 2, &pathlen); + lua_pushboolean(L, !!(luaheaderp = Route(host, hostlen, path, pathlen))); + return 1; } static int LuaRespond(lua_State *L, char *respond(unsigned, const char *)) { @@ -2141,16 +2695,12 @@ static int LuaCategorizeIp(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 LuaGetUrl(lua_State *L) { - LuaPushLatin1(L, inbuf.p + msg.uri.a, msg.uri.b - msg.uri.a); + char *p; + size_t n; + p = EncodeUrl(&url, &n); + lua_pushlstring(L, p, n); + free(p); return 1; } @@ -2253,6 +2803,14 @@ 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 char *FoldHeader(int h, size_t *z) { char *p; size_t i, n, m; @@ -2330,8 +2888,8 @@ static int LuaGetHeaders(lua_State *L) { static int LuaSetHeader(lua_State *L) { int h; - char *p; ssize_t rc; + char *p, *q; const char *key, *val, *eval; size_t i, keylen, vallen, evallen; key = luaL_checklstring(L, 1, &keylen); @@ -2346,16 +2904,12 @@ static int LuaSetHeader(lua_State *L) { luaL_argerror(L, 2, "invalid"); unreachable; } - if (!luaheaderp) { - p = SetStatus(200, "OK"); - } else { - while (luaheaderp - hdrbuf.p + keylen + 2 + evallen + 2 + 512 > hdrbuf.n) { - hdrbuf.n += hdrbuf.n >> 1; - p = xrealloc(hdrbuf.p, hdrbuf.n); - luaheaderp = p + (luaheaderp - hdrbuf.p); - hdrbuf.p = p; - } - p = luaheaderp; + p = GetLuaResponse(); + while (p - hdrbuf.p + keylen + 2 + evallen + 2 + 512 > hdrbuf.n) { + hdrbuf.n += hdrbuf.n >> 1; + q = xrealloc(hdrbuf.p, hdrbuf.n); + luaheaderp = p = q + (p - hdrbuf.p); + hdrbuf.p = q; } switch (h) { case kHttpConnection: @@ -2590,6 +3144,10 @@ static int LuaIsAcceptablePath(lua_State *L) { return LuaIsValid(L, IsAcceptablePath); } +static int LuaIsReasonablePath(lua_State *L) { + return LuaIsValid(L, IsReasonablePath); +} + static int LuaIsAcceptableHost(lua_State *L) { return LuaIsValid(L, IsAcceptableHost); } @@ -3156,6 +3714,7 @@ static const luaL_Reg kLuaFuncs[] = { {"IsLoopbackIp", LuaIsLoopbackIp}, // {"IsPrivateIp", LuaIsPrivateIp}, // {"IsPublicIp", LuaIsPublicIp}, // + {"IsReasonablePath", LuaIsReasonablePath}, // {"IsValidHttpToken", LuaIsValidHttpToken}, // {"LaunchBrowser", LuaLaunchBrowser}, // {"LoadAsset", LuaLoadAsset}, // @@ -3172,8 +3731,14 @@ static const luaL_Reg kLuaFuncs[] = { {"ProgramPort", LuaProgramPort}, // {"ProgramRedirect", LuaProgramRedirect}, // {"ProgramTimeout", LuaProgramTimeout}, // + {"Route", LuaRoute}, // + {"RouteHost", LuaRouteHost}, // + {"RoutePath", LuaRoutePath}, // {"ServeAsset", LuaServeAsset}, // {"ServeError", LuaServeError}, // + {"ServeIndex", LuaServeIndex}, // + {"ServeListing", LuaServeListing}, // + {"ServeStatusz", LuaServeStatusz}, // {"SetHeader", LuaSetHeader}, // {"SetLogLevel", LuaSetLogLevel}, // {"SetStatus", LuaSetStatus}, // @@ -3226,7 +3791,10 @@ static void LuaInit(void) { LuaSetConstant(L, "kLogWarn", kLogWarn); LuaSetConstant(L, "kLogError", kLogError); LuaSetConstant(L, "kLogFatal", kLogFatal); - if (!LuaRun("/.init.lua")) { + if (LuaRun("/.init.lua")) { + hasluaglobalhandler = !!lua_getglobal(L, "OnHttpRequest"); + lua_pop(L, 1); + } else { DEBUGF("no /.init.lua defined"); } #endif @@ -3254,75 +3822,6 @@ static void HandleHeartbeat(void) { #endif } -static char *ServeLua(struct Asset *a, const char *path, size_t pathlen) { - char *p, *code; - luaheaderp = NULL; - effectivepath.p = path; - effectivepath.n = pathlen; - if ((code = FreeLater(LoadAsset(a, NULL)))) { - if (luaL_dostring(L, code) == LUA_OK) { - if (!(p = luaheaderp)) { - p = SetStatus(200, "OK"); - p = AppendContentType(p, "text/html"); - } - return CommitOutput(p); - } else { - WARNF("%s %s", DescribeClient(), lua_tostring(L, -1)); - lua_pop(L, 1); /* remove message */ - connectionclose = true; - } - } - return ServeError(500, "Internal Server Error"); -} - -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, - ".lua", 4); -} - -static char *BadMethod(void) { - LockInc(&shared->badmethods); - return stpcpy(ServeError(405, "Method Not Allowed"), "Allow: GET, HEAD\r\n"); -} - -static char *HandleAsset(struct Asset *a, const char *path, size_t pathlen) { -#ifndef STATIC - if (IsLua(a)) { - LockInc(&shared->dynamicrequests); - return ServeLua(a, path, pathlen); - } -#endif - if (msg.method == kHttpGet || msg.method == kHttpHead) { - LockInc(&shared->staticrequests); - return stpcpy(ServeAsset(a, path, pathlen), - "X-Content-Type-Options: nosniff\r\n"); - } else { - return BadMethod(); - } -} - -static char *HandleRedirect(struct Redirect *r) { - int code; - struct Asset *a; - if (!r->code && (a = GetAsset(r->location, strlen(r->location)))) { - LockInc(&shared->rewrites); - DEBUGF("rewriting to %`'s", r->location); - return HandleAsset(a, r->location, strlen(r->location)); - } else if (msg.version < 10) { - return ServeError(505, "HTTP Version Not Supported"); - } else { - LockInc(&shared->redirects); - code = r->code; - if (!code) code = 307; - DEBUGF("%d redirecting %`'s", code, r->location); - return AppendHeader(SetStatus(code, GetHttpReason(code)), "Location", - FreeLater(EncodeHttpHeaderValue(r->location, -1, 0))); - } -} - static void LogMessage(const char *d, const char *s, size_t n) { size_t n2, n3; char *s2, *s3; @@ -3384,41 +3883,6 @@ Content-Length: 0\r\n\ \r\n"); } -static void SendWarmupRequests(void) { - int pid, i; - struct sockaddr_in addr; - static const char kWarmup[] = "\ -OPTIONS * HTTP/1.1\n\ -User-Agent: redbean/0.4\n\ -\n\ -GET /statusz HTTP/1.1\n\ -User-Agent: redbean/0.4\n\ -\n"; - if (uniprocess) return; - switch (fork()) { - default: - warmups = 2; - ++shared->workers; - break; - case 0: - DEBUGF("sending warmup requests"); - memcpy(&addr, &serveraddr, sizeof(addr)); - if (addr.sin_addr.s_addr == htonl(INADDR_ANY)) { - addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - } - for (i = 0; i < 2; ++i) { - client = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - connect(client, &addr, sizeof(addr)); - write(client, kWarmup, sizeof(kWarmup) - 1); - read(client, inbuf.p, inbuf.n); - close(client); - } - _exit(0); - case -1: - break; - } -} - static void LogClose(const char *reason) { if (amtread || meltdown || killed) { LockInc(&shared->fumbles); @@ -3438,27 +3902,6 @@ static const char *DescribeClose(void) { return "destroyed"; } -static const char *DescribeCompressionRatio(char rb[8], uint64_t lf) { - long percent; - if (ZIP_LFILE_COMPRESSIONMETHOD(zmap + lf) == kZipCompressionNone) { - return "n/a"; - } else { - percent = lround(100 - (double)GetZipLfileCompressedSize(zmap + lf) / - GetZipLfileUncompressedSize(zmap + lf) * 100); - sprintf(rb, "%ld%%", MIN(999, MAX(-999, percent))); - return rb; - } -} - -static int GetDecimalWidth(int x) { - int w = x ? ceil(log10(x)) : 1; - return w + (w - 1) / 3; -} - -static int GetOctalWidth(int x) { - return !x ? 1 : x < 8 ? 2 : 1 + bsr(x) / 3; -} - static void RecordNetworkOrigin(void) { uint32_t ip; GetRemoteAddr(&ip, 0); @@ -3520,279 +3963,6 @@ static void RecordNetworkOrigin(void) { } } -static const char *MergeNames(const char *a, const char *b) { - return FreeLater(xasprintf("%s.ru_utime", a)); -} - -static void AppendLong1(const char *a, long x) { - if (x) Append("%s: %ld\r\n", a, x); -} - -static void AppendLong2(const char *a, const char *b, long x) { - if (x) Append("%s.%s: %ld\r\n", a, b, x); -} - -static void AppendTimeval(const char *a, struct timeval *tv) { - AppendLong2(a, "tv_sec", tv->tv_sec); - AppendLong2(a, "tv_usec", tv->tv_usec); -} - -static void AppendRusage(const char *a, struct rusage *ru) { - AppendTimeval(MergeNames(a, "ru_utime"), &ru->ru_utime); - AppendTimeval(MergeNames(a, "ru_stime"), &ru->ru_stime); - AppendLong2(a, "ru_maxrss", ru->ru_maxrss); - AppendLong2(a, "ru_ixrss", ru->ru_ixrss); - AppendLong2(a, "ru_idrss", ru->ru_idrss); - AppendLong2(a, "ru_isrss", ru->ru_isrss); - AppendLong2(a, "ru_minflt", ru->ru_minflt); - AppendLong2(a, "ru_majflt", ru->ru_majflt); - AppendLong2(a, "ru_nswap", ru->ru_nswap); - AppendLong2(a, "ru_inblock", ru->ru_inblock); - AppendLong2(a, "ru_oublock", ru->ru_oublock); - AppendLong2(a, "ru_msgsnd", ru->ru_msgsnd); - AppendLong2(a, "ru_msgrcv", ru->ru_msgrcv); - AppendLong2(a, "ru_nsignals", ru->ru_nsignals); - AppendLong2(a, "ru_nvcsw", ru->ru_nvcsw); - AppendLong2(a, "ru_nivcsw", ru->ru_nivcsw); -} - -char *ServeStatusz(void) { - char *p; - if (msg.method != kHttpGet && msg.method != kHttpHead) return BadMethod(); - AppendLong1("pid", getpid()); - AppendLong1("ppid", getppid()); - AppendLong1("now", nowl()); - AppendLong1("nowish", shared->nowish); - AppendLong1("heartless", heartless); - AppendLong1("gmtoff", gmtoff); - AppendLong1("CLK_TCK", CLK_TCK); - AppendLong1("startserver", startserver); - AppendLong1("lastmeltdown", shared->lastmeltdown); - AppendLong1("workers", shared->workers); - AppendLong1("assets.n", assets.n); - AppendLong1("accepterrors", shared->accepterrors); - AppendLong1("acceptinterrupts", shared->acceptinterrupts); - AppendLong1("acceptresets", shared->acceptresets); - AppendLong1("badlengths", shared->badlengths); - AppendLong1("badmessages", shared->badmessages); - AppendLong1("badmethods", shared->badmethods); - AppendLong1("badranges", shared->badranges); - AppendLong1("closeerrors", shared->closeerrors); - AppendLong1("compressedresponses", shared->compressedresponses); - AppendLong1("connectionshandled", shared->connectionshandled); - AppendLong1("connectsrefused", shared->connectsrefused); - AppendLong1("continues", shared->continues); - AppendLong1("decompressedresponses", shared->decompressedresponses); - AppendLong1("deflates", shared->deflates); - AppendLong1("dropped", shared->dropped); - AppendLong1("dynamicrequests", shared->dynamicrequests); - AppendLong1("emfiles", shared->emfiles); - AppendLong1("enetdowns", shared->enetdowns); - AppendLong1("enfiles", shared->enfiles); - AppendLong1("enobufs", shared->enobufs); - AppendLong1("enomems", shared->enomems); - AppendLong1("enonets", shared->enonets); - AppendLong1("errors", shared->errors); - AppendLong1("expectsrefused", shared->expectsrefused); - AppendLong1("failedchildren", shared->failedchildren); - AppendLong1("forbiddens", shared->forbiddens); - AppendLong1("forkerrors", shared->forkerrors); - AppendLong1("frags", shared->frags); - AppendLong1("fumbles", shared->fumbles); - AppendLong1("http09", shared->http09); - AppendLong1("http10", shared->http10); - AppendLong1("http11", shared->http11); - AppendLong1("http12", shared->http12); - AppendLong1("hugepayloads", shared->hugepayloads); - AppendLong1("identityresponses", shared->identityresponses); - AppendLong1("inflates", shared->inflates); - AppendLong1("listingrequests", shared->listingrequests); - AppendLong1("loops", shared->loops); - AppendLong1("mapfails", shared->mapfails); - AppendLong1("maps", shared->maps); - AppendLong1("meltdowns", shared->meltdowns); - AppendLong1("messageshandled", shared->messageshandled); - AppendLong1("missinglengths", shared->missinglengths); - AppendLong1("netafrinic", shared->netafrinic); - AppendLong1("netanonymous", shared->netanonymous); - AppendLong1("netapnic", shared->netapnic); - AppendLong1("netapple", shared->netapple); - AppendLong1("netarin", shared->netarin); - AppendLong1("netatt", shared->netatt); - AppendLong1("netcogent", shared->netcogent); - AppendLong1("netcomcast", shared->netcomcast); - AppendLong1("netdod", shared->netdod); - AppendLong1("netford", shared->netford); - AppendLong1("netlacnic", shared->netlacnic); - AppendLong1("netloopback", shared->netloopback); - AppendLong1("netother", shared->netother); - AppendLong1("netprivate", shared->netprivate); - AppendLong1("netprudential", shared->netprudential); - AppendLong1("netripe", shared->netripe); - AppendLong1("nettestnet", shared->nettestnet); - AppendLong1("netusps", shared->netusps); - AppendLong1("notfounds", shared->notfounds); - AppendLong1("notmodifieds", shared->notmodifieds); - AppendLong1("openfails", shared->openfails); - AppendLong1("partialresponses", shared->partialresponses); - AppendLong1("payloaddisconnects", shared->payloaddisconnects); - AppendLong1("pipelinedrequests", shared->pipelinedrequests); - AppendLong1("precompressedresponses", shared->precompressedresponses); - AppendLong1("readerrors", shared->readerrors); - AppendLong1("readinterrupts", shared->readinterrupts); - AppendLong1("readresets", shared->readresets); - AppendLong1("readtimeouts", shared->readtimeouts); - AppendLong1("redirects", shared->redirects); - AppendLong1("reloads", shared->reloads); - AppendLong1("rewrites", shared->rewrites); - AppendLong1("serveroptions", shared->serveroptions); - AppendLong1("shutdowns", shared->shutdowns); - AppendLong1("slowloris", shared->slowloris); - AppendLong1("slurps", shared->slurps); - AppendLong1("statfails", shared->statfails); - AppendLong1("staticrequests", shared->staticrequests); - AppendLong1("stats", shared->stats); - AppendLong1("statuszrequests", shared->statuszrequests); - AppendLong1("synchronizationfailures", shared->synchronizationfailures); - AppendLong1("terminatedchildren", shared->terminatedchildren); - AppendLong1("thiscorruption", shared->thiscorruption); - AppendLong1("transfersrefused", shared->transfersrefused); - AppendLong1("urisrefused", shared->urisrefused); - AppendLong1("verifies", shared->verifies); - AppendLong1("writeerrors", shared->writeerrors); - AppendLong1("writeinterruputs", shared->writeinterruputs); - AppendLong1("writeresets", shared->writeresets); - AppendLong1("writetimeouts", shared->writetimeouts); - AppendRusage("server", &shared->server); - AppendRusage("children", &shared->children); - p = SetStatus(200, "OK"); - p = AppendContentType(p, "text/plain"); - p = AppendCache(p, 0); - return CommitOutput(p); -} - -char *ServeListing(void) { - long x; - ldiv_t y; - int w[3]; - struct tm tm; - const char *and; - int64_t lastmod; - uint64_t cf, lf; - struct rusage ru; - char *p, *q, *path; - char rb[8], tb[64], *rp[6]; - size_t i, n, pathlen, rn[6]; - if (msg.method != kHttpGet && msg.method != kHttpHead) return BadMethod(); - Append("\ -\r\n\ -\r\n\ -redbean zip listing\r\n\ -\r\n\ -

\r\n"); - AppendLogo(); - rp[0] = EscapeHtml(brand, -1, &rn[0]); - AppendData(rp[0], rn[0]); - free(rp[0]); - Append("


\r\n");
-  memset(w, 0, sizeof(w));
-  n = GetZipCdirRecords(cdir);
-  for (cf = GetZipCdirOffset(cdir); n--; cf += ZIP_CFILE_HDRSIZE(zmap + cf)) {
-    CHECK_EQ(kZipCfileHdrMagic, ZIP_CFILE_MAGIC(zmap + cf));
-    lf = GetZipCfileOffset(zmap + cf);
-    path = GetAssetPath(cf, &pathlen);
-    if (!IsHiddenPath(path)) {
-      w[0] = min(80, max(w[0], strwidth(path, 0) + 2));
-      w[1] = max(w[1], GetOctalWidth(GetZipCfileMode(zmap + cf)));
-      w[2] = max(w[2], GetDecimalWidth(GetZipLfileUncompressedSize(zmap + lf)));
-    }
-    free(path);
-  }
-  n = GetZipCdirRecords(cdir);
-  for (cf = GetZipCdirOffset(cdir); n--; cf += ZIP_CFILE_HDRSIZE(zmap + cf)) {
-    CHECK_EQ(kZipCfileHdrMagic, ZIP_CFILE_MAGIC(zmap + cf));
-    lf = GetZipCfileOffset(zmap + cf);
-    path = GetAssetPath(cf, &pathlen);
-    if (!IsHiddenPath(path)) {
-      rp[0] = VisualizeControlCodes(path, pathlen, &rn[0]);
-      rp[1] = EscapePath(path, pathlen, &rn[1]);
-      rp[2] = EscapeHtml(rp[1], rn[1], &rn[2]);
-      rp[3] = VisualizeControlCodes(ZIP_CFILE_COMMENT(zmap + cf),
-                                    strnlen(ZIP_CFILE_COMMENT(zmap + cf),
-                                            ZIP_CFILE_COMMENTSIZE(zmap + cf)),
-                                    &rn[3]);
-      rp[4] = EscapeHtml(rp[0], rn[0], &rn[4]);
-      rp[5] = EscapeHtml(rp[3], rn[3], &rn[0]);
-      lastmod = GetZipCfileLastModified(zmap + cf);
-      localtime_r(&lastmod, &tm);
-      strftime(tb, sizeof(tb), "%Y-%m-%d %H:%M:%S %Z", &tm);
-      if (IsCompressionMethodSupported(
-              ZIP_LFILE_COMPRESSIONMETHOD(zmap + lf)) &&
-          IsAcceptablePath(path, pathlen)) {
-        Append("%-*.*s %s  %0*o %4s  %,*ld  %'s\r\n",
-               rn[2], rp[2], w[0], rn[4], rp[4], tb, w[1],
-               GetZipCfileMode(zmap + cf), DescribeCompressionRatio(rb, lf),
-               w[2], GetZipLfileUncompressedSize(zmap + lf), rp[5]);
-      } else {
-        Append("%-*.*s %s  %0*o %4s  %,*ld  %'s\r\n", w[0], rn[4], rp[4], tb,
-               w[1], GetZipCfileMode(zmap + cf),
-               DescribeCompressionRatio(rb, lf), w[2],
-               GetZipLfileUncompressedSize(zmap + lf), rp[5]);
-      }
-      free(rp[5]);
-      free(rp[4]);
-      free(rp[3]);
-      free(rp[2]);
-      free(rp[1]);
-      free(rp[0]);
-    }
-    free(path);
-  }
-  Append("
\r\n"); - p = SetStatus(200, "OK"); - p = AppendContentType(p, "text/html"); - p = AppendCache(p, 0); - return CommitOutput(p); -} - char *ServeServerOptions(void) { char *p; p = SetStatus(200, "OK"); @@ -3805,7 +3975,6 @@ char *ServeServerOptions(void) { #endif return p; } - static bool HasAtMostThisElement(int h, const char *s) { size_t i, n; struct HttpRequestHeader *x; @@ -3917,93 +4086,19 @@ static void ParseRequestParameters(void) { FreeLater(url.params.p); } -static char *RedirectSlash(void) { - size_t n; - char *p, *e; - if (url.path.n && url.path.p[url.path.n - 1] != '/') { - LockInc(&shared->redirects); - p = SetStatus(307, "Temporary Redirect"); - p = stpcpy(p, "Location: "); - e = EscapePath(url.path.p, url.path.n, &n); - p = mempcpy(p, e, n); - p = stpcpy(p, "/\r\n"); - free(e); - return p; +static char *OnHttpRequest(void) { + effectivepath.p = url.path.p; + effectivepath.n = url.path.n; + lua_getglobal(L, "OnHttpRequest"); + if (lua_pcall(L, 0, 0, 0) == LUA_OK) { + return CommitOutput(GetLuaResponse()); } else { - LockInc(&shared->loops); - return SetStatus(508, "Loop Detected"); + WARNF("%s", lua_tostring(L, -1)); + lua_pop(L, 1); + return ServeError(500, "Internal Server Error"); } } -static char *TryPath(const char *, size_t); -static char *TryIndex(const char *path, size_t pathlen) { - size_t i, n; - char *p, *q; - p = NULL; - for (i = 0; !p && i < ARRAYLEN(kIndexPaths); ++i) { - q = MergePaths(path, pathlen, kIndexPaths[i], strlen(kIndexPaths[i]), &n); - p = TryPath(q, n); - free(q); - } - return p; -} - -static char *TryPath(const char *path, size_t pathlen) { - int m; - long r; - char *p; - struct Asset *a; - DEBUGF("TryPath(%`'.*s)", pathlen, path); - if ((a = GetAsset(path, pathlen))) { - if ((m = GetMode(a)) & 0004) { - if (S_ISDIR(m)) { - if (path[pathlen - 1] == '/') { - if ((p = TryIndex(path, pathlen))) { - return p; - } else { - LockInc(&shared->forbiddens); - LOGF("directory %`'.*s lacks index page", pathlen, path); - return ServeError(403, "Forbidden"); - } - } else { - return RedirectSlash(); - } - } else { - return HandleAsset(a, path, pathlen); - } - } else { - LockInc(&shared->forbiddens); - LOGF("asset %`'.*s %#o isn't readable", pathlen, path, m); - return ServeError(403, "Forbidden"); - } - } else if ((r = FindRedirect(path, pathlen)) != -1) { - return HandleRedirect(redirects.p + r); - } else { - return NULL; - } -} - -static char *TryHost(const char *host, size_t hostlen) { - size_t hn; - char *hp, *p; - hn = 1 + hostlen + url.path.n; - hp = FreeLater(xmalloc(3 + 1 + hn)); - hp[0] = '/'; - mempcpy(mempcpy(hp + 1, host, hostlen), url.path.p, url.path.n); - if ((p = TryPath(hp, hn))) return p; - if (ParseIp(host, hostlen) == -1) { - if (hostlen > 4 && !memcmp(host, "www.", 4)) { - mempcpy(mempcpy(hp + 1, host + 4, hostlen - 4), url.path.p, url.path.n); - if ((p = TryPath(hp, hn - 4))) return p; - } else { - mempcpy(mempcpy(mempcpy(hp + 1, "www.", 4), host, hostlen), url.path.p, - url.path.n); - if ((p = TryPath(hp, hn + 4))) return p; - } - } - return NULL; -} - static char *HandleRequest(void) { char *p; if (msg.version < 10) { @@ -4051,22 +4146,8 @@ static char *HandleRequest(void) { FreeLater(EncodeUrl(&url, 0)), HeaderLength(kHttpReferer), HeaderData(kHttpReferer), HeaderLength(kHttpUserAgent), HeaderData(kHttpUserAgent)); - if (url.host.n && (p = TryHost(url.host.p, url.host.n))) { - return p; - } - if (SlicesEqual(url.path.p, url.path.n, "/", 1)) { - if ((p = TryIndex("/", 1))) return p; - LockInc(&shared->listingrequests); - return ServeListing(); - } else if ((p = TryPath(url.path.p, url.path.n))) { - return p; - } else if (SlicesEqual(url.path.p, url.path.n, "/statusz", 8)) { - LockInc(&shared->statuszrequests); - return ServeStatusz(); - } else { - LockInc(&shared->notfounds); - return ServeError(404, "Not Found"); - } + if (hasluaglobalhandler) return OnHttpRequest(); + return Route(url.host.p, url.host.n, url.path.p, url.path.n); } static bool HandleMessage(void) { @@ -4153,11 +4234,13 @@ static bool HandleMessage(void) { static void InitRequest(void) { frags = 0; + gzipped = 0; + branded = 0; + content = 0; msgsize = 0; + loops.n = 0; outbuf.n = 0; - content = NULL; - gzipped = false; - branded = false; + luaheaderp = 0; contentlength = 0; InitHttpRequest(&msg); } @@ -4269,9 +4352,6 @@ static void HandleConnection(void) { if (uniprocess) { pid = -1; connectionclose = true; - } else if (warmups) { - --warmups; - pid = -1; } else { switch ((pid = fork())) { case 0: @@ -4445,7 +4525,6 @@ void RedBean(int argc, char *argv[], const char *prog) { hdrbuf.p = xvalloc(hdrbuf.n); inbuf.n = 64 * 1024; inbuf.p = xvalloc(inbuf.n); - SendWarmupRequests(); while (!terminated) { if (zombied) { ReapZombies();