Have redbean show zip listing as default / handler

If an "index.lua" or "index.html" doesn't exist in zip file or the
filesystem, and no redirects have been defined for it either, then
redbean will render a listing of the zip central directory content
only if the request uri points to the root path.
This commit is contained in:
Justine Tunney 2021-03-29 01:22:49 -07:00
parent ae300d0c40
commit 1753b669cf
10 changed files with 436 additions and 112 deletions

View file

@ -29,23 +29,21 @@
* @see xasprintf() for a better API
*/
int(vasprintf)(char **strp, const char *fmt, va_list va) {
/*
* This implementation guarantees the smallest possible allocation,
* using an optimistic approach w/o changing asymptotic complexity.
*/
size_t size = 32;
if ((*strp = malloc(size))) {
int wrote;
char *p;
size_t size;
va_list va2;
if ((*strp = malloc((size = 512)))) {
va_copy(va2, va);
int wrote = (vsnprintf)(*strp, size, fmt, va);
wrote = (vsnprintf)(*strp, size, fmt, va);
if (wrote == -1) return -1;
if (wrote <= size - 1) {
if (wrote < size) {
if ((p = realloc(*strp, wrote + 1))) *strp = p;
return wrote;
} else {
size = wrote + 1;
char *buf2 = realloc(*strp, size);
if (buf2) {
*strp = buf2;
if ((p = realloc(*strp, size))) {
*strp = p;
wrote = (vsnprintf)(*strp, size, fmt, va2);
assert(wrote == size - 1);
return wrote;

View file

@ -22,15 +22,29 @@
#include "libc/sysv/consts/sock.h"
int sys_socket(int family, int type, int protocol) {
int rc, olderr, modernflags;
static bool once, demodernize;
int sock, olderr;
if (!once && (type & (SOCK_CLOEXEC | SOCK_NONBLOCK))) {
if (IsXnu()) {
demodernize = true;
once = true;
} else {
olderr = errno;
rc = __sys_socket(family, type, protocol);
if ((SupportsLinux() || SupportsXnu()) &&
(rc == -1 && errno == EINVAL /* rhel5 behavior */) &&
(modernflags = (type & (SOCK_CLOEXEC | SOCK_NONBLOCK)))) {
if ((sock = __sys_socket(family, type, protocol)) != -1) {
once = true;
return sock;
} else {
errno = olderr;
rc = __fixupnewsockfd(__sys_socket(family, type & ~modernflags, protocol),
modernflags);
demodernize = true;
once = true;
}
}
}
if (!demodernize) {
return __sys_socket(family, type, protocol);
} else {
return __fixupnewsockfd(
__sys_socket(family, type & ~(SOCK_CLOEXEC | SOCK_NONBLOCK), protocol),
type);
}
return rc;
}

View file

@ -16,8 +16,8 @@ GET, kHttpGet
HEAD, kHttpHead
POST, kHttpPost
PUT, kHttpPut
CONNECT, kHttpConnect
OPTIONS, kHttpOptions
CONNECT, kHttpConnect
TRACE, kHttpTrace
COPY, kHttpCopy
LOCK, kHttpLock

View file

@ -8,8 +8,8 @@
#define kHttpPost 2
#define kHttpPut 3
#define kHttpDelete 4
#define kHttpConnect 5
#define kHttpOptions 6
#define kHttpOptions 5
#define kHttpConnect 6
#define kHttpTrace 7
#define kHttpCopy 8
#define kHttpLock 9
@ -89,8 +89,7 @@ struct HttpRequestSlice {
};
struct HttpRequest {
int i, t, a;
int method;
int i, t, a, method;
struct HttpRequestSlice k;
struct HttpRequestSlice uri;
struct HttpRequestSlice version;

View file

@ -24,8 +24,8 @@ const char kHttpMethod[17][8] = {
"POST", //
"PUT", //
"DELETE", //
"CONNECT", //
"OPTIONS", //
"CONNECT", //
"TRACE", //
"COPY", //
"LOCK", //

View file

@ -389,6 +389,7 @@ static void read_string (LexState *ls, int del, SemInfo *seminfo) {
int c; /* final character to be saved */
save_and_next(ls); /* keep '\\' for error messages */
switch (ls->current) {
case 'e': c = '\e'; goto read_save;
case 'a': c = '\a'; goto read_save;
case 'b': c = '\b'; goto read_save;
case 'f': c = '\f'; goto read_save;

View file

@ -1 +1,2 @@
-- special script called by main redbean process at startup
ProgramRedirect(0, '/favicon.ico', '/tool/net/redbean.ico')

View file

@ -1,2 +1,3 @@
-- redbean xhr handler demo
SetHeader('Vary', 'X-Custom-Header')
SetHeader('X-Custom-Header', 'hello ' .. GetHeader('x-custom-header'))

View file

@ -34,6 +34,7 @@
#include "libc/log/log.h"
#include "libc/macros.internal.h"
#include "libc/math.h"
#include "libc/mem/fmt.h"
#include "libc/mem/mem.h"
#include "libc/nexgen32e/bsf.h"
#include "libc/nexgen32e/bsr.h"
@ -99,16 +100,18 @@ FLAGS\n\
-z print port\n\
-m log messages\n\
-b log message bodies\n\
-k encourage keep-alive\n\
-D DIR serve assets from directory\n\
-c INT cache seconds\n\
-r /X=/Y redirect X to Y\n\
-R /X=/Y rewrite X to Y\n\
-l ADDR listen ip [default 0.0.0.0]\n\
-p PORT listen port [default 8080]\n\
-L PATH log file location\n\
-P PATH pid file location\n\
-U INT daemon set user id\n\
-G INT daemon set group id\n\
-B STR changes server header\n\
-B STR changes brand\n\
\n\
FEATURES\n\
\n\
@ -149,10 +152,6 @@ USAGE\n\
connection processes, which grow to whatever number your system\n\
limits and tcp stack configuration allow. If fork() should fail\n\
then redbean starts shutting idle connections down.\n\
\n\
Redirects emit a 307 response unless the location exists in\n\
the zip directory, in which case it transparently rewrites.\n\
\n\
\n"
#define HASH_LOAD_FACTOR /* 1. / */ 4
@ -281,6 +280,7 @@ static struct Freelist {
static struct Redirects {
size_t n;
struct Redirect {
int code;
const char *path;
size_t pathlen;
const char *location;
@ -301,12 +301,17 @@ static struct Assets {
} * p;
} assets;
static struct Shared {
int workers; //
} * shared;
static bool killed;
static bool istext;
static bool zombied;
static bool gzipped;
static bool branded;
static bool meltdown;
static bool unbranded;
static bool heartless;
static bool printport;
static bool heartbeat;
@ -319,6 +324,7 @@ static bool logmessages;
static bool checkedmethod;
static bool connectionclose;
static bool keyboardinterrupt;
static bool encouragekeepalive;
static int frags;
static int gmtoff;
@ -340,6 +346,8 @@ static size_t msgsize;
static size_t amtread;
static char *luaheaderp;
struct Strings stagedirs;
static const char *sauce;
static const char *brand;
static const char *pidpath;
static const char *logpath;
static int64_t programtime;
@ -348,6 +356,7 @@ static int64_t cacheseconds;
static uint8_t gzip_footer[8];
static const char *serverheader;
static struct Buffer logo;
static struct Buffer inbuf;
static struct Buffer hdrbuf;
static struct Buffer outbuf;
@ -355,6 +364,7 @@ static struct Request request;
static long double nowish;
static long double startread;
static long double lastmeltdown;
static long double startrequest;
static long double startconnection;
static struct sockaddr_in serveraddr;
@ -436,15 +446,17 @@ static long FindRedirect(const char *path, size_t n) {
return -1;
}
static void AddRedirect(const char *arg) {
static void ProgramRedirect(int code, const char *src, const char *dst) {
long i, j;
const char *p;
struct Redirect r;
CHECK_NOTNULL((p = strchr(arg, '=')));
CHECK_GT(p - arg, 0);
r.path = arg;
r.pathlen = p - arg;
r.location = p + 1;
if (code && code != 301 && code != 302 && code != 307 && code != 308) {
fprintf(stderr, "error: unsupported redirect code %d\n", code);
exit(1);
}
r.code = code;
r.path = strdup(src);
r.pathlen = strlen(src);
r.location = strdup(dst);
if ((i = FindRedirect(r.path, r.pathlen)) != -1) {
redirects.p[i] = r;
} else {
@ -463,6 +475,17 @@ static void AddRedirect(const char *arg) {
}
}
static void ProgramRedirectArg(int code, const char *arg) {
char *s;
const char *p;
if (!(p = strchr(arg, '='))) {
fprintf(stderr, "error: redirect arg missing '='\n");
exit(1);
}
ProgramRedirect(code, (s = strndup(arg, p - arg)), p + 1);
free(s);
}
static int CompareInts(const uint64_t x, uint64_t y) {
return x > y ? 1 : x < y ? -1 : 0;
}
@ -512,15 +535,32 @@ static void DescribeAddress(char buf[32], const struct sockaddr_in *addr) {
*p = '\0';
}
static void ProgramBrand(const char *s) {
free(brand);
free(serverheader);
brand = strdup(s);
if (!strstr(s, "redbean")) unbranded = true;
if (!(serverheader = EncodeHttpHeaderValue(brand, -1, 0))) {
fprintf(stderr, "error: brand isn't latin1 encodable: %`'s", brand);
exit(1);
}
}
static void ProgramCache(long x) {
cacheseconds = x;
}
static void ProgramPort(long x) {
serveraddr.sin_port = htons(x);
}
static void SetDefaults(void) {
cacheseconds = -1;
serverheader = "redbean/0.2";
ProgramBrand("redbean/0.2");
ProgramCache(-1);
ProgramPort(DEFAULT_PORT);
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(DEFAULT_PORT);
serveraddr.sin_addr.s_addr = INADDR_ANY;
AddRedirect("/=/tool/net/redbean.html");
AddRedirect("/index.html=/tool/net/redbean.html");
AddRedirect("/favicon.ico=/tool/net/redbean.ico");
if (IsWindows()) uniprocess = true;
}
static wontreturn void PrintUsage(FILE *f, int rc) {
@ -548,7 +588,7 @@ static void AddStagingDirectory(const char *dirpath) {
static void GetOpts(int argc, char *argv[]) {
int opt;
while ((opt = getopt(argc, argv, "zhduvmbl:p:w:r:c:L:P:U:G:B:D:")) != -1) {
while ((opt = getopt(argc, argv, "zhduvmbl:p:w:r:R:c:L:P:U:G:B:D:")) != -1) {
switch (opt) {
case 'v':
__log_level++;
@ -568,23 +608,29 @@ static void GetOpts(int argc, char *argv[]) {
case 'z':
printport = true;
break;
case 'k':
encouragekeepalive = true;
break;
case 'r':
AddRedirect(optarg);
ProgramRedirectArg(307, optarg);
break;
case 'R':
ProgramRedirectArg(0, optarg);
break;
case 'D':
AddStagingDirectory(optarg);
break;
case 'c':
cacheseconds = strtol(optarg, NULL, 0);
ProgramCache(strtol(optarg, NULL, 0));
break;
case 'p':
CHECK_NE(0xFFFF, (serveraddr.sin_port = htons(parseport(optarg))));
ProgramPort(strtol(optarg, NULL, 0));
break;
case 'l':
CHECK_EQ(1, inet_pton(AF_INET, optarg, &serveraddr.sin_addr));
break;
case 'B':
serverheader = emptytonull(EncodeHttpHeaderValue(optarg, -1, 0));
ProgramBrand(optarg);
break;
case 'L':
logpath = optarg;
@ -631,14 +677,18 @@ static void Daemonize(void) {
}
static void OnWorkerExit(int pid, int ws) {
int w;
w = --shared->workers;
if (WIFEXITED(ws)) {
if (WEXITSTATUS(ws)) {
WARNF("worker %d exited with %d", pid, WEXITSTATUS(ws));
WARNF("worker %d exited with %d (%,d workers remain)", pid,
WEXITSTATUS(ws), w);
} else {
DEBUGF("worker %d exited", pid);
DEBUGF("worker %d exited (%,d workers remain)", pid, w);
}
} else {
WARNF("worker %d terminated with %s", pid, strsignal(WTERMSIG(ws)));
WARNF("worker %d terminated with %s (%,d workers remain)", pid,
strsignal(WTERMSIG(ws)), w);
}
}
@ -651,7 +701,9 @@ static void WaitAll(void) {
if (errno == ECHILD) break;
if (errno == EINTR) {
if (killed) {
WARNF("%s terminating harder", serveraddrstr);
killed = false;
terminated = false;
WARNF("%s redbean shall terminate harder", serveraddrstr);
LOGIFNEG1(kill(0, SIGTERM));
}
continue;
@ -803,11 +855,18 @@ static void *FreeLater(void *p) {
static void CollectGarbage(void) {
size_t i;
for (i = 0; i < freelist.n; ++i) free(freelist.p[i]);
free(freelist.p);
freelist.p = 0;
freelist.n = 0;
free(outbuf.p);
free(request.params.p);
DestroyHttpRequest(&msg);
}
static bool IsCompressionMethodSupported(int method) {
return method == kZipCompressionNone || method == kZipCompressionDeflate;
}
static void IndexAssets(void) {
int64_t lm;
struct Asset *p;
@ -819,6 +878,12 @@ static void IndexAssets(void) {
CHECK_EQ(kZipCdirHdrMagic, ZIP_CDIR_MAGIC(zdir));
for (cf = ZIP_CDIR_OFFSET(zdir); n--; cf += ZIP_CFILE_HDRSIZE(zmap + cf)) {
CHECK_EQ(kZipCfileHdrMagic, ZIP_CFILE_MAGIC(zmap + cf));
if (!IsCompressionMethodSupported(ZIP_CFILE_COMPRESSIONMETHOD(zmap + cf))) {
WARNF("don't understand zip compression method %d used by %`'.*s",
ZIP_CFILE_COMPRESSIONMETHOD(zmap + cf),
ZIP_CFILE_NAMESIZE(zmap + cf), ZIP_CFILE_NAME(zmap + cf));
continue;
}
hash = Hash(ZIP_CFILE_NAME(zmap + cf), ZIP_CFILE_NAMESIZE(zmap + cf));
step = 0;
do {
@ -1019,7 +1084,7 @@ static void ParseFragment(struct Parser *u, struct Buffer *h) {
u->q = u->p;
}
static bool ParseRequestUri(void) {
static void ParseRequestUri(void) {
struct Parser u;
u.i = 0;
u.c = 0;
@ -1032,8 +1097,6 @@ static bool ParseRequestUri(void) {
ParsePath(&u, &request.path);
if (u.c == '?') ParseParams(&u, &request.params);
if (u.c == '#') ParseFragment(&u, &request.fragment);
return u.i == u.size &&
IsAcceptableHttpRequestPath(request.path.p, request.path.n);
}
static void ParseFormParams(void) {
@ -1310,6 +1373,43 @@ static char *ServeAsset(struct Asset *a, const char *path, size_t pathlen) {
return p;
}
static void AppendData(const char *data, size_t size) {
outbuf.p = xrealloc(outbuf.p, outbuf.n + size);
memcpy(outbuf.p + outbuf.n, data, size);
outbuf.n += size;
}
static void AppendString(const char *s) {
AppendData(s, strlen(s));
}
static void AppendFmt(const char *fmt, ...) {
int size;
char *data;
va_list va;
data = NULL;
va_start(va, fmt);
CHECK_NE(-1, (size = vasprintf(&data, fmt, va)));
va_end(va);
AppendData(data, size);
free(data);
}
static char *CommitOutput(char *p) {
if (istext && outbuf.n >= 100 && ClientAcceptsGzip()) {
gzipped = true;
p = AppendHeader(p, "Content-Encoding", "gzip");
p = AppendHeader(p, "Vary", "Accept-Encoding");
WRITE32LE(gzip_footer + 0, crc32_z(0, outbuf.p, outbuf.n));
WRITE32LE(gzip_footer + 4, outbuf.n);
content = FreeLater(Deflate(outbuf.p, outbuf.n, &contentlength));
} else {
content = outbuf.p;
contentlength = outbuf.n;
}
return p;
}
static int LuaServeAsset(lua_State *L) {
size_t pathlen;
struct Asset *a;
@ -1389,11 +1489,6 @@ static int LuaGetPath(lua_State *L) {
return 1;
}
static int LuaGetFragment(lua_State *L) {
lua_pushlstring(L, request.fragment.p, request.fragment.n);
return 1;
}
static void LuaPushLatin1(lua_State *L, const char *s, size_t n) {
char *t;
size_t m;
@ -1588,9 +1683,7 @@ static int LuaWrite(lua_State *L) {
size_t size;
const char *data;
data = luaL_checklstring(L, 1, &size);
outbuf.p = xrealloc(outbuf.p, outbuf.n + size);
memcpy(outbuf.p + outbuf.n, data, size);
outbuf.n += size;
AppendData(data, size);
return 0;
}
@ -1697,11 +1790,58 @@ static int LuaCrc32c(lua_State *L) {
return 1;
}
static int LuaProgramPort(lua_State *L) {
ProgramPort(luaL_checkinteger(L, 1));
return 0;
}
static int LuaProgramCache(lua_State *L) {
ProgramCache(luaL_checkinteger(L, 1));
return 0;
}
static int LuaProgramBrand(lua_State *L) {
ProgramBrand(luaL_checkstring(L, 1));
return 0;
}
static int LuaProgramRedirect(lua_State *L) {
ProgramRedirect(luaL_checkinteger(L, 1), luaL_checkstring(L, 2),
luaL_checkstring(L, 3));
return 0;
}
static int LuaGetLogLevel(lua_State *L) {
lua_pushinteger(L, __log_level);
return 1;
}
static int LuaSetLogLevel(lua_State *L) {
__log_level = luaL_checkinteger(L, 1);
return 0;
}
static int LuaLog(lua_State *L) {
int level;
lua_Debug ar;
const char *msg, *module;
level = luaL_checkinteger(L, 1);
if (LOGGABLE(level)) {
msg = luaL_checkstring(L, 2);
lua_getstack(L, 1, &ar);
lua_getinfo(L, "nSl", &ar);
module = !strcmp(ar.name, "main") ? sauce : ar.name;
flogf(level, module, ar.currentline, NULL, "%s", msg);
}
return 0;
}
static void LuaRun(const char *path) {
struct Asset *a;
const char *code;
if ((a = LocateAsset(path, strlen(path)))) {
code = LoadAsset(a, NULL);
sauce = path + 1;
if (luaL_dostring(L, code) != LUA_OK) {
WARNF("%s %s", path, lua_tostring(L, -1));
}
@ -1723,9 +1863,9 @@ static const luaL_Reg kLuaFuncs[] = {
{"FormatHttpDateTime", LuaFormatHttpDateTime}, //
{"GetClientAddr", LuaGetClientAddr}, //
{"GetDate", LuaGetDate}, //
{"GetFragment", LuaGetFragment}, //
{"GetHeader", LuaGetHeader}, //
{"GetHeaders", LuaGetHeaders}, //
{"GetLogLevel", LuaGetLogLevel}, //
{"GetMethod", LuaGetMethod}, //
{"GetParam", LuaGetParam}, //
{"GetParams", LuaGetParams}, //
@ -1736,10 +1876,16 @@ static const luaL_Reg kLuaFuncs[] = {
{"GetVersion", LuaGetVersion}, //
{"HasParam", LuaHasParam}, //
{"LoadAsset", LuaLoadAsset}, //
{"Log", LuaLog}, //
{"ParseHttpDateTime", LuaParseHttpDateTime}, //
{"ProgramBrand", LuaProgramBrand}, //
{"ProgramCache", LuaProgramCache}, //
{"ProgramPort", LuaProgramPort}, //
{"ProgramRedirect", LuaProgramRedirect}, //
{"ServeAsset", LuaServeAsset}, //
{"ServeError", LuaServeError}, //
{"SetHeader", LuaSetHeader}, //
{"SetLogLevel", LuaSetLogLevel}, //
{"SetStatus", LuaSetStatus}, //
{"Write", LuaWrite}, //
{"bsf", LuaBsf}, //
@ -1749,7 +1895,7 @@ static const luaL_Reg kLuaFuncs[] = {
{"popcnt", LuaPopcnt}, //
};
static void LuaSetArgv(void) {
static void LuaSetArgv(lua_State *L) {
size_t i;
lua_newtable(L);
for (i = optind; i < __argc; ++i) {
@ -1759,6 +1905,11 @@ static void LuaSetArgv(void) {
lua_setglobal(L, "argv");
}
static void LuaSetConstant(lua_State *L, const char *s, long x) {
lua_pushinteger(L, x);
lua_setglobal(L, s);
}
static void LuaInit(void) {
size_t i;
L = luaL_newstate();
@ -1767,7 +1918,13 @@ static void LuaInit(void) {
lua_pushcfunction(L, kLuaFuncs[i].func);
lua_setglobal(L, kLuaFuncs[i].name);
}
LuaSetArgv();
LuaSetArgv(L);
LuaSetConstant(L, "kLogDebug", kLogDebug);
LuaSetConstant(L, "kLogVerbose", kLogVerbose);
LuaSetConstant(L, "kLogInfo", kLogInfo);
LuaSetConstant(L, "kLogWarn", kLogWarn);
LuaSetConstant(L, "kLogError", kLogError);
LuaSetConstant(L, "kLogFatal", kLogFatal);
LuaRun("/tool/net/.init.lua");
}
@ -1777,25 +1934,15 @@ static void LuaReload(void) {
static char *ServeLua(struct Asset *a) {
char *p;
outbuf.n = 0;
luaheaderp = NULL;
sauce = FreeLater(strndup(request.path.p + 1, request.path.n - 1));
if (luaL_dostring(L, FreeLater(LoadAsset(a, NULL))) == LUA_OK) {
if (!(p = luaheaderp)) {
p = SetStatus(200, "OK");
p = AppendContentType(p, "text/html");
}
if (outbuf.n) {
if (istext && outbuf.n >= 100 && ClientAcceptsGzip()) {
gzipped = true;
p = AppendHeader(p, "Content-Encoding", "gzip");
p = AppendHeader(p, "Vary", "Accept-Encoding");
WRITE32LE(gzip_footer + 0, crc32_z(0, outbuf.p, outbuf.n));
WRITE32LE(gzip_footer + 4, outbuf.n);
content = FreeLater(Deflate(outbuf.p, outbuf.n, &contentlength));
} else {
content = outbuf.p;
contentlength = outbuf.n;
}
p = CommitOutput(p);
}
return p;
} else {
@ -1828,24 +1975,25 @@ static char *HandleAsset(struct Asset *a, const char *path, size_t pathlen) {
}
static char *HandleRedirect(struct Redirect *r) {
char *p;
struct Asset *a;
if (!r->code) {
if ((a = LocateAsset(r->location, strlen(r->location)))) {
DEBUGF("%s %s %`'.*s rewritten %`'s", clientaddrstr,
kHttpMethod[msg.method], request.path.n, request.path.p,
r->location);
p = HandleAsset(a, r->location, strlen(r->location));
} else if (httpversion == 9) {
p = ServeError(505, "HTTP Version Not Supported");
return HandleAsset(a, r->location, strlen(r->location));
} else {
DEBUGF("%s %s %`'.*s redirecting %`'s", clientaddrstr,
kHttpMethod[msg.method], request.path.n, request.path.p,
return ServeError(505, "HTTP Version Not Supported");
}
} else if (httpversion == 9) {
return ServeError(505, "HTTP Version Not Supported");
} else {
DEBUGF("%s %s %`'.*s %d redirecting %`'s", clientaddrstr,
kHttpMethod[msg.method], request.path.n, request.path.p, r->code,
r->location);
p = SetStatus(307, "Temporary Redirect");
p = AppendHeader(p, "Location",
return AppendHeader(SetStatus(r->code, GetHttpReason(r->code)), "Location",
FreeLater(EncodeHttpHeaderValue(r->location, -1, 0)));
}
return p;
}
static void LogMessage(const char *d, const char *s, size_t n) {
@ -1930,6 +2078,140 @@ static const char *DescribeClose(void) {
return "destroyed";
}
static const char *DescribeCompressionRatio(char rb[8], uint32_t cf) {
long percent;
if (ZIP_CFILE_COMPRESSIONMETHOD(zmap + cf) == kZipCompressionNone) {
return "n/a";
} else {
percent = lround(100 - (double)ZIP_CFILE_COMPRESSEDSIZE(zmap + cf) /
ZIP_CFILE_UNCOMPRESSEDSIZE(zmap + cf) * 100);
sprintf(rb, "%ld%%", MIN(999, MAX(-999, percent)));
return rb;
}
}
static void LoadLogo(void) {
char *p;
size_t n;
struct Asset *a;
const char *logopath;
logopath = "/tool/net/redbean.png";
if ((a = LocateAsset(logopath, strlen(logopath)))) {
p = LoadAsset(a, &n);
logo.p = EncodeBase64(p, n, &logo.n);
free(p);
}
}
static char *ServeListing(void) {
char rb[8];
char tb[64];
int w, x, y;
struct tm tm;
int64_t lastmod;
uint32_t cf, lf;
size_t n, n1, n2;
char *p, *p1, *p2;
struct EscapeResult r[3];
AppendString("\
<!doctype html>\n\
<meta charset=\"utf-8\">\n\
<title>redbean zip listing</title>\n\
<style>\n\
html {\n\
color: #111;\n\
font-family: sans-serif;\n\
}\n\
a {\n\
text-decoration: none;\n\
}\n\
img {\n\
vertical-align: middle;\n\
}\n\
</style>\n\
<h1>\n");
if (logo.n) {
AppendString("<img src=\"data:image/png;base64,");
AppendData(logo.p, logo.n);
AppendString("\">\n");
}
r[0] = EscapeHtml(brand, strlen(brand));
AppendData(r[0].data, r[0].size);
free(r[0].data);
AppendString("</h1><hr><pre>\n");
w = x = 0;
n = ZIP_CDIR_RECORDS(zdir);
CHECK_EQ(kZipCdirHdrMagic, ZIP_CDIR_MAGIC(zdir));
for (cf = ZIP_CDIR_OFFSET(zdir); n--; cf += ZIP_CFILE_HDRSIZE(zmap + cf)) {
CHECK_EQ(kZipCfileHdrMagic, ZIP_CFILE_MAGIC(zmap + cf));
y = strnwidth(ZIP_CFILE_NAME(zmap + cf), ZIP_CFILE_NAMESIZE(zmap + cf), 0);
w = MIN(80, MAX(w, y + 2));
y = ZIP_CFILE_UNCOMPRESSEDSIZE(zmap + cf);
y = y ? llog10(y) : 1;
x = MIN(80, MAX(x, y + (y - 1) / 3 + 2));
}
n = ZIP_CDIR_RECORDS(zdir);
for (cf = ZIP_CDIR_OFFSET(zdir); n--; cf += ZIP_CFILE_HDRSIZE(zmap + cf)) {
CHECK_EQ(kZipCfileHdrMagic, ZIP_CFILE_MAGIC(zmap + cf));
p1 = ZIP_CFILE_NAME(zmap + cf);
n1 = ZIP_CFILE_NAMESIZE(zmap + cf);
p2 = malloc(1 + n1);
n2 = 1 + n1;
*p2 = '/';
memcpy(p2 + 1, p1, n1);
r[0] = EscapeHtml(p2, n2);
r[1] = EscapeUrlPath(p2, n2);
r[2] = EscapeHtml(r[1].data, r[1].size);
lastmod = GetLastModifiedZip(zmap + cf);
localtime_r(&lastmod, &tm);
strftime(tb, sizeof(tb), "%Y-%m-%d %H:%M:%S", &tm);
if (IsCompressionMethodSupported(ZIP_CFILE_COMPRESSIONMETHOD(zmap + cf)) &&
IsAcceptableHttpRequestPath(p2, n2)) {
AppendFmt("<a href=\"%.*s\">%-*.*s</a> %s %4s %,*ld\n", r[2].size,
r[2].data, w, r[0].size, r[0].data, tb,
DescribeCompressionRatio(rb, cf), x,
ZIP_CFILE_UNCOMPRESSEDSIZE(zmap + cf));
} else {
AppendFmt("%-*.*s %s %4s %,*ld\n", w, r[0].size, r[0].data, tb,
DescribeCompressionRatio(rb, cf), x,
ZIP_CFILE_UNCOMPRESSEDSIZE(zmap + cf));
}
free(r[2].data);
free(r[1].data);
free(r[0].data);
free(p2);
}
AppendString("</pre><hr><p>\n");
if (!unbranded) {
AppendString("\
this listing is for /\n\
when there's no /index.lua or /index.html in your zip<br>\n\
<a href=\"https://justine.lol/redbean/index.html\">redbean</a> is based on\n\
<a href=\"https://github.com/jart/cosmopolitan\">cosmopolitan</a> and\n\
<a href=\"https://justine.storage.googleapis.com/ape.html\">αcτµαlly\n\
pδrταblε εxεcµταblε</a><br>\n\
redbean is authored by Justine Tunney who's on\n\
<a href=\"https://github.com/jart\">GitHub</a> and\n\
<a href=\"https://twitter.com/JustineTunney\">Twitter</a><br>\n\
your redbean is ");
}
w = shared->workers;
AppendFmt("currently servicing %,d connection%s\n", w, w == 1 ? "" : "s");
p = SetStatus(200, "OK");
p = AppendCache(p, 0);
return CommitOutput(p);
}
static char *ServeServerOptions(void) {
char *p;
p = SetStatus(200, "OK");
p = AppendHeader(p, "Accept", "*/*");
p = AppendHeader(p, "Accept-Charset", "utf-8");
p = AppendHeader(p, "Allow", "GET, HEAD, POST, PUT, DELETE, OPTIONS");
VERBOSEF("%s pinged our server with OPTIONS *", clientaddrstr);
return p;
}
static char *HandleMessage(void) {
long r;
ssize_t cl, rc;
@ -1940,7 +2222,7 @@ static char *HandleMessage(void) {
if (httpversion > 101) {
return ServeError(505, "HTTP Version Not Supported");
}
if (msg.method > kHttpPost ||
if (msg.method > kHttpOptions ||
(HasHeader(kHttpTransferEncoding) &&
!HeaderEquals(kHttpTransferEncoding, "identity"))) {
return ServeError(501, "Not Implemented");
@ -1954,7 +2236,7 @@ static char *HandleMessage(void) {
if (HasHeader(kHttpContentLength)) {
return ServeError(400, "Bad Request");
} else if (msg.method != kHttpGet && msg.method != kHttpHead &&
msg.method != kHttpOptions) {
msg.method != kHttpDelete && msg.method != kHttpOptions) {
return ServeError(411, "Length Required");
} else {
cl = 0;
@ -1969,7 +2251,7 @@ static char *HandleMessage(void) {
}
while (amtread < need) {
if (++frags == 64) {
LogClose("payload fragged");
LogClose("payload fragged!");
return ServeError(408, "Request Timeout");
}
if ((rc = read(client, inbuf.p + amtread, inbuf.n - amtread)) != -1) {
@ -1996,7 +2278,12 @@ static char *HandleMessage(void) {
if (httpversion != 101 || IsConnectionClose()) {
connectionclose = true;
}
if (!ParseRequestUri()) {
ParseRequestUri();
if (msg.method == kHttpOptions &&
!CompareSlices(request.path.p, request.path.n, "*", 1)) {
return ServeServerOptions();
}
if (!IsAcceptableHttpRequestPath(request.path.p, request.path.n)) {
WARNF("%s could not parse request request %`'.*s", clientaddrstr,
msg.uri.b - msg.uri.a, inbuf.p + msg.uri.a);
connectionclose = true;
@ -2013,6 +2300,8 @@ static char *HandleMessage(void) {
return HandleAsset(a, request.path.p, request.path.n);
} else if ((r = FindRedirect(request.path.p, request.path.n)) != -1) {
return HandleRedirect(redirects.p + r);
} else if (!CompareSlices(request.path.p, request.path.n, "/", 1)) {
return ServeListing();
} else {
return ServeError(404, "Not Found");
}
@ -2051,6 +2340,8 @@ static bool HandleRequest(void) {
}
if (connectionclose) {
p = AppendHeader(p, "Connection", "close");
} else if (encouragekeepalive && httpversion >= 101) {
p = AppendHeader(p, "Connection", "keep-alive");
}
actualcontentlength = contentlength;
if (gzipped) {
@ -2091,6 +2382,8 @@ static bool HandleRequest(void) {
static void InitRequest(void) {
frags = 0;
msgsize = 0;
outbuf.p = 0;
outbuf.n = 0;
content = NULL;
gzipped = false;
branded = false;
@ -2118,7 +2411,7 @@ static void ProcessRequests(void) {
} else if (got) {
if (++frags == 32) {
SendTimeout();
LogClose("fragged");
LogClose("fragged!");
return;
} else {
DEBUGF("%s fragmented msg %,ld %,ld", clientaddrstr, amtread,
@ -2151,6 +2444,13 @@ static void ProcessRequests(void) {
}
}
static void EnterMeltdownMode(void) {
if (lastmeltdown && nowl() - lastmeltdown < 1) return;
WARNF("redbean is entering meltdown mode with %,d workers", shared->workers);
LOGIFNEG1(kill(0, SIGUSR2));
lastmeltdown = nowl();
}
static void ProcessConnection(void) {
int pid;
clientaddrsize = sizeof(clientaddr);
@ -2167,11 +2467,11 @@ static void ProcessConnection(void) {
connectionclose = false;
break;
case -1:
WARNF("redbean is entering meltdown mode");
LOGIFNEG1(kill(0, SIGUSR2));
EnterMeltdownMode();
SendServiceUnavailable();
/* fallthrough */
default:
++shared->workers;
close(client);
return;
}
@ -2195,12 +2495,20 @@ static void TuneServerSocket(void) {
LOGIFNEG1(setsockopt(server, IPPROTO_TCP, TCP_QUICKACK, &yes, sizeof(yes)));
}
void RedBean(void) {
void RedBean(int argc, char *argv[]) {
uint32_t addrsize;
if (IsWindows()) uniprocess = true;
gmtoff = GetGmtOffset();
CHECK_NE(MAP_FAILED,
(shared = mmap(NULL, ROUNDUP(sizeof(struct Shared), FRAMESIZE),
PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS,
-1, 0)));
OpenZip((const char *)getauxval(AT_EXECFN));
IndexAssets();
LoadLogo();
SetDefaults();
GetOpts(argc, argv);
LuaInit();
if (uniprocess) shared->workers = 1;
xsigaction(SIGINT, OnInt, 0, 0, 0);
xsigaction(SIGHUP, OnHup, 0, 0, 0);
xsigaction(SIGTERM, OnTerm, 0, 0, 0);
@ -2239,7 +2547,6 @@ void RedBean(void) {
inbuf.p = xvalloc(inbuf.n);
hdrbuf.n = 4 * 1024;
hdrbuf.p = xvalloc(hdrbuf.n);
LuaInit();
while (!terminated) {
if (zombied) {
ReapZombies();
@ -2256,7 +2563,7 @@ void RedBean(void) {
ProcessConnection();
}
}
VERBOSEF("%s terminated", serveraddrstr);
VERBOSEF("%s shutting down", serveraddrstr);
LOGIFNEG1(close(server));
if (!keyboardinterrupt) {
if (!killed) {
@ -2269,8 +2576,6 @@ void RedBean(void) {
int main(int argc, char *argv[]) {
showcrashreports();
SetDefaults();
GetOpts(argc, argv);
RedBean();
RedBean(argc, argv);
return 0;
}

View file

@ -1,6 +1,9 @@
-- redbean lua server page demo
local function main()
-- This is the best way to print data to the console or log file.
Log(kLogWarn, "hello from \e[1mlua\e[0m!")
-- This check is pedantic but might be good to have.
if GetMethod() ~= 'GET' and GetMethod() ~= 'HEAD' then
ServeError(405)
@ -19,6 +22,12 @@ local function main()
Write('<title>redbean</title>\n')
Write('<h1>redbean lua server page demo</h1>\n')
-- Prevent caching.
-- We need this because we're doing things like putting the client's
-- IP address in the response so we naturally don't want that cached
SetHeader('Expires', FormatHttpDateTime(GetDate()))
SetHeader('Cache-Control', 'no-cache, must-revalidate, max-age=0')
-- GetParams() returns an ordered list of Request-URI query params.
Write('<h3>request uri parameters</h3>\n')
params = GetParams()
@ -100,10 +109,6 @@ local function main()
Write('<dd>')
Write(GetServerAddr())
Write('\n')
Write('<dt>FormatHttpDateTime(GetDate())\n')
Write('<dd>')
Write(FormatHttpDateTime(GetDate()))
Write('\n')
Write('</dl>\n')
end