const fs = require("fs"); const path = require("path"); const types = require("./types.js"); const log = require("./log.js"); const { version } = require("./modules/03-cache.js"); const mustache = require("mustache"); const LRU = require("lru-cache"); const cache = new LRU({ max: 100 }); const time = Date.now().toString(32).toString(36); const template404 = fs.readFileSync("assets/404.html", "utf8"); module.exports = generate; const handlers = { directory: require("./handlers/directory.js"), markdown: require("./handlers/markdown.js"), gemini: require("./handlers/gemini.js"), file: require("./handlers/file.js"), }; function generate(file, raw = false) { const where = path.resolve(__dirname + "/overlay/" + file); if(!fs.existsSync(where)) { return send("text/html", mustache.render(template404, { file, time, cache: { style: version("/assets/style.css") } }), 404); } try { const stat = fs.statSync(where); if(stat.isDirectory()) { if(!file.endsWith("/")) return (req, res) => res.redirect(file + "/"); return send("text/html", run(handlers.directory)); } else if(path.extname(where) === ".md" && !raw) { return send("text/html", run(handlers.markdown)); } else if(path.extname(where) === ".gmi" && !raw) { return send("text/html", run(handlers.gemini)); } else { const type = types.get(path.extname(where).slice(1)); if(stat.size < 1024 * 32) { return send(type, run(handlers.file)); } else { return (req, res) => { const range = req.range(stat.size); if(range === -2) return res.sendStatus(400); if(range === -1) return res.sendStatus(416); const headers = { "Content-Type": type || "application/octet-stream", "Content-Length": stat.size, "Accept-Ranges": "bytes", }; if(range) { if(range.type !== "bytes") return res.sendStatus(400); headers["Content-Range"] = `bytes ${range[0].start}-${range[0].end}/${stat.size}`; headers["Content-Length"] = Math.abs(range[0].end - range[0].start) + 1; res.writeHead(206, headers); fs.createReadStream(where, range[0]).pipe(res); } else { res.writeHead(200, headers); fs.createReadStream(where).pipe(res); } }; } } } catch { return send("text/html", mustache.render(template404, { file, time, cache: { style: version("/assets/style.css") } }), 404); } function send(type, content, status = 200) { if(typeof content === "function") return content; content = Buffer.from(content); const headers = { "Content-Type": type || "text/plain", "Content-Length": content.length }; return (req, res) => res.writeHead(status, headers).end(content); } function run(mod) { const hash = mod.hash(where, file); if(cache.has(where)) { const [rendered, oldhash] = cache.get(where); if(hash === oldhash) return rendered; } const rendered = mod.render(where, file); cache.set(where, [rendered, hash]); return rendered; } }; module.exports.handle = (req, res) => { // stop sneaky people if(req.path.includes("..")) return res.writeHead(403).end("nice try bud, but we don't do that here."); generate(path.normalize(decodeURIComponent(req.path)), "raw" in req.query)(req, res); };