diff --git a/test/tool/net/lua_test.lua b/test/tool/net/lua_test.lua index d3a25cf42..e687b4e8a 100644 --- a/test/tool/net/lua_test.lua +++ b/test/tool/net/lua_test.lua @@ -21,3 +21,12 @@ assert(0200 == 128) assert("\e" == "\x1b") assert("hi" * 3 == "hihihi") assert("hello %d" % {123} == "hello 123") + +-- test MapContentType +assert(MapContentType("txt") == "text/plain") +assert(MapContentType("htm") == "text/html") +assert(MapContentType("abc.htm") == "text/html") +assert(MapContentType("1") == nil) +MapContentType("1", "text/x-foo") +assert(MapContentType("1"), "text/x-foo") +assert(MapContentType("file.1"), "text/x-foo") diff --git a/tool/net/help.txt b/tool/net/help.txt index f288b3858..3b3971cf7 100644 --- a/tool/net/help.txt +++ b/tool/net/help.txt @@ -1271,6 +1271,9 @@ FUNCTIONS flag was used. If slurping large file into memory is a concern, then consider using ServeAsset which can serve directly off disk. + MapContentType(ext:str[, contenttype:str]) → str + Sets or returns content type associated with a file extension. + StoreAsset(path:str, data:str[, mode:int]) Stores asset to executable's ZIP central directory. This currently happens in an append-only fashion and is still diff --git a/tool/net/redbean.c b/tool/net/redbean.c index 3e5e4988a..ea768d260 100644 --- a/tool/net/redbean.c +++ b/tool/net/redbean.c @@ -463,6 +463,7 @@ static const char *serverheader; static struct Strings stagedirs; static struct Strings hidepaths; static const char *launchbrowser; +static const char ctIdx = 'c'; // a pseudo variable to get address of static struct spawn replth; static struct spawn monitorth; @@ -4608,6 +4609,50 @@ static int LuaIsAssetCompressed(lua_State *L) { return 1; } +static const char *GetContentTypeExt(const char *path, size_t n) { + char *r, *e; + int top; + lua_State *L = GL; + if ((r = FindContentType(path, n))) return r; + + // extract the last .; use the entire path if none is present + if (e = strrchr(path, '.')) path = e + 1; + top = lua_gettop(L); + lua_pushlightuserdata(L, (void *)&ctIdx); // push address as unique key + CHECK_EQ(lua_gettable(L, LUA_REGISTRYINDEX), LUA_TTABLE); + + lua_pushstring(L, path); + if (lua_gettable(L, -2) == LUA_TSTRING) + r = FreeLater(strdup(lua_tostring(L, -1))); + lua_settop(L, top); + return r; +} + +static int LuaMapContentType(lua_State *L) { + const char *ext = luaL_checkstring(L, 1); + const char *ct; + int n = lua_gettop(L); + + lua_pushlightuserdata(L, (void *)&ctIdx); // push address as unique key + CHECK_EQ(lua_gettable(L, LUA_REGISTRYINDEX), LUA_TTABLE); + + if (n == 1) { + ext = FreeLater(xasprintf(".%s", ext)); + if ((ct = GetContentTypeExt(ext, strlen(ext)))) { + lua_pushstring(L, ct); + } else { + lua_pushnil(L); + } + return 1; + } else { + ct = luaL_checkstring(L, 2); + lua_pushstring(L, ext); + lua_pushstring(L, ct); + lua_settable(L, -3); // table[ext] = ct + return 0; + } +} + static int LuaIsDaemon(lua_State *L) { lua_pushboolean(L, daemonize); return 1; @@ -4822,6 +4867,7 @@ static const luaL_Reg kLuaFuncs[] = { {"Lemur64", LuaLemur64}, // {"LoadAsset", LuaLoadAsset}, // {"Log", LuaLog}, // + {"MapContentType", LuaMapContentType}, // {"Md5", LuaMd5}, // {"MeasureEntropy", LuaMeasureEntropy}, // {"ParseHost", LuaParseHost}, // @@ -4966,6 +5012,10 @@ static void LuaStart(void) { LuaSetConstant(L, "kLogWarn", kLogWarn); LuaSetConstant(L, "kLogError", kLogError); LuaSetConstant(L, "kLogFatal", kLogFatal); + // create a list of custom content types + lua_pushlightuserdata(L, (void *)&ctIdx); // push address as unique key + lua_newtable(L); + lua_settable(L, LUA_REGISTRYINDEX); // registry[&ctIdx] = {} #endif } @@ -5724,13 +5774,13 @@ static char *HandleAsset(struct Asset *a, const char *path, size_t pathlen) { static const char *GetContentType(struct Asset *a, const char *path, size_t n) { const char *r; - if (a->file && (r = FindContentType(a->file->path.s, a->file->path.n))) { + if (a->file && (r = GetContentTypeExt(a->file->path.s, a->file->path.n))) { return r; } return firstnonnull( - FindContentType(path, n), - firstnonnull(FindContentType(ZIP_CFILE_NAME(zbase + a->cf), - ZIP_CFILE_NAMESIZE(zbase + a->cf)), + GetContentTypeExt(path, n), + firstnonnull(GetContentTypeExt(ZIP_CFILE_NAME(zbase + a->cf), + ZIP_CFILE_NAMESIZE(zbase + a->cf)), a->istext ? "text/plain" : "application/octet-stream")); }