Enable serving zip directory entries

This commit is contained in:
Justine Tunney 2021-03-29 02:39:20 -07:00
parent 1753b669cf
commit 7dc8973567
3 changed files with 131 additions and 50 deletions

View file

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

View file

@ -36,6 +36,7 @@ TOOL_NET_DIRECTDEPS = \
LIBC_SYSV \
LIBC_SYSV_CALLS \
LIBC_TIME \
LIBC_TINYMATH \
LIBC_UNICODE \
LIBC_X \
NET_HTTP \

View file

@ -345,7 +345,6 @@ static size_t hdrsize;
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;
@ -355,6 +354,8 @@ static size_t contentlength;
static int64_t cacheseconds;
static uint8_t gzip_footer[8];
static const char *serverheader;
static struct Strings stagedirs;
static struct Strings hidepaths;
static struct Buffer logo;
static struct Buffer inbuf;
@ -508,7 +509,7 @@ static const char *FindContentType(uint64_t ext) {
return NULL;
}
static const char *GetContentType(const char *path, size_t n) {
static const char *GetContentType2(const char *path, size_t n) {
size_t i;
uint64_t x;
const char *p, *r;
@ -521,7 +522,15 @@ static const char *GetContentType(const char *path, size_t n) {
return r;
}
}
return "application/octet-stream";
return NULL;
}
static const char *GetContentType(struct Asset *a, const char *path, size_t n) {
return firstnonnull(
GetContentType2(path, n),
firstnonnull(GetContentType2(ZIP_LFILE_NAME(zmap + a->lf),
ZIP_LFILE_NAMESIZE(zmap + a->lf)),
"application/octet-stream"));
}
static void DescribeAddress(char buf[32], const struct sockaddr_in *addr) {
@ -575,6 +584,11 @@ static char *RemoveTrailingSlashes(char *s) {
return s;
}
static void AddString(struct Strings *l, char *s) {
l->p = xrealloc(l->p, ++l->n * sizeof(*l->p));
l->p[l->n - 1] = s;
}
static void AddStagingDirectory(const char *dirpath) {
char *s;
s = RemoveTrailingSlashes(strdup(dirpath));
@ -582,8 +596,7 @@ static void AddStagingDirectory(const char *dirpath) {
fprintf(stderr, "error: not a directory: %s\n", s);
exit(1);
}
stagedirs.p = xrealloc(stagedirs.p, ++stagedirs.n * sizeof(*stagedirs.p));
stagedirs.p[stagedirs.n - 1] = s;
AddString(&stagedirs, s);
}
static void GetOpts(int argc, char *argv[]) {
@ -884,6 +897,11 @@ static void IndexAssets(void) {
ZIP_CFILE_NAMESIZE(zmap + cf), ZIP_CFILE_NAME(zmap + cf));
continue;
}
if (ZIP_CFILE_NAMESIZE(zmap + cf) > 1 &&
ZIP_CFILE_NAME(zmap + cf)[ZIP_CFILE_NAMESIZE(zmap + cf) - 1] == '/' &&
!ZIP_CFILE_UNCOMPRESSEDSIZE(zmap + cf)) {
continue;
}
hash = Hash(ZIP_CFILE_NAME(zmap + cf), ZIP_CFILE_NAMESIZE(zmap + cf));
step = 0;
do {
@ -932,19 +950,22 @@ static struct Asset *FindAsset(const char *path, size_t pathlen) {
}
static struct Asset *LocateAssetZip(const char *path, size_t pathlen) {
char *path2;
char *p2, *p3, *p4;
struct Asset *a;
if (pathlen && path[0] == '/') ++path, --pathlen;
if (!(a = FindAsset(path, pathlen)) &&
(!pathlen || (pathlen && path[pathlen - 1] == '/'))) {
path2 = xmalloc(pathlen + 10);
memcpy(path2, path, pathlen);
memcpy(path2 + pathlen, "index.lua", 9);
if (!(a = FindAsset(path2, pathlen + 9))) {
memcpy(path2 + pathlen, "index.html", 10);
a = FindAsset(path2, pathlen + 10);
p2 = strndup(path, pathlen);
p3 = xjoinpaths(p2, "index.lua");
LOGF("find asset %s", p3);
if (!(a = FindAsset(p3, strlen(p3)))) {
p4 = xjoinpaths(p2, "index.html");
LOGF("find asset %s", p4);
a = FindAsset(p4, strlen(p4));
free(p4);
}
free(path2);
free(p3);
free(p2);
}
return a;
}
@ -1360,7 +1381,7 @@ static char *ServeAsset(struct Asset *a, const char *path, size_t pathlen) {
}
if (httpversion >= 100) {
p = AppendHeader(p, "Last-Modified", a->lastmodifiedstr);
p = AppendContentType(p, GetContentType(path, pathlen));
p = AppendContentType(p, GetContentType(a, path, pathlen));
if (httpversion >= 101) {
p = AppendCache(p, cacheseconds);
if (!IsCompressed(a)) {
@ -1410,6 +1431,30 @@ static char *CommitOutput(char *p) {
return p;
}
static char *GetAssetPath(uint32_t cf, size_t *out_size) {
char *p1, *p2;
size_t n1, n2;
p1 = ZIP_CFILE_NAME(zmap + cf);
n1 = ZIP_CFILE_NAMESIZE(zmap + cf);
p2 = malloc(1 + n1 + 1);
n2 = 1 + n1 + 1;
p2[0] = '/';
memcpy(p2 + 1, p1, n1);
p2[1 + n1] = '\0';
if (out_size) *out_size = 1 + n1;
return p2;
}
static bool IsHiddenPath(const char *s) {
size_t i;
for (i = 0; i < hidepaths.n; ++i) {
if (startswith(s, hidepaths.p[i])) {
return true;
}
}
return false;
}
static int LuaServeAsset(lua_State *L) {
size_t pathlen;
struct Asset *a;
@ -1821,6 +1866,11 @@ static int LuaSetLogLevel(lua_State *L) {
return 0;
}
static int LuaHidePath(lua_State *L) {
AddString(&hidepaths, strdup(luaL_checkstring(L, 1)));
return 0;
}
static int LuaLog(lua_State *L) {
int level;
lua_Debug ar;
@ -1836,6 +1886,29 @@ static int LuaLog(lua_State *L) {
return 0;
}
static int LuaIsHiddenPath(lua_State *L) {
lua_pushboolean(L, IsHiddenPath(luaL_checkstring(L, 1)));
return 1;
}
static int LuaGetZipPaths(lua_State *L) {
char *path;
uint32_t cf;
size_t i, n, pathlen;
lua_newtable(L);
i = 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));
path = GetAssetPath(cf, &pathlen);
lua_pushlstring(L, path, pathlen);
lua_seti(L, -2, ++i);
free(path);
}
return 1;
}
static void LuaRun(const char *path) {
struct Asset *a;
const char *code;
@ -1874,7 +1947,9 @@ static const luaL_Reg kLuaFuncs[] = {
{"GetServerAddr", LuaGetServerAddr}, //
{"GetUri", LuaGetUri}, //
{"GetVersion", LuaGetVersion}, //
{"GetZipPaths", LuaGetZipPaths}, //
{"HasParam", LuaHasParam}, //
{"HidePath", LuaHidePath}, //
{"LoadAsset", LuaLoadAsset}, //
{"Log", LuaLog}, //
{"ParseHttpDateTime", LuaParseHttpDateTime}, //
@ -2108,10 +2183,10 @@ static char *ServeListing(void) {
char tb[64];
int w, x, y;
struct tm tm;
char *p, *path;
int64_t lastmod;
uint32_t cf, lf;
size_t n, n1, n2;
char *p, *p1, *p2;
size_t i, n, pathlen;
struct EscapeResult r[3];
AppendString("\
<!doctype html>\n\
@ -2128,8 +2203,11 @@ a {\n\
img {\n\
vertical-align: middle;\n\
}\n\
footer {\n\
font-size: 11pt;\n\
}\n\
</style>\n\
<h1>\n");
<header><h1>\n");
if (logo.n) {
AppendString("<img src=\"data:image/png;base64,");
AppendData(logo.p, logo.n);
@ -2138,50 +2216,50 @@ img {\n\
r[0] = EscapeHtml(brand, strlen(brand));
AppendData(r[0].data, r[0].size);
free(r[0].data);
AppendString("</h1><hr><pre>\n");
AppendString("</h1><hr></header><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));
if (!IsHiddenPath((path = GetAssetPath(cf, &pathlen)))) {
y = strwidth(path, 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));
path = GetAssetPath(cf, &pathlen);
if (!IsHiddenPath(path)) {
r[0] = EscapeHtml(path, pathlen);
r[1] = EscapeUrlPath(path, pathlen);
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(path, pathlen)) {
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(r[2].data);
free(r[1].data);
free(r[0].data);
free(p2);
free(path);
}
AppendString("</pre><hr><p>\n");
AppendString("</pre><footer><hr><p>\n");
if (!unbranded) {
AppendString("\
this listing is for /\n\
@ -2197,6 +2275,7 @@ your redbean is ");
}
w = shared->workers;
AppendFmt("currently servicing %,d connection%s\n", w, w == 1 ? "" : "s");
AppendString("</footer>\n");
p = SetStatus(200, "OK");
p = AppendCache(p, 0);
return CommitOutput(p);