#!/usr/bin/env node const cmn = require("./modules/$common.js"); const express = require("express"); const http = require("http"); const socketio = require("socket.io"); const url = require("url"); const fs = require("fs"); const util = require("util"); const node_path = require("path"); const bodyParser = require("body-parser"); const basicAuth = require("basic-auth"); // const Logger = require("./logger"); if (process.argv.length > 3) { console.log("Pass optional port as first argument"); process.exit(1); } const PORT = process.argv.length == 3 ? +process.argv[2] : 80; const app = express(); const httpServer = http.Server(app); const io = socketio(httpServer); if ("NODE_TRUST_PROXY" in process.env && process.env["NODE_TRUST_PROXY"].length > 0) { console.log("Note: Trusting reverse proxy's X-Forwarded-* headers."); app.set("trust proxy", "loopback"); } else { console.log("Note: Not trusting reverse proxy. Set NODE_TRUST_PROXY to change."); } // const reqlogger = new Logger("request_log.txt"); // const whatpulse = {"keys": "", "clicks": ""}; // function refreshWhatpulse() { // const url = "http://api.whatpulse.org/user.php?user=tomsmeding&format=json&formatted=yes"; // http.get(url, function (res) { // let body = ""; // res.on("data", function (data) { body += data; }); // res.on("end", function () { // try { body = JSON.parse(body); } // catch (e) { return; } // whatpulse.keys = body.Keys[>.replace(/,/g, " ")<]; // whatpulse.clicks = body.Clicks[>.replace(/,/g, " ")<]; // }); // }).on("error", function (err) { // console.log(`Whatpulse request error: ${err.message}`); // }); // } // setInterval(refreshWhatpulse, 6*3600*1000); //every 6 hours // refreshWhatpulse(); // function simpleLogEscape(str) { // return str // .replace(/\\/g, "\\\\") // .replace(/\n/g, "\\n") // .replace(/ /g, "\\_"); // } // app.use(function (req, res, next) { // let line = simpleLogEscape(req.ip) + " " + // simpleLogEscape(req.method) + " " + // simpleLogEscape(req.hostname) + " " + // simpleLogEscape(req.url); // if (req.headers["dnt"]) line += " dnt=" + simpleLogEscape(req.headers["dnt"]); // if (req.headers["referer"]) line += " referer=" + simpleLogEscape(req.headers["referer"]); // reqlogger.log(line); // next(); // }); // app.use(function (req, res, next) { // console.log(req.subdomains); // next(); // }); app.use(bodyParser.text()); app.use(express.static("static")); const module_list = fs.readdirSync("modules").filter(function (f) { return ["$common.js", ".DS_Store"].indexOf(f) == -1 && f[0] != "$"; }); for (let i = 0; i < module_list.length; i++) { const mod = require("./modules/" + module_list[i] + "/" + module_list[i] + ".js"); const ret = mod(app, io, cmn.serverdir + "/modules/" + module_list[i]); if (ret === false) { console.log("FAILED LOADING module " + module_list[i]); } else { console.log("Loaded module " + module_list[i]); } } function padLeft(s, len, fill) { let prefix = ""; const num = len - s.length; for (let i = 0; i < num; i++) prefix += fill; return prefix + s; } function buildPublicationsHTML(info) { let res = ""; for (const obj of info.pubs) { if (res.length != 0) res += "\n\t\t\t"; const seen = new Map(); res += "
  • "; res += obj.year; seen.set("year", true); if ("month" in obj) res += "-" + padLeft(obj.month + "", 2, "0") seen.set("month", true); res += " "; res += obj.authors.map(key => key in info.authors ? info.authors[key] : key).join(", "); seen.set("authors", true); res += ": “" + obj.title + "”."; seen.set("title", true); if ("conference" in obj) res += " At " + obj.conference + "."; seen.set("conference", true); if ("journal" in obj) res += " In " + obj.journal + "."; seen.set("journal", true); if ("preprint" in obj) { res += " Preprint"; if (typeof obj.preprint == "string") res += ", " + obj.preprint; res += "."; } seen.set("preprint", true); if ("note" in obj) res += " " + obj.note; seen.set("note", true); if ("links" in obj && obj.links.length > 0) { res += " ["; const anchors = []; for (const link of obj.links) { let name, url; const note = "note" in link ? " (" + link.note + ")" : ""; switch (link.type) { case "arxiv": name = "arxiv"; url = "https://arxiv.org/abs/" + link.id; break; case "doi": name = "doi"; url = "https://doi.org/" + link.id; break; case "code": name = "code"; url = link.url; break; case "pdf": name = "PDF"; url = link.url; break; default: throw new Error("Unknown publication link type " + link.type); } anchors.push("" + name + "" + note); } res += anchors.join(", "); res += "]" } seen.set("links", true); res += "
  • "; for (const key in obj) { if (!seen.has(key)) { throw new Error(`Key '${key}' unknown in publication`); } } } return res; } const publicationsHTML = buildPublicationsHTML(JSON.parse(fs.readFileSync(cmn.serverdir + "/publications.json"))); function anyComponentHidden(fname) { return fname[0] == "." || fname.indexOf("/.") != -1; } // if 'mime' is not null/undefined, it should be the content-type of the file // options: // { // mime: "content-type value", // listdirs: true/false, // cors: true/false, // cache: "cache-control value" // } // (defaults are null/false) function requestFile(req, res, path, origpath, options) { if (options == null) options = {}; fname = cmn.webfilesdir + path; console.log("Requesting file " + fname); if (anyComponentHidden(fname) || !fs.existsSync(fname)) { res.status(404).send("That file does not exist."); return; } const stats = fs.statSync(fname); if (stats.isFile()) { const headers = {}; if (options.mime != null) headers["content-type"] = options.mime; if (options.cors == true) headers["access-control-allow-origin"] = "*"; if (options.cache != null) headers["cache-control"] = options.cache; res.sendFile(fname, {headers}); } else if (stats.isDirectory() && options.listdirs) { const items = fs .readdirSync(fname) .filter(function (f) { return f[0] != "."; }) .map(function (f) { try { if (fs.statSync(node_path.join(fname, f)).isDirectory()) return f + "/"; else return f; } catch (e) { return null; } }) .filter(function (f) { return f !== null; }); res.send( String(fs.readFileSync(cmn.serverdir + "/dirlisting.html")) .replace("", path) .replace("[/*LISTINGLISTING*/]", JSON.stringify(items)) ); } else if (stats.isDirectory() && fs.readdirSync(fname).indexOf("index.html") != -1) { if (origpath[origpath.length-1] != "/") origpath += "/"; res.redirect(301, origpath + "index.html"); } else { res.status(404).send("That file does not exist."); return; } } function makeUrlSafe(req, sliceLength) { const parsed = url.parse(req.url); const part = parsed.pathname.slice(sliceLength); if (part == "") return "/"; return part.replace(/\/\.+[^\/]*\//g, "/"); } app.get("/", function (req, res) { // res.sendFile(cmn.serverdir + "/index.html"); res.send( String(fs.readFileSync(cmn.serverdir + "/index.html")) .replace(/.*/, publicationsHTML) ); // res.send( // String(fs.readFileSync(cmn.serverdir + "/index.html")) // .replace(//, whatpulse["keys"]) // .replace(//, whatpulse["clicks"]) // ); }); app.get(["/f/univq", "/f/univq/*"], cmn.authgen()); app.get(["/f", "/f/*"], function (req, res) { requestFile(req, res, unescape(makeUrlSafe(req, 2)), req.url); }); app.get(["/ff", "/ff/*"], cmn.authgen()); app.get(["/ff", "/ff/*"], function (req, res) { requestFile(req, res, unescape(makeUrlSafe(req, 3)), req.url, {listdirs: true}); }); app.get("/.well-known/matrix/*", function (req, res) { // Prevent these matrix files from being requested for every trifling event // by giving them an explicit cache lifetime requestFile(req, res, "/well-known" + unescape(makeUrlSafe(req, 12)), req.url, {mime: "application/json", cors: true, cache: "public, max-age=7200"}); }); app.get("/.well-known/*", function (req, res) { requestFile(req, res, "/well-known" + unescape(makeUrlSafe(req, 12)), req.url); }); app.get("/google*.html", function (req, res) { const url = unescape(makeUrlSafe(req, 0)); if (url.match(/^\/google[0-9a-fA-F]*\.html$/)) { const fname = __dirname + url; fs.exists(fname, fs.constants.R_OK, err => { if (err) res.status(400).send("That file does not exist."); else res.sendFile(fname); }); } else { res.status(404).send("That file does not exist."); } }); ["o", "k", "rip", "rip2"].forEach(function (target) { app.get("/" + target, function (req, res) { res.sendFile(cmn.webfilesdir + "/" + target + ".html"); }); app.get("/" + target + "/*", function (req, res) { res.set('Content-Type', 'text/html'); const msg = url.parse(req.url).pathname.slice(target.length + 2); res.send( String(fs.readFileSync(cmn.webfilesdir + "/" + target + ".html")) .replace("", cmn.simpleHTMLescape(msg)) ); }); }); app.get("/dr", function (req, res) { res.sendFile(cmn.webfilesdir + "/duckroll.html"); }); app.get(["/gpg", "/pgp", "/gpg.asc", "/pgp.asc"], function (req, res) { res.type("text/plain"); res.sendFile(cmn.webfilesdir + "/pgp.asc"); }); app.get("/tomsg", function (req, res) { res.sendFile(cmn.webfilesdir + "/tomsg.html"); }); app.get("/goioi", function (req, res) { res.sendFile(cmn.webfilesdir + "/goioi.html"); }); // app.get("/chat", function (req, res) { // res.redirect(301, "http://tomsmeding.com:81"); // }); // Error-handling middleware app.use(function (err, req, res, next) { const id = new Date().getTime().toString() + Math.random().toFixed(10).slice(2); console.error("Error caught in app: (id=" + id + ")"); console.error(err); console.error(err.stack); res.status(500).end("An internal error occurred; it has been logged with id " + id + ".\n"); }); const server = httpServer.listen(PORT, function () { const host = server.address().address; const port = server.address().port; console.log("Server listening at http://" + host + ":" + port); }); // vim: set sw=4 ts=4 noet: