123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363 |
- var querystring = require("querystring");
- var fs = require("fs");
- var pathlib = require("path");
- var formidable = require("formidable");
- var crypto = require("crypto");
-
- var basepath = "/admin/api/";
- var slideshow;
-
- exports.init = function(_slideshow) {
- slideshow = _slideshow;
- }
-
- var tokens = {};
-
- function pad(str, n) {
- if (str.length >= n)
- return str;
-
- var missing = str.length;
- for (var i = 0; i < n - missing; ++i)
- str = "0" + str;
-
- return str;
- }
-
- // Used in every method handler to make sure the correct arguments are provided
- function hasargs(query, respond, expected) {
- var missing = [];
- for (var e of expected) {
- if (query[e] === undefined)
- missing.push(e);
- }
-
- if (missing.length > 0) {
- respond("Missing arguments: "+missing.join(", "));
- return false;
- } else {
- return true;
- }
- }
-
- // Method handlers are defined here
- var methods = {
-
- // List all slides in the slides directory
- list_slides: function(query, conf, req, respond) {
- fs.readdir(conf.slides, (err, files) => {
- if (err)
- return respond(err);
-
- respond(null, files);
- });
- },
-
- // Get metadata about a slide
- slide_meta: function(query, conf, req, respond) {
- if (!hasargs(query, respond, [ "slide" ])) return;
-
- var path = pathlib.join(conf.slides, query.slide);
- fs.stat(path, (err, stat) => {
- if (err || !stat.isDirectory())
- return respond(path+" is not a slide.");
-
- fs.readFile(pathlib.join(path, "meta.json"), (err, res) => {
- if (err && err.code === "ENOENT")
- return respond(null, {});
- else if (err)
- return respond(err);
-
- try {
- respond(null, JSON.parse(res));
- } catch (err) {
- respond(err);
- }
- });
- });
- },
-
- // Get a list of files of a slide
- slide_file_list: function(query, conf, req, respond) {
- if (!hasargs(query, respond, [ "slide" ])) return;
-
- var dir = pathlib.join(conf.slides, query.slide);
- fs.readdir(dir, (err, files) => {
- if (err)
- return respond(err);
-
- respond(null, { files: files });
- });
- },
-
- // Get a slide's HTML
- slide_content: function(query, conf, req, respond) {
- if (!hasargs(query, respond, [ "slide" ])) return;
-
- var path = pathlib.join(conf.slides, query.slide, "index.md");
- fs.readFile(path, "utf-8", (err, text) => {
- if (err && err.code === "ENOENT")
- return respond(null, { text: "" });
- else if (err)
- return respond(err);
-
- respond(null, { text: text });
- });
- },
-
- // Update a slide's HTML
- slide_content_update: function(query, conf, req, respond) {
- if (!hasargs(query, respond, [ "slide", "text" ])) return;
-
- var path = pathlib.join(conf.slides, query.slide, "index.md");
- fs.writeFile(path, query.text, err => {
- respond(err);
- });
- },
-
- // Rename a file
- slide_file_rename: function(query, conf, req, respond) {
- if (!hasargs(query, respond, [ "slide", "from", "to" ])) return;
-
- var op = pathlib.join(conf.slides, query.slide, query.from);
- var np = pathlib.join(conf.slides, query.slide, query.to);
-
- fs.rename(op, np, err => respond(err));
- },
-
- // Delete a file
- slide_file_delete: function(query, conf, req, respond) {
- if (!hasargs(query, respond, [ "slide", "file" ])) return;
-
- var path = pathlib.join(conf.slides, query.slide, query.file);
- fs.unlink(path, err => respond(err));
- },
-
- // Upload a file to a slide
- slide_file_upload: function(query, conf, req, respond) {
- if (!hasargs(query, respond, [ "slide" ])) return;
-
- var form = new formidable.IncomingForm();
- form.uploadDir = pathlib.join(conf.slides, query.slide);
- form.keepExtensions = true;
-
- form.on("fileBegin", (name, file) => {
- file.path = pathlib.join(form.uploadDir, file.name);
- });
-
- form.parse(req, (err, fields, files) => {
- if (err)
- return respond(err);
- });
-
- form.on("error", err => {
- respond(err);
- });
-
- form.on("end", () => {
- respond();
- });
- },
-
- // Set a meta property for slide
- // Synchronous fs stuff, we don't want races
- slide_set_meta: function(query, conf, req, respond) {
- if (!hasargs(query, respond, [ "slide", "key", "val" ])) return;
-
- var metafile = pathlib.join(conf.slides, query.slide, "meta.json");
- var meta = {};
- try {
- meta = JSON.parse(fs.readFileSync(metafile));
- } catch (err) {
- if (err.code !== "ENOENT")
- return respond(err);
- }
-
- meta[query.key] = JSON.parse(query.val);
-
- try {
- fs.writeFileSync(metafile, JSON.stringify(meta, null, 4)+"\n");
- } catch (err) {
- respond(err);
- }
-
- slideshow.updateSlides();
-
- respond();
- },
-
- // Get meta properties
- slide_get_meta: function(query, conf, req, respond) {
- if (!hasargs(query, respond, [ "slide" ])) return;
-
- var metafile = pathlib.join(conf.slides, query.slide, "meta.json");
- fs.readFile(metafile, (err, res) => {
- if (err && err.code !== "ENOENT")
- return respond(err);
-
- var obj = {};
- if (!err) {
- obj = JSON.parse(res);
- }
-
- respond(null, obj);
- });
- },
-
- // Create a slide
- // Lots of synchronous fs stuff, we don't want races
- slide_create: function(query, conf, req, respond) {
- var dirs;
- try {
- dirs = fs.readdirSync(conf.slides);
- } catch (err) {
- return respond(err);
- }
-
- dirs = dirs.sort();
- var biggest = dirs[dirs.length - 1];
- var newId = pad((parseInt(biggest) + 1).toString(), biggest.length);
-
- var path = pathlib.join(conf.slides, newId);
-
- var meta = JSON.stringify({
- disabled: true
- }, null, 4) + "\n";
-
- try {
- fs.mkdirSync(path);
- fs.writeFileSync(pathlib.join(path, "index.md"), "");
- fs.writeFileSync(pathlib.join(path, "meta.json"), meta);
- } catch (err) {
- return respond(err);
- }
-
- slideshow.updateSlides();
-
- respond(null, newId);
- },
-
- // Delete a slide
- // Also synchronous fs stuff
- slide_delete: function(query, conf, req, respond) {
- if (!hasargs(query, respond, [ "slide" ])) return;
-
- var path = pathlib.join(conf.slides, query.slide);
- var files;
- try {
- files = fs.readdirSync(path);
- } catch (err) {
- return respond(err);
- }
-
- for (var f of files) {
- try {
- fs.unlinkSync(pathlib.join(path, f));
- } catch (err) {
- return respond(err);
- }
- }
-
- try {
- fs.rmdirSync(path);
- } catch (err) {
- return respond(err);
- }
-
- respond();
- }
- }
-
- exports.canServe = function(parts) {
-
- // Temporary, while working on stuff
- var name = parts.pathname.replace(basepath, "");
- return methods[name] !== undefined || name === "login";
- }
-
- var sessTokens = [];
- function loginHandler(conf, req, respond) {
- var pass = req.headers["session-pass"];
- if (!conf.password)
- return respond(null, false);
- if (!pass)
- return respond(null, false);
- if (pass !== conf.password)
- return respond(null, false);
-
- var token = crypto.randomBytes(16).toString("hex");
- var id = sessTokens.length;
- sessTokens[id] = token;
-
- // Time out after 30 minutes
- setTimeout(() => {
- sessTokens[id] = undefined;
- }, 30 * 60 * 1000);
-
- respond(null, token);
- }
- function validateToken(req) {
- var cookie = req.headers.cookie;
- if (!cookie)
- return false;
-
- var token;
- for (var c of cookie.split(/;\s*/)) {
- var parts = c.split("=");
- if (parts[0] === "token") {
- token = parts[1];
- break;
- }
- }
- if (!token)
- return false;
-
- for (var i = 0; i < sessTokens.length; ++i) {
- if (sessTokens[i] && sessTokens[i] === token)
- return true;
- }
-
- return false;
- }
-
- exports.serve = function(parts, conf, req, res) {
- var name = parts.pathname.replace(basepath, "");
-
- // Better than manually doing res.end(JSON.stringify(obj)) everywhere
- function respond(err, obj) {
- var result = {
- obj: obj,
- err: err ? err.toString() : null
- };
-
- if (err)
- res.writeHead(400);
- else
- res.writeHead(200);
-
- res.end(JSON.stringify(result));
- }
-
- // Special login handler
- if (name === "login")
- return loginHandler(conf, req, respond);
-
- // Verify token
- if (!validateToken(req))
- return respond("EINVALTOKEN");
-
- var fn = methods[name];
- if (!fn) {
- res.writeHead(404);
- res.end();
- return;
- }
-
- var query = querystring.parse(parts.query);
- for (var i in query) {
- query[i] = decodeURIComponent(query[i]);
- }
-
- // Finally, call method handler
- fn(query, conf, req, respond);
- }
|