Compare commits

..

1 Commits

Author SHA1 Message Date
17320ee1dd Simplify for Butlersaurus
All checks were successful
TerribleCodeClub/ButlerBin/pipeline/head This commit looks good
2020-06-08 22:59:10 +01:00
6 changed files with 116 additions and 96 deletions

View File

@ -1,9 +1,10 @@
{ {
"host": "0.0.0.0", "host": "0.0.0.0",
"port": 7777, "port": 7777,
"keyLength": 5, "keyLength": 10,
"keySpace": "abcdefghijklmnopqrstuvwxyz0123456789", "maxLength": 400000,
"staticMaxAge": 86400, "staticMaxAge": 86400,
"recompressStaticAssets": true,
"logging": [ "logging": [
{ {
"level": "verbose", "level": "verbose",

View File

@ -1,102 +1,119 @@
var Busboy = require('busboy'); var Busboy = require('busboy');
var Winston = require('winston'); var Winston = require('winston');
// For handling serving stored documents. // For handling serving stored documents
var DocumentHandler = function(options = {}) { var DocumentHandler = function(options) {
if (!options) {
options = {};
}
this.keyLength = options.keyLength || DocumentHandler.defaultKeyLength;
this.maxLength = options.maxLength; // none by default
this.store = options.store; this.store = options.store;
this.keyGenerator = options.keyGenerator; this.keyGenerator = options.keyGenerator;
}; };
DocumentHandler.defaultKeyLength = 10;
// Handle retrieving a document. // Handle retrieving a document
DocumentHandler.prototype.handleGet = function(key, response) { DocumentHandler.prototype.handleGet = function(key, response) {
this.store.get(key, function(ret) { this.store.get(key, function(ret) {
if (ret) { if (ret) {
Winston.verbose("Retrieved document.", { key: key }); Winston.verbose('retrieved document', { key: key });
response.writeHead(200, { "content-type": "application/json" }); response.writeHead(200, { 'content-type': 'application/json' });
response.end(JSON.stringify({ data: ret, key: key })); response.end(JSON.stringify({ data: ret, key: key }));
} }
else { else {
Winston.warn("Document not found.", { key: key }); Winston.warn('document not found', { key: key });
response.writeHead(404, { "content-type": "application/json" }); response.writeHead(404, { 'content-type': 'application/json' });
response.end(JSON.stringify({ message: "Document not found." })); response.end(JSON.stringify({ message: 'Document not found.' }));
} }
}); });
}; };
// Handle retrieving the raw version of a document. // Handle retrieving the raw version of a document
DocumentHandler.prototype.handleRawGet = function(key, response) { DocumentHandler.prototype.handleRawGet = function(key, response) {
this.store.get(key, function(ret) { this.store.get(key, function(ret) {
if (ret) { if (ret) {
Winston.verbose("Retrieved raw document.", { key: key }); Winston.verbose('retrieved raw document', { key: key });
response.writeHead(200, { "content-type": "text/plain; charset=UTF-8" }); response.writeHead(200, { 'content-type': 'text/plain; charset=UTF-8' });
response.end(ret); response.end(ret);
} }
else { else {
Winston.warn("Raw document not found.", { key: key }); Winston.warn('raw document not found', { key: key });
response.writeHead(404, { "content-type": "application/json" }); response.writeHead(404, { 'content-type': 'application/json' });
response.end(JSON.stringify({ message: "Document not found." })); response.end(JSON.stringify({ message: 'Document not found.' }));
} }
}); });
}; };
// Handle adding a new document. // Handle adding a new Document
DocumentHandler.prototype.handlePost = function (request, response) { DocumentHandler.prototype.handlePost = function (request, response) {
var _this = this; var _this = this;
var buffer = ""; var buffer = '';
var cancelled = false; var cancelled = false;
// If success... // What to do when done
var onSuccess = function () { var onSuccess = function () {
// Check length
if (_this.maxLength && buffer.length > _this.maxLength) {
cancelled = true;
Winston.warn('document >maxLength', { maxLength: _this.maxLength });
response.writeHead(400, { 'content-type': 'application/json' });
response.end(
JSON.stringify({ message: 'Document exceeds maximum length.' })
);
return;
}
// And then save if we should
_this.chooseKey(function (key) { _this.chooseKey(function (key) {
_this.store.set(key, buffer, function (res) { _this.store.set(key, buffer, function (res) {
if (res) { if (res) {
Winston.verbose("Added document.", { key: key }); Winston.verbose('added document', { key: key });
response.writeHead(200, { "content-type": "application/json" }); response.writeHead(200, { 'content-type': 'application/json' });
response.end(JSON.stringify({ key: key })); response.end(JSON.stringify({ key: key }));
} }
else { else {
Winston.verbose("Error adding document"); Winston.verbose('error adding document');
response.writeHead(500, { "content-type": "application/json" }); response.writeHead(500, { 'content-type': 'application/json' });
response.end(JSON.stringify({ message: "Error adding document." })); response.end(JSON.stringify({ message: 'Error adding document.' }));
} }
}); });
}); });
}; };
// Parse form to grab the data. // If we should, parse a form to grab the data
var ct = request.headers["content-type"]; var ct = request.headers['content-type'];
if (ct && ct.split(";")[0] === "multipart/form-data") { if (ct && ct.split(';')[0] === 'multipart/form-data') {
var busboy = new Busboy({ headers: request.headers }); var busboy = new Busboy({ headers: request.headers });
busboy.on("field", function (fieldname, val) { busboy.on('field', function (fieldname, val) {
if (fieldname === "data") { if (fieldname === 'data') {
buffer = val; buffer = val;
} }
}); });
busboy.on("finish", function () { busboy.on('finish', function () {
onSuccess(); onSuccess();
}); });
request.pipe(busboy); request.pipe(busboy);
// Otherwise, use our own and just grab flat data from POST body. // Otherwise, use our own and just grab flat data from POST body
} else { } else {
request.on("data", function (data) { request.on('data', function (data) {
buffer += data.toString(); buffer += data.toString();
}); });
request.on("end", function () { request.on('end', function () {
if (cancelled) { return; } if (cancelled) { return; }
onSuccess(); onSuccess();
}); });
request.on("error", function (error) { request.on('error', function (error) {
Winston.error("Connection error: " + error.message); Winston.error('connection error: ' + error.message);
response.writeHead(500, { "content-type": "application/json" }); response.writeHead(500, { 'content-type': 'application/json' });
response.end(JSON.stringify({ message: "Connection error." })); response.end(JSON.stringify({ message: 'Connection error.' }));
cancelled = true; cancelled = true;
}); });
} }
}; };
// Keep choosing keys until we find one that isn't taken. // Keep choosing keys until one isn't taken
DocumentHandler.prototype.chooseKey = function(callback) { DocumentHandler.prototype.chooseKey = function(callback) {
var key = this.acceptableKey(); var key = this.acceptableKey();
var _this = this; var _this = this;
@ -106,11 +123,11 @@ DocumentHandler.prototype.chooseKey = function(callback) {
} else { } else {
callback(key); callback(key);
} }
}, true); // Don't bump expirations when key searching. }, true); // Don't bump expirations when key searching
}; };
DocumentHandler.prototype.acceptableKey = function() { DocumentHandler.prototype.acceptableKey = function() {
return this.keyGenerator.createKey(); return this.keyGenerator.createKey(this.keyLength);
}; };
module.exports = DocumentHandler; module.exports = DocumentHandler;

View File

@ -1,23 +1,23 @@
var fs = require("fs"); var fs = require('fs');
var crypto = require("crypto"); var crypto = require('crypto');
var FileDocumentStore = function() { var FileDocumentStore = function() {
this.basePath = "/data"; this.basePath = '/data';
this.expire = null; this.expire = null;
}; };
FileDocumentStore.md5 = function(str) { FileDocumentStore.md5 = function(str) {
var md5sum = crypto.createHash("md5"); var md5sum = crypto.createHash('md5');
md5sum.update(str); md5sum.update(str);
return md5sum.digest("hex"); return md5sum.digest('hex');
}; };
FileDocumentStore.prototype.set = function(key, data, callback) { FileDocumentStore.prototype.set = function(key, data, callback) {
try { try {
var _this = this; var _this = this;
fs.mkdir(this.basePath, "700", function() { fs.mkdir(this.basePath, '700', function() {
var fn = _this.basePath + "/" + FileDocumentStore.md5(key); var fn = _this.basePath + '/' + FileDocumentStore.md5(key);
fs.writeFile(fn, data, "utf8", function(err) { fs.writeFile(fn, data, 'utf8', function(err) {
if (err) { if (err) {
callback(false); callback(false);
} }
@ -33,8 +33,8 @@ FileDocumentStore.prototype.set = function(key, data, callback) {
FileDocumentStore.prototype.get = function(key, callback) { FileDocumentStore.prototype.get = function(key, callback) {
var _this = this; var _this = this;
var fn = _this.basePath + "/" + FileDocumentStore.md5(key); var fn = _this.basePath + '/' + FileDocumentStore.md5(key);
fs.readFile(fn, "utf8", function(err, data) { fs.readFile(fn, 'utf8', function(err, data) {
if (err) { if (err) {
callback(false); callback(false);
} }

View File

@ -1,18 +1,19 @@
module.exports = class KeyGenerator { module.exports = class KeyGenerator {
// Initialise a new generator with the given key space. // Initialize a new generator with the given keySpace
constructor(options = {}) { constructor() {
this.keyLength = options.keyLength; this.keyspace = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
this.keySpace = options.keySpace;
} }
// Generate a key of the given length. // Generate a key of the given length
createKey() { createKey(keyLength) {
var text = ""; var text = '';
for (var i = 0; i < this.keyLength; i++) {
const index = Math.floor(Math.random() * this.keySpace.length); for (var i = 0; i < keyLength; i++) {
text += this.keySpace.charAt(index); const index = Math.floor(Math.random() * this.keyspace.length);
text += this.keyspace.charAt(index);
} }
return text; return text;
} }

View File

@ -18,6 +18,9 @@
"connect": "3.4.1", "connect": "3.4.1",
"st": "1.1.0", "st": "1.1.0",
"winston": "0.6.2", "winston": "0.6.2",
"redis-url": "0.1.0",
"redis": "0.8.1",
"uglify-js": "3.1.6",
"busboy": "0.2.4", "busboy": "0.2.4",
"pg": "4.1.1" "pg": "4.1.1"
}, },

View File

@ -1,25 +1,25 @@
var Connect = require("connect"); var Connect = require('connect');
var ConnectSt = require("st"); var ConnectSt = require('st');
var Fs = require("fs"); var Fs = require('fs');
var Http = require("http"); var Http = require('http');
var Route = require("connect-route"); var Route = require('connect-route');
var Winston = require("winston"); var Winston = require('winston');
var DocumentHandler = require("./lib/document_handler"); var DocumentHandler = require('./lib/document_handler');
var KeyGenerator = require("./lib/key_generator"); var KeyGenerator = require('./lib/key_generator');
var Store = require("./lib/document_store"); var Store = require('./lib/document_store');
// Load the configuration and set some defaults. // Load the configuration and set some defaults
var config = JSON.parse(Fs.readFileSync("./config.js", "utf8")); var config = JSON.parse(Fs.readFileSync('./config.js', 'utf8'));
config.host = config.host || "127.0.0.1"; config.host = config.host || '127.0.0.1';
config.port = config.port || 7777; config.port = config.port || 7777;
// Set up the logger. // Set up the logger
if (config.logging) { if (config.logging) {
try { try {
Winston.remove(Winston.transports.Console); Winston.remove(Winston.transports.Console);
} catch(e) { } catch(e) {
// This is fine. // This is fine
} }
var detail, type; var detail, type;
for (var i = 0; i < config.logging.length; i++) { for (var i = 0; i < config.logging.length; i++) {
@ -30,62 +30,60 @@ if (config.logging) {
} }
} }
// Configure the data store. // Configure the data store
store = new Store(); store = new Store();
// Pick up a key generator. // Pick up a key generator
var keyGenerator = new KeyGenerator({ var keyGenerator = new KeyGenerator();
keyLength: config.keyLength,
keySpace: config.keySpace
});
// Configure the document handler. // Configure the document handler
var documentHandler = new DocumentHandler({ var documentHandler = new DocumentHandler({
store: store, store: store,
maxLength: config.maxLength,
keyLength: config.keyLength, keyLength: config.keyLength,
keyGenerator: keyGenerator keyGenerator: keyGenerator
}); });
var app = Connect(); var app = Connect();
// Configure the API routes. // API
app.use(Route(function(router) { app.use(Route(function(router) {
router.get("/raw/:id", function(request, response) { router.get('/raw/:id', function(request, response) {
var key = request.params.id.split(".")[0]; var key = request.params.id.split('.')[0];
return documentHandler.handleRawGet(key, response); return documentHandler.handleRawGet(key, response);
}); });
router.post("/documents", function(request, response) { router.post('/documents', function(request, response) {
return documentHandler.handlePost(request, response); return documentHandler.handlePost(request, response);
}); });
router.get("/documents/:id", function(request, response) { router.get('/documents/:id', function(request, response) {
var key = request.params.id.split(".")[0]; var key = request.params.id.split('.')[0];
return documentHandler.handleGet(key, response); return documentHandler.handleGet(key, response);
}); });
})); }));
// Route static files. // Static files
app.use(ConnectSt({ app.use(ConnectSt({
path: __dirname + "/static", path: __dirname + '/static',
content: { maxAge: config.staticMaxAge }, content: { maxAge: config.staticMaxAge },
passthrough: true, passthrough: true,
index: false index: false
})); }));
// All the pastebin IDs. // All the pastebin IDs
app.use(Route(function(router) { app.use(Route(function(router) {
router.get("/:id", function(request, response, next) { router.get('/:id', function(request, response, next) {
request.sturl = "/"; request.sturl = '/';
next(); next();
}); });
})); }));
// Route index.html. // Index
app.use(ConnectSt({ app.use(ConnectSt({
path: __dirname + "/static", path: __dirname + '/static',
content: { maxAge: config.staticMaxAge }, content: { maxAge: config.staticMaxAge },
index: "index.html" index: 'index.html'
})); }));
Http.createServer(app).listen(config.port, config.host); Http.createServer(app).listen(config.port, config.host);
Winston.info("Listening on " + config.host + ":" + config.port); Winston.info('Listening on ' + config.host + ':' + config.port);