#!/usr/bin/env node

var http = require("http")
  , path = require("path")
  , url = require("url")
  , fs = require("fs")
  , port = process.env.PORT || 8888
  , ip = process.env.IP || "0.0.0.0";

function lookupMime(filename) {
    var ext = /[^\/\\.]*$/.exec(filename)[0];
    return {
        js: "application/javascript",
        ico: "image/x-icon",
        css: "text/css",
        svg: "image/svg+xml",
        png: "image/png",
        jpg: "image/jpg",
        html: "text/html",
        jpeg: "image/jpeg",
        wasm: "application/wasm",
        json: "application/json",
        xml: "application/xml",
        ttf: "font/ttf",
    }[ext];
}
  
// compatibility with node 0.6
if (!fs.exists)
    fs.exists = path.exists;

var allowSave = process.argv.indexOf("--allow-save") != -1;
if (allowSave)
    console.warn("writing files from browser is enabled");

http.createServer(function(req, res) {
    var uri = unescape(url.parse(req.url).pathname);
    console.log("[" + req.method + "]", uri);
    var root = process.cwd() + path.sep;
    var filename = path.join(root, uri);

    if (filename.slice(0, root.length) !== root) {
        return error(res, 400, "400 Bad request: Directory traversal is not allowed.");
    }

    if (req.method == "OPTIONS") {
        writeHead(res, 200);
        return res.end();
    }
    
    if (req.method == "PUT") {
        if (!allowSave)
            return error(res, 404, "Saving not allowed (pass --allow-save to enable)");
        return save(req, res, filename);
    }

    fs.stat(filename, function(err, stat) {
        if (err)
            return error(res, 404, "404 Not Found\n" + filename);

        if (stat.isDirectory())
            return serveDirectory(filename, uri, req, res);

        var contentType = lookupMime(filename);
        writeHead(res, 200, contentType);
        res.setHeader("Content-Length", stat.size);
        var stream = fs.createReadStream(filename, {});
        req.on("close", stream.destroy.bind(stream));
        stream.on("error", function(err){
            if (res._header)
                return req.destroy();
            writeHead(res, 500, "text/plain");
            res.end();
        });
        stream.pipe(res);
    });
}).listen(port, ip);

function writeHead(res, code, contentType) {
    res.setHeader("Access-Control-Allow-Origin", "file://");
    res.setHeader("Access-Control-Allow-Methods", "PUT, GET, OPTIONS, HEAD");
    res.setHeader("Content-Type", contentType || "text/plain");
    res.statusCode = code;
}

function escapeHTML(str) {
    return ("" + str).replace(/&/g, "&#38;").replace(/"/g, "&#34;").replace(/'/g, "&#39;").replace(/</g, "&#60;");
}
function serveDirectory(filename, uri, req, res) {
    var files = fs.readdirSync(filename);
    writeHead(res, 200, "text/html");
    
    files.push("..");
    var html = files.map(function(name) {
        try {
            var stat = fs.statSync(filename + "/" + name);
        } catch(e) {}
        var index = name == "." ? 2 : name == ".." ? 3 : stat.isDirectory() ? 1 : 0;
        return { name: name, index: index, size: stat.size };
    }).filter(Boolean).sort(function(a, b) {
        if (a.index == b.index)
            return a.name.localeCompare(b.name);
        return b.index - a.index;
    }).map(function(stat) {
        var name = stat.name;
        if (stat.index) name += "/";
        var size = "";
        if (!stat.size) size = "";
        else if (stat.size < 1024) size = stat.size + "b";
        else if (stat.size < 1024 * 1024) size = (stat.size / 1024).toFixed(2) + "kb";
        else size = (stat.size / 1024 / 1024).toFixed(2) + "mb";
        return "<tr>"
            + "<td>&nbsp;&nbsp;" + size + "&nbsp;&nbsp;</td>"
            + "<td><a href='" + escapeHTML(name) + "'>" + escapeHTML(name) + "</a></td>"
        + "</tr>";
    }).join("");
    html = "<table>" + html + "</table>";

    var baseUri = uri.replace(/\/?$/, "/");
    html = "<base href='"+ escapeHTML(baseUri) + "'>" 
        + "<meta charset='utf-8'>" + html;
    res._hasBody && res.write(html);
    res.end();
}

function error(res, status, message, error) {
    console.error(error || message);
    writeHead(res, status, "text/plain");
    res.write(message);
    res.end();
}

function save(req, res, filePath) {
    var data = "";
    req.on("data", function(chunk) {
        data += chunk;
    });
    req.on("error", function() {
        error(res, 404, "Could't save file");
    });
    req.on("end", function() {
        try {
            fs.writeFileSync(filePath, data);
        }
        catch (e) {
            return error(res, 404, "Could't save file", e);
        }
        writeHead(res, 200);
        res.end("OK");
        console.log("saved ", filePath);
    });
}

function getLocalIps() {
    var os = require("os");

    var interfaces = os.networkInterfaces ? os.networkInterfaces() : {};
    var addresses = [];
    for (var k in interfaces) {
        for (var k2 in interfaces[k]) {
            var address = interfaces[k][k2];
            if (address.family === "IPv4" && !address.internal) {
                addresses.push(address.address);
            }
        }
    }
    return addresses;
}

console.log("http://" + (ip == "0.0.0.0" ? getLocalIps()[0] : ip) + ":" + port);

