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

View file

@ -22,15 +22,29 @@
#include "libc/sysv/consts/sock.h" #include "libc/sysv/consts/sock.h"
int sys_socket(int family, int type, int protocol) { int sys_socket(int family, int type, int protocol) {
int rc, olderr, modernflags; static bool once, demodernize;
olderr = errno; int sock, olderr;
rc = __sys_socket(family, type, protocol); if (!once && (type & (SOCK_CLOEXEC | SOCK_NONBLOCK))) {
if ((SupportsLinux() || SupportsXnu()) && if (IsXnu()) {
(rc == -1 && errno == EINVAL /* rhel5 behavior */) && demodernize = true;
(modernflags = (type & (SOCK_CLOEXEC | SOCK_NONBLOCK)))) { once = true;
errno = olderr; } else {
rc = __fixupnewsockfd(__sys_socket(family, type & ~modernflags, protocol), olderr = errno;
modernflags); if ((sock = __sys_socket(family, type, protocol)) != -1) {
once = true;
return sock;
} else {
errno = olderr;
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 HEAD, kHttpHead
POST, kHttpPost POST, kHttpPost
PUT, kHttpPut PUT, kHttpPut
CONNECT, kHttpConnect
OPTIONS, kHttpOptions OPTIONS, kHttpOptions
CONNECT, kHttpConnect
TRACE, kHttpTrace TRACE, kHttpTrace
COPY, kHttpCopy COPY, kHttpCopy
LOCK, kHttpLock LOCK, kHttpLock

View file

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

View file

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

View file

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

View file

@ -1 +1,2 @@
-- special script called by main redbean process at startup -- 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 -- redbean xhr handler demo
SetHeader('Vary', 'X-Custom-Header')
SetHeader('X-Custom-Header', 'hello ' .. GetHeader('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/log/log.h"
#include "libc/macros.internal.h" #include "libc/macros.internal.h"
#include "libc/math.h" #include "libc/math.h"
#include "libc/mem/fmt.h"
#include "libc/mem/mem.h" #include "libc/mem/mem.h"
#include "libc/nexgen32e/bsf.h" #include "libc/nexgen32e/bsf.h"
#include "libc/nexgen32e/bsr.h" #include "libc/nexgen32e/bsr.h"
@ -99,16 +100,18 @@ FLAGS\n\
-z print port\n\ -z print port\n\
-m log messages\n\ -m log messages\n\
-b log message bodies\n\ -b log message bodies\n\
-k encourage keep-alive\n\
-D DIR serve assets from directory\n\ -D DIR serve assets from directory\n\
-c INT cache seconds\n\ -c INT cache seconds\n\
-r /X=/Y redirect X to Y\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\ -l ADDR listen ip [default 0.0.0.0]\n\
-p PORT listen port [default 8080]\n\ -p PORT listen port [default 8080]\n\
-L PATH log file location\n\ -L PATH log file location\n\
-P PATH pid file location\n\ -P PATH pid file location\n\
-U INT daemon set user id\n\ -U INT daemon set user id\n\
-G INT daemon set group id\n\ -G INT daemon set group id\n\
-B STR changes server header\n\ -B STR changes brand\n\
\n\ \n\
FEATURES\n\ FEATURES\n\
\n\ \n\
@ -149,10 +152,6 @@ USAGE\n\
connection processes, which grow to whatever number your system\n\ connection processes, which grow to whatever number your system\n\
limits and tcp stack configuration allow. If fork() should fail\n\ limits and tcp stack configuration allow. If fork() should fail\n\
then redbean starts shutting idle connections down.\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" \n"
#define HASH_LOAD_FACTOR /* 1. / */ 4 #define HASH_LOAD_FACTOR /* 1. / */ 4
@ -281,6 +280,7 @@ static struct Freelist {
static struct Redirects { static struct Redirects {
size_t n; size_t n;
struct Redirect { struct Redirect {
int code;
const char *path; const char *path;
size_t pathlen; size_t pathlen;
const char *location; const char *location;
@ -301,12 +301,17 @@ static struct Assets {
} * p; } * p;
} assets; } assets;
static struct Shared {
int workers; //
} * shared;
static bool killed; static bool killed;
static bool istext; static bool istext;
static bool zombied; static bool zombied;
static bool gzipped; static bool gzipped;
static bool branded; static bool branded;
static bool meltdown; static bool meltdown;
static bool unbranded;
static bool heartless; static bool heartless;
static bool printport; static bool printport;
static bool heartbeat; static bool heartbeat;
@ -319,6 +324,7 @@ static bool logmessages;
static bool checkedmethod; static bool checkedmethod;
static bool connectionclose; static bool connectionclose;
static bool keyboardinterrupt; static bool keyboardinterrupt;
static bool encouragekeepalive;
static int frags; static int frags;
static int gmtoff; static int gmtoff;
@ -340,6 +346,8 @@ static size_t msgsize;
static size_t amtread; static size_t amtread;
static char *luaheaderp; static char *luaheaderp;
struct Strings stagedirs; struct Strings stagedirs;
static const char *sauce;
static const char *brand;
static const char *pidpath; static const char *pidpath;
static const char *logpath; static const char *logpath;
static int64_t programtime; static int64_t programtime;
@ -348,6 +356,7 @@ static int64_t cacheseconds;
static uint8_t gzip_footer[8]; static uint8_t gzip_footer[8];
static const char *serverheader; static const char *serverheader;
static struct Buffer logo;
static struct Buffer inbuf; static struct Buffer inbuf;
static struct Buffer hdrbuf; static struct Buffer hdrbuf;
static struct Buffer outbuf; static struct Buffer outbuf;
@ -355,6 +364,7 @@ static struct Request request;
static long double nowish; static long double nowish;
static long double startread; static long double startread;
static long double lastmeltdown;
static long double startrequest; static long double startrequest;
static long double startconnection; static long double startconnection;
static struct sockaddr_in serveraddr; static struct sockaddr_in serveraddr;
@ -436,15 +446,17 @@ static long FindRedirect(const char *path, size_t n) {
return -1; return -1;
} }
static void AddRedirect(const char *arg) { static void ProgramRedirect(int code, const char *src, const char *dst) {
long i, j; long i, j;
const char *p;
struct Redirect r; struct Redirect r;
CHECK_NOTNULL((p = strchr(arg, '='))); if (code && code != 301 && code != 302 && code != 307 && code != 308) {
CHECK_GT(p - arg, 0); fprintf(stderr, "error: unsupported redirect code %d\n", code);
r.path = arg; exit(1);
r.pathlen = p - arg; }
r.location = p + 1; r.code = code;
r.path = strdup(src);
r.pathlen = strlen(src);
r.location = strdup(dst);
if ((i = FindRedirect(r.path, r.pathlen)) != -1) { if ((i = FindRedirect(r.path, r.pathlen)) != -1) {
redirects.p[i] = r; redirects.p[i] = r;
} else { } 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) { static int CompareInts(const uint64_t x, uint64_t y) {
return x > y ? 1 : x < y ? -1 : 0; 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'; *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) { static void SetDefaults(void) {
cacheseconds = -1; ProgramBrand("redbean/0.2");
serverheader = "redbean/0.2"; ProgramCache(-1);
ProgramPort(DEFAULT_PORT);
serveraddr.sin_family = AF_INET; serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(DEFAULT_PORT);
serveraddr.sin_addr.s_addr = INADDR_ANY; serveraddr.sin_addr.s_addr = INADDR_ANY;
AddRedirect("/=/tool/net/redbean.html"); if (IsWindows()) uniprocess = true;
AddRedirect("/index.html=/tool/net/redbean.html");
AddRedirect("/favicon.ico=/tool/net/redbean.ico");
} }
static wontreturn void PrintUsage(FILE *f, int rc) { 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[]) { static void GetOpts(int argc, char *argv[]) {
int opt; 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) { switch (opt) {
case 'v': case 'v':
__log_level++; __log_level++;
@ -568,23 +608,29 @@ static void GetOpts(int argc, char *argv[]) {
case 'z': case 'z':
printport = true; printport = true;
break; break;
case 'k':
encouragekeepalive = true;
break;
case 'r': case 'r':
AddRedirect(optarg); ProgramRedirectArg(307, optarg);
break;
case 'R':
ProgramRedirectArg(0, optarg);
break; break;
case 'D': case 'D':
AddStagingDirectory(optarg); AddStagingDirectory(optarg);
break; break;
case 'c': case 'c':
cacheseconds = strtol(optarg, NULL, 0); ProgramCache(strtol(optarg, NULL, 0));
break; break;
case 'p': case 'p':
CHECK_NE(0xFFFF, (serveraddr.sin_port = htons(parseport(optarg)))); ProgramPort(strtol(optarg, NULL, 0));
break; break;
case 'l': case 'l':
CHECK_EQ(1, inet_pton(AF_INET, optarg, &serveraddr.sin_addr)); CHECK_EQ(1, inet_pton(AF_INET, optarg, &serveraddr.sin_addr));
break; break;
case 'B': case 'B':
serverheader = emptytonull(EncodeHttpHeaderValue(optarg, -1, 0)); ProgramBrand(optarg);
break; break;
case 'L': case 'L':
logpath = optarg; logpath = optarg;
@ -631,14 +677,18 @@ static void Daemonize(void) {
} }
static void OnWorkerExit(int pid, int ws) { static void OnWorkerExit(int pid, int ws) {
int w;
w = --shared->workers;
if (WIFEXITED(ws)) { if (WIFEXITED(ws)) {
if (WEXITSTATUS(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 { } else {
DEBUGF("worker %d exited", pid); DEBUGF("worker %d exited (%,d workers remain)", pid, w);
} }
} else { } 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 == ECHILD) break;
if (errno == EINTR) { if (errno == EINTR) {
if (killed) { if (killed) {
WARNF("%s terminating harder", serveraddrstr); killed = false;
terminated = false;
WARNF("%s redbean shall terminate harder", serveraddrstr);
LOGIFNEG1(kill(0, SIGTERM)); LOGIFNEG1(kill(0, SIGTERM));
} }
continue; continue;
@ -803,11 +855,18 @@ static void *FreeLater(void *p) {
static void CollectGarbage(void) { static void CollectGarbage(void) {
size_t i; size_t i;
for (i = 0; i < freelist.n; ++i) free(freelist.p[i]); for (i = 0; i < freelist.n; ++i) free(freelist.p[i]);
free(freelist.p);
freelist.p = 0;
freelist.n = 0; freelist.n = 0;
free(outbuf.p);
free(request.params.p); free(request.params.p);
DestroyHttpRequest(&msg); DestroyHttpRequest(&msg);
} }
static bool IsCompressionMethodSupported(int method) {
return method == kZipCompressionNone || method == kZipCompressionDeflate;
}
static void IndexAssets(void) { static void IndexAssets(void) {
int64_t lm; int64_t lm;
struct Asset *p; struct Asset *p;
@ -819,6 +878,12 @@ static void IndexAssets(void) {
CHECK_EQ(kZipCdirHdrMagic, ZIP_CDIR_MAGIC(zdir)); CHECK_EQ(kZipCdirHdrMagic, ZIP_CDIR_MAGIC(zdir));
for (cf = ZIP_CDIR_OFFSET(zdir); n--; cf += ZIP_CFILE_HDRSIZE(zmap + cf)) { for (cf = ZIP_CDIR_OFFSET(zdir); n--; cf += ZIP_CFILE_HDRSIZE(zmap + cf)) {
CHECK_EQ(kZipCfileHdrMagic, ZIP_CFILE_MAGIC(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)); hash = Hash(ZIP_CFILE_NAME(zmap + cf), ZIP_CFILE_NAMESIZE(zmap + cf));
step = 0; step = 0;
do { do {
@ -1019,7 +1084,7 @@ static void ParseFragment(struct Parser *u, struct Buffer *h) {
u->q = u->p; u->q = u->p;
} }
static bool ParseRequestUri(void) { static void ParseRequestUri(void) {
struct Parser u; struct Parser u;
u.i = 0; u.i = 0;
u.c = 0; u.c = 0;
@ -1032,8 +1097,6 @@ static bool ParseRequestUri(void) {
ParsePath(&u, &request.path); ParsePath(&u, &request.path);
if (u.c == '?') ParseParams(&u, &request.params); if (u.c == '?') ParseParams(&u, &request.params);
if (u.c == '#') ParseFragment(&u, &request.fragment); if (u.c == '#') ParseFragment(&u, &request.fragment);
return u.i == u.size &&
IsAcceptableHttpRequestPath(request.path.p, request.path.n);
} }
static void ParseFormParams(void) { static void ParseFormParams(void) {
@ -1310,6 +1373,43 @@ static char *ServeAsset(struct Asset *a, const char *path, size_t pathlen) {
return p; 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) { static int LuaServeAsset(lua_State *L) {
size_t pathlen; size_t pathlen;
struct Asset *a; struct Asset *a;
@ -1389,11 +1489,6 @@ static int LuaGetPath(lua_State *L) {
return 1; 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) { static void LuaPushLatin1(lua_State *L, const char *s, size_t n) {
char *t; char *t;
size_t m; size_t m;
@ -1588,9 +1683,7 @@ static int LuaWrite(lua_State *L) {
size_t size; size_t size;
const char *data; const char *data;
data = luaL_checklstring(L, 1, &size); data = luaL_checklstring(L, 1, &size);
outbuf.p = xrealloc(outbuf.p, outbuf.n + size); AppendData(data, size);
memcpy(outbuf.p + outbuf.n, data, size);
outbuf.n += size;
return 0; return 0;
} }
@ -1697,11 +1790,58 @@ static int LuaCrc32c(lua_State *L) {
return 1; 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) { static void LuaRun(const char *path) {
struct Asset *a; struct Asset *a;
const char *code; const char *code;
if ((a = LocateAsset(path, strlen(path)))) { if ((a = LocateAsset(path, strlen(path)))) {
code = LoadAsset(a, NULL); code = LoadAsset(a, NULL);
sauce = path + 1;
if (luaL_dostring(L, code) != LUA_OK) { if (luaL_dostring(L, code) != LUA_OK) {
WARNF("%s %s", path, lua_tostring(L, -1)); WARNF("%s %s", path, lua_tostring(L, -1));
} }
@ -1723,9 +1863,9 @@ static const luaL_Reg kLuaFuncs[] = {
{"FormatHttpDateTime", LuaFormatHttpDateTime}, // {"FormatHttpDateTime", LuaFormatHttpDateTime}, //
{"GetClientAddr", LuaGetClientAddr}, // {"GetClientAddr", LuaGetClientAddr}, //
{"GetDate", LuaGetDate}, // {"GetDate", LuaGetDate}, //
{"GetFragment", LuaGetFragment}, //
{"GetHeader", LuaGetHeader}, // {"GetHeader", LuaGetHeader}, //
{"GetHeaders", LuaGetHeaders}, // {"GetHeaders", LuaGetHeaders}, //
{"GetLogLevel", LuaGetLogLevel}, //
{"GetMethod", LuaGetMethod}, // {"GetMethod", LuaGetMethod}, //
{"GetParam", LuaGetParam}, // {"GetParam", LuaGetParam}, //
{"GetParams", LuaGetParams}, // {"GetParams", LuaGetParams}, //
@ -1736,10 +1876,16 @@ static const luaL_Reg kLuaFuncs[] = {
{"GetVersion", LuaGetVersion}, // {"GetVersion", LuaGetVersion}, //
{"HasParam", LuaHasParam}, // {"HasParam", LuaHasParam}, //
{"LoadAsset", LuaLoadAsset}, // {"LoadAsset", LuaLoadAsset}, //
{"Log", LuaLog}, //
{"ParseHttpDateTime", LuaParseHttpDateTime}, // {"ParseHttpDateTime", LuaParseHttpDateTime}, //
{"ProgramBrand", LuaProgramBrand}, //
{"ProgramCache", LuaProgramCache}, //
{"ProgramPort", LuaProgramPort}, //
{"ProgramRedirect", LuaProgramRedirect}, //
{"ServeAsset", LuaServeAsset}, // {"ServeAsset", LuaServeAsset}, //
{"ServeError", LuaServeError}, // {"ServeError", LuaServeError}, //
{"SetHeader", LuaSetHeader}, // {"SetHeader", LuaSetHeader}, //
{"SetLogLevel", LuaSetLogLevel}, //
{"SetStatus", LuaSetStatus}, // {"SetStatus", LuaSetStatus}, //
{"Write", LuaWrite}, // {"Write", LuaWrite}, //
{"bsf", LuaBsf}, // {"bsf", LuaBsf}, //
@ -1749,7 +1895,7 @@ static const luaL_Reg kLuaFuncs[] = {
{"popcnt", LuaPopcnt}, // {"popcnt", LuaPopcnt}, //
}; };
static void LuaSetArgv(void) { static void LuaSetArgv(lua_State *L) {
size_t i; size_t i;
lua_newtable(L); lua_newtable(L);
for (i = optind; i < __argc; ++i) { for (i = optind; i < __argc; ++i) {
@ -1759,6 +1905,11 @@ static void LuaSetArgv(void) {
lua_setglobal(L, "argv"); 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) { static void LuaInit(void) {
size_t i; size_t i;
L = luaL_newstate(); L = luaL_newstate();
@ -1767,7 +1918,13 @@ static void LuaInit(void) {
lua_pushcfunction(L, kLuaFuncs[i].func); lua_pushcfunction(L, kLuaFuncs[i].func);
lua_setglobal(L, kLuaFuncs[i].name); 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"); LuaRun("/tool/net/.init.lua");
} }
@ -1777,25 +1934,15 @@ static void LuaReload(void) {
static char *ServeLua(struct Asset *a) { static char *ServeLua(struct Asset *a) {
char *p; char *p;
outbuf.n = 0;
luaheaderp = NULL; luaheaderp = NULL;
sauce = FreeLater(strndup(request.path.p + 1, request.path.n - 1));
if (luaL_dostring(L, FreeLater(LoadAsset(a, NULL))) == LUA_OK) { if (luaL_dostring(L, FreeLater(LoadAsset(a, NULL))) == LUA_OK) {
if (!(p = luaheaderp)) { if (!(p = luaheaderp)) {
p = SetStatus(200, "OK"); p = SetStatus(200, "OK");
p = AppendContentType(p, "text/html"); p = AppendContentType(p, "text/html");
} }
if (outbuf.n) { if (outbuf.n) {
if (istext && outbuf.n >= 100 && ClientAcceptsGzip()) { p = CommitOutput(p);
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; return p;
} else { } else {
@ -1828,24 +1975,25 @@ static char *HandleAsset(struct Asset *a, const char *path, size_t pathlen) {
} }
static char *HandleRedirect(struct Redirect *r) { static char *HandleRedirect(struct Redirect *r) {
char *p;
struct Asset *a; struct Asset *a;
if ((a = LocateAsset(r->location, strlen(r->location)))) { if (!r->code) {
DEBUGF("%s %s %`'.*s rewritten %`'s", clientaddrstr, if ((a = LocateAsset(r->location, strlen(r->location)))) {
kHttpMethod[msg.method], request.path.n, request.path.p, DEBUGF("%s %s %`'.*s rewritten %`'s", clientaddrstr,
r->location); kHttpMethod[msg.method], request.path.n, request.path.p,
p = HandleAsset(a, r->location, strlen(r->location)); r->location);
return HandleAsset(a, r->location, strlen(r->location));
} else {
return ServeError(505, "HTTP Version Not Supported");
}
} else if (httpversion == 9) { } else if (httpversion == 9) {
p = ServeError(505, "HTTP Version Not Supported"); return ServeError(505, "HTTP Version Not Supported");
} else { } else {
DEBUGF("%s %s %`'.*s redirecting %`'s", clientaddrstr, DEBUGF("%s %s %`'.*s %d redirecting %`'s", clientaddrstr,
kHttpMethod[msg.method], request.path.n, request.path.p, kHttpMethod[msg.method], request.path.n, request.path.p, r->code,
r->location); r->location);
p = SetStatus(307, "Temporary Redirect"); return AppendHeader(SetStatus(r->code, GetHttpReason(r->code)), "Location",
p = AppendHeader(p, "Location", FreeLater(EncodeHttpHeaderValue(r->location, -1, 0)));
FreeLater(EncodeHttpHeaderValue(r->location, -1, 0)));
} }
return p;
} }
static void LogMessage(const char *d, const char *s, size_t n) { static void LogMessage(const char *d, const char *s, size_t n) {
@ -1930,6 +2078,140 @@ static const char *DescribeClose(void) {
return "destroyed"; 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) { static char *HandleMessage(void) {
long r; long r;
ssize_t cl, rc; ssize_t cl, rc;
@ -1940,7 +2222,7 @@ static char *HandleMessage(void) {
if (httpversion > 101) { if (httpversion > 101) {
return ServeError(505, "HTTP Version Not Supported"); return ServeError(505, "HTTP Version Not Supported");
} }
if (msg.method > kHttpPost || if (msg.method > kHttpOptions ||
(HasHeader(kHttpTransferEncoding) && (HasHeader(kHttpTransferEncoding) &&
!HeaderEquals(kHttpTransferEncoding, "identity"))) { !HeaderEquals(kHttpTransferEncoding, "identity"))) {
return ServeError(501, "Not Implemented"); return ServeError(501, "Not Implemented");
@ -1954,7 +2236,7 @@ static char *HandleMessage(void) {
if (HasHeader(kHttpContentLength)) { if (HasHeader(kHttpContentLength)) {
return ServeError(400, "Bad Request"); return ServeError(400, "Bad Request");
} else if (msg.method != kHttpGet && msg.method != kHttpHead && } else if (msg.method != kHttpGet && msg.method != kHttpHead &&
msg.method != kHttpOptions) { msg.method != kHttpDelete && msg.method != kHttpOptions) {
return ServeError(411, "Length Required"); return ServeError(411, "Length Required");
} else { } else {
cl = 0; cl = 0;
@ -1969,7 +2251,7 @@ static char *HandleMessage(void) {
} }
while (amtread < need) { while (amtread < need) {
if (++frags == 64) { if (++frags == 64) {
LogClose("payload fragged"); LogClose("payload fragged!");
return ServeError(408, "Request Timeout"); return ServeError(408, "Request Timeout");
} }
if ((rc = read(client, inbuf.p + amtread, inbuf.n - amtread)) != -1) { if ((rc = read(client, inbuf.p + amtread, inbuf.n - amtread)) != -1) {
@ -1996,7 +2278,12 @@ static char *HandleMessage(void) {
if (httpversion != 101 || IsConnectionClose()) { if (httpversion != 101 || IsConnectionClose()) {
connectionclose = true; 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, WARNF("%s could not parse request request %`'.*s", clientaddrstr,
msg.uri.b - msg.uri.a, inbuf.p + msg.uri.a); msg.uri.b - msg.uri.a, inbuf.p + msg.uri.a);
connectionclose = true; connectionclose = true;
@ -2013,6 +2300,8 @@ static char *HandleMessage(void) {
return HandleAsset(a, request.path.p, request.path.n); return HandleAsset(a, request.path.p, request.path.n);
} else if ((r = FindRedirect(request.path.p, request.path.n)) != -1) { } else if ((r = FindRedirect(request.path.p, request.path.n)) != -1) {
return HandleRedirect(redirects.p + r); return HandleRedirect(redirects.p + r);
} else if (!CompareSlices(request.path.p, request.path.n, "/", 1)) {
return ServeListing();
} else { } else {
return ServeError(404, "Not Found"); return ServeError(404, "Not Found");
} }
@ -2051,6 +2340,8 @@ static bool HandleRequest(void) {
} }
if (connectionclose) { if (connectionclose) {
p = AppendHeader(p, "Connection", "close"); p = AppendHeader(p, "Connection", "close");
} else if (encouragekeepalive && httpversion >= 101) {
p = AppendHeader(p, "Connection", "keep-alive");
} }
actualcontentlength = contentlength; actualcontentlength = contentlength;
if (gzipped) { if (gzipped) {
@ -2091,6 +2382,8 @@ static bool HandleRequest(void) {
static void InitRequest(void) { static void InitRequest(void) {
frags = 0; frags = 0;
msgsize = 0; msgsize = 0;
outbuf.p = 0;
outbuf.n = 0;
content = NULL; content = NULL;
gzipped = false; gzipped = false;
branded = false; branded = false;
@ -2118,7 +2411,7 @@ static void ProcessRequests(void) {
} else if (got) { } else if (got) {
if (++frags == 32) { if (++frags == 32) {
SendTimeout(); SendTimeout();
LogClose("fragged"); LogClose("fragged!");
return; return;
} else { } else {
DEBUGF("%s fragmented msg %,ld %,ld", clientaddrstr, amtread, 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) { static void ProcessConnection(void) {
int pid; int pid;
clientaddrsize = sizeof(clientaddr); clientaddrsize = sizeof(clientaddr);
@ -2167,11 +2467,11 @@ static void ProcessConnection(void) {
connectionclose = false; connectionclose = false;
break; break;
case -1: case -1:
WARNF("redbean is entering meltdown mode"); EnterMeltdownMode();
LOGIFNEG1(kill(0, SIGUSR2));
SendServiceUnavailable(); SendServiceUnavailable();
/* fallthrough */ /* fallthrough */
default: default:
++shared->workers;
close(client); close(client);
return; return;
} }
@ -2195,12 +2495,20 @@ static void TuneServerSocket(void) {
LOGIFNEG1(setsockopt(server, IPPROTO_TCP, TCP_QUICKACK, &yes, sizeof(yes))); LOGIFNEG1(setsockopt(server, IPPROTO_TCP, TCP_QUICKACK, &yes, sizeof(yes)));
} }
void RedBean(void) { void RedBean(int argc, char *argv[]) {
uint32_t addrsize; uint32_t addrsize;
if (IsWindows()) uniprocess = true;
gmtoff = GetGmtOffset(); 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)); OpenZip((const char *)getauxval(AT_EXECFN));
IndexAssets(); IndexAssets();
LoadLogo();
SetDefaults();
GetOpts(argc, argv);
LuaInit();
if (uniprocess) shared->workers = 1;
xsigaction(SIGINT, OnInt, 0, 0, 0); xsigaction(SIGINT, OnInt, 0, 0, 0);
xsigaction(SIGHUP, OnHup, 0, 0, 0); xsigaction(SIGHUP, OnHup, 0, 0, 0);
xsigaction(SIGTERM, OnTerm, 0, 0, 0); xsigaction(SIGTERM, OnTerm, 0, 0, 0);
@ -2239,7 +2547,6 @@ void RedBean(void) {
inbuf.p = xvalloc(inbuf.n); inbuf.p = xvalloc(inbuf.n);
hdrbuf.n = 4 * 1024; hdrbuf.n = 4 * 1024;
hdrbuf.p = xvalloc(hdrbuf.n); hdrbuf.p = xvalloc(hdrbuf.n);
LuaInit();
while (!terminated) { while (!terminated) {
if (zombied) { if (zombied) {
ReapZombies(); ReapZombies();
@ -2256,7 +2563,7 @@ void RedBean(void) {
ProcessConnection(); ProcessConnection();
} }
} }
VERBOSEF("%s terminated", serveraddrstr); VERBOSEF("%s shutting down", serveraddrstr);
LOGIFNEG1(close(server)); LOGIFNEG1(close(server));
if (!keyboardinterrupt) { if (!keyboardinterrupt) {
if (!killed) { if (!killed) {
@ -2269,8 +2576,6 @@ void RedBean(void) {
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
showcrashreports(); showcrashreports();
SetDefaults(); RedBean(argc, argv);
GetOpts(argc, argv);
RedBean();
return 0; return 0;
} }

View file

@ -1,6 +1,9 @@
-- redbean lua server page demo -- redbean lua server page demo
local function main() 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. -- This check is pedantic but might be good to have.
if GetMethod() ~= 'GET' and GetMethod() ~= 'HEAD' then if GetMethod() ~= 'GET' and GetMethod() ~= 'HEAD' then
ServeError(405) ServeError(405)
@ -19,6 +22,12 @@ local function main()
Write('<title>redbean</title>\n') Write('<title>redbean</title>\n')
Write('<h1>redbean lua server page demo</h1>\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. -- GetParams() returns an ordered list of Request-URI query params.
Write('<h3>request uri parameters</h3>\n') Write('<h3>request uri parameters</h3>\n')
params = GetParams() params = GetParams()
@ -100,10 +109,6 @@ local function main()
Write('<dd>') Write('<dd>')
Write(GetServerAddr()) Write(GetServerAddr())
Write('\n') Write('\n')
Write('<dt>FormatHttpDateTime(GetDate())\n')
Write('<dd>')
Write(FormatHttpDateTime(GetDate()))
Write('\n')
Write('</dl>\n') Write('</dl>\n')
end end