var fs = require("fs"); var pathlib = require("path"); var mimes = { txt: "text/plain", css: "text/css", html: "text/html", js: "application/javascript", json: "application/json", xml: "application/xml", zip: "application/zip", pdf: "application/pdf", gif: "image/gif", png: "image/png", jpeg: "image/jpeg", jpg: "image/jpeg", svg: "image/svg+xml", bmp: "image/bmp", webp: "image/webp", midi: "audio/midi", mp3: "audio/mpeg", ogg: "audio/ogg", webm: "video/webm", mkv: "video/mkv", ogv: "video/ogg", } function mimetype(path) { var unknown = "application/octet-stream"; var ext = pathlib.extname(path); if (ext) return mimes[ext.substr(1)] || unknown; else return unknown; } function sendfile(path, app, pathname, req, res) { fs.open(path, "r", (err, fd) => { if (err) { app.notice(err); res.writeHead(404); res.end(app.template(app.res404, { method: req.method, pathname: pathname })); return; } res.writeHead(200, { "Content-Type": mimetype(path) }); var rs = fs.createReadStream(null, { fd: fd }); rs.on("error", err => { app.warning(err); }); rs.on("data", d => res.write(d)); rs.on("end", () => res.end()); }); } var transformCache = {}; function handleTransform(stat, path, req, res, app) { var ext = pathlib.extname(path); if (ext === "") return false; var transform = app._transforms[ext]; if (!transform) return false; if (transformCache[path]) { var c = transformCache[path]; if (stat.ctime === c.ctime) { res.writeHead(c.data.status, c.data.headers); res.end(c.data.content); return true; } else { delete transformCache[path]; } } var data = { content: "", headers: null, status: null }; var writeStream = { headersSent: false, status: 200, headers: { "content-type": transform.mime }, write: function(d) { if (!writeStream.headersSent) { res.writeHead(writeStream.status, writeStream.headers); writeStream.headersSent = true; } if (d != null) { data.content += d; res.write(d); } }, end: function(d, nocache) { writeStream.write(d); res.end(); if (!nocache) { data.status = writeStream.status; data.headers = writeStream.headers; transformCache[path] = { data: data, ctime: stat.ctime }; } }, error: function(err) { if (writeStream.status === 200) writeStream.status = 500; app.warning("Transform for "+path+": "+err.toString()); writeStream.end(null, true); } } transform.func(path, writeStream); return true; } module.exports = function(root, before) { return function(req, res, app) { // `req.urlobj.pathname` is too long. var pn = req.urlobj.pathname; // Join the web root with the request's path name var path = pathlib.join(root, pn.replace(before, "")); // Send a file function send(path) { sendfile(path, app, pn, req, res); } // Prevent leaking information if (pn.indexOf("../") !== -1 || pn.indexOf("/..") !== -1 || pn === "..") { res.writeHead(403); res.end(app.template(app.res403, { method: req.method, pathname: pn })); return; } fs.stat(path, (err, stat) => { // If there's an error stat'ing, just error if (err) { app.notice(err); res.writeHead(404); res.end(app.template(app.res404, { method: req.method, pathname: pn })); return; } // If it's a directory, we want the index.html file, // or redirect to / if (stat.isDirectory()) { if (pn[pn.length - 1] !== "/") { res.writeHead(302, { location: pn + "/" }); res.end(); return; } path = pathlib.join(path, "index.html"); } // If there's a transform, let that handle it if (handleTransform(stat, path, req, res, app)) return; // Otherwise, send the file send(path); }); } }