#!/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");
mod(app, io, cmn.serverdir + "/modules/" + module_list[i]);
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 ("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;
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 + "");
}
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 }
// (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"] = "*";
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) {
requestFile(req, res, "/well-known" + unescape(makeUrlSafe(req, 12)), req.url, {mime: "application/json", cors: true});
});
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: