Compare commits

...

2 Commits

Author SHA1 Message Date
Jack Hadrill f35eae30d5 Add MongoDB support to Hastebin.
TerribleCodeClub/ButlerBin/pipeline/head This commit looks good Details
2020-06-09 01:26:48 +01:00
Jack Hadrill a21a6c595a Simplify for Butlersaurus
TerribleCodeClub/ButlerBin/pipeline/head This commit looks good Details
2020-06-08 23:36:02 +01:00
24 changed files with 337 additions and 1308 deletions

5
Dockerfile Normal file
View File

@ -0,0 +1,5 @@
FROM node:alpine
WORKDIR /app
COPY . /app
RUN npm install
CMD [ "npm", "start" ]

0
Jenkinsfile vendored Normal file
View File

View File

@ -1,61 +0,0 @@
# Haste
Sharing code is a good thing, and it should be _really_ easy to do it.
A lot of times, I want to show you something I'm seeing - and that's where we
use pastebins.
Haste is the prettiest, easiest to use pastebin ever made.
## Basic Usage
Type what you want me to see, click "Save", and then copy the URL. Send that
URL to someone and they'll see what you see.
To make a new entry, click "New" (or type 'control + n')
## From the Console
Most of the time I want to show you some text, it's coming from my current
console session. We should make it really easy to take code from the console
and send it to people.
`cat something | haste` # https://hastebin.com/1238193
You can even take this a step further, and cut out the last step of copying the
URL with:
* osx: `cat something | haste | pbcopy`
* linux: `cat something | haste | xsel`
* windows: check out [WinHaste](https://github.com/ajryan/WinHaste)
After running that, the STDOUT output of `cat something` will show up at a URL
which has been conveniently copied to your clipboard.
That's all there is to that, and you can install it with `gem install haste`
right now.
* osx: you will need to have an up to date version of Xcode
* linux: you will need to have rubygems and ruby-devel installed
## Duration
Pastes will stay for 30 days from their last view. They may be removed earlier
and without notice.
## Privacy
While the contents of hastebin.com are not directly crawled by any search robot
that obeys "robots.txt", there should be no great expectation of privacy. Post
things at your own risk. Not responsible for any loss of data or removed
pastes.
## Open Source
Haste can easily be installed behind your network, and it's all open source!
* [haste-client](https://github.com/seejohnrun/haste-client)
* [haste-server](https://github.com/seejohnrun/haste-server)
## Author
Code by John Crepezzi <john.crepezzi@gmail.com>
Key Design by Brian Dawson <bridawson@gmail.com>

View File

@ -1,46 +1,18 @@
{ {
"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,
"mongodbUsername": "ButlerBot",
"recompressStaticAssets": true, "mongodbPassword": "YzSwi2j2Pp6m5ln0",
"mongodbDbName": "butler_db",
"mongodbCollectionName": "pastes-test",
"logging": [ "logging": [
{ {
"level": "verbose", "level": "verbose",
"type": "Console", "type": "Console",
"colorize": true "colorize": true
} }
], ]
"keyGenerator": {
"type": "phonetic"
},
"rateLimits": {
"categories": {
"normal": {
"totalRequests": 500,
"every": 60000
}
}
},
"storage": {
"type": "memcached",
"host": "127.0.0.1",
"port": 11211,
"expire": 2592000
},
"documents": {
"about": "./about.md"
}
} }

View File

@ -1,119 +1,102 @@
var winston = require('winston');
var Busboy = require('busboy'); var Busboy = require('busboy');
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, skipExpire) { 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." }));
} }
}, skipExpire); });
}; };
// Handle retrieving the raw version of a document // Handle retrieving the raw version of a document.
DocumentHandler.prototype.handleRawGet = function(key, response, skipExpire) { 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." }));
} }
}, skipExpire); });
}; };
// 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;
// What to do when done // If success...
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." }));
} }
}); });
}); });
}; };
// If we should, parse a form to grab the data // Parse 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 one isn't taken // Keep choosing keys until we find one that 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;
@ -123,11 +106,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(this.keyLength); return this.keyGenerator.createKey();
}; };
module.exports = DocumentHandler; module.exports = DocumentHandler;

62
lib/document_store.js Normal file
View File

@ -0,0 +1,62 @@
var MongoClient = require("mongodb").MongoClient
var Winston = require("winston")
class MongoDocumentStore {
constructor(options) {
this.connectionURL = `mongodb+srv://${options.username}:${options.password}@cluster0-vycgu.mongodb.net/${options.dbName}?retryWrites=true&w=majority`;
this.dbName = options.dbName;
this.collectionName = options.collectionName;
}
set(key, data, callback) {
var _this = this;
const client = new MongoClient(this.connectionURL, { useUnifiedTopology: true, useNewUrlParser: true });
client.connect(err => {
if (err) {
callback(false);
Winston.error("Error connecting to MongoDB database.", { error: err });
return;
}
const db = client.db(_this.dbName);
const collection = db.collection(_this.collectionName);
collection.insertOne({key: key, data: data}, (err, result) => {
if (err) {
callback(false);
Winston.error("Error inserting data into MongoDB database.", { error: error });
return;
}
Winston.info("Inserted data into MongoDB database.");
callback(true);
});
});
}
get(key, callback) {
var _this = this;
MongoClient.connect(this.connectionURL, { useUnifiedTopology: true }, function(err, client) {
if (err) {
callback(false);
Winston.error("Error connecting to MongoDB database.", { error: err });
return;
}
const db = client.db(_this.dbName);
const collection = db.collection(_this.collectionName);
collection.findOne({key: key}, (err, result) => {
if (err || !result) {
callback(false);
if (err) {
Winston.error("Error getting data from MongoDB database.", { error: error });
}
return;
}
Winston.info("Retrieved data from MongoDB database.");
callback(result.data);
});
});
}
}
module.exports = MongoDocumentStore;

View File

@ -1,56 +0,0 @@
/*global require,module,process*/
var AWS = require('aws-sdk');
var winston = require('winston');
var AmazonS3DocumentStore = function(options) {
this.expire = options.expire;
this.bucket = options.bucket;
this.client = new AWS.S3({region: options.region});
};
AmazonS3DocumentStore.prototype.get = function(key, callback, skipExpire) {
var _this = this;
var req = {
Bucket: _this.bucket,
Key: key
};
_this.client.getObject(req, function(err, data) {
if(err) {
callback(false);
}
else {
callback(data.Body.toString('utf-8'));
if (_this.expire && !skipExpire) {
winston.warn('amazon s3 store cannot set expirations on keys');
}
}
});
}
AmazonS3DocumentStore.prototype.set = function(key, data, callback, skipExpire) {
var _this = this;
var req = {
Bucket: _this.bucket,
Key: key,
Body: data,
ContentType: 'text/plain'
};
_this.client.putObject(req, function(err, data) {
if (err) {
callback(false);
}
else {
callback(true);
if (_this.expire && !skipExpire) {
winston.warn('amazon s3 store cannot set expirations on keys');
}
}
});
}
module.exports = AmazonS3DocumentStore;

View File

@ -1,63 +0,0 @@
var fs = require('fs');
var crypto = require('crypto');
var winston = require('winston');
// For storing in files
// options[type] = file
// options[path] - Where to store
var FileDocumentStore = function(options) {
this.basePath = options.path || './data';
this.expire = options.expire;
};
// Generate md5 of a string
FileDocumentStore.md5 = function(str) {
var md5sum = crypto.createHash('md5');
md5sum.update(str);
return md5sum.digest('hex');
};
// Save data in a file, key as md5 - since we don't know what we could
// be passed here
FileDocumentStore.prototype.set = function(key, data, callback, skipExpire) {
try {
var _this = this;
fs.mkdir(this.basePath, '700', function() {
var fn = _this.basePath + '/' + FileDocumentStore.md5(key);
fs.writeFile(fn, data, 'utf8', function(err) {
if (err) {
callback(false);
}
else {
callback(true);
if (_this.expire && !skipExpire) {
winston.warn('file store cannot set expirations on keys');
}
}
});
});
} catch(err) {
callback(false);
}
};
// Get data from a file from key
FileDocumentStore.prototype.get = function(key, callback, skipExpire) {
var _this = this;
var fn = this.basePath + '/' + FileDocumentStore.md5(key);
fs.readFile(fn, 'utf8', function(err, data) {
if (err) {
callback(false);
}
else {
callback(data);
if (_this.expire && !skipExpire) {
winston.warn('file store cannot set expirations on keys');
}
}
});
};
module.exports = FileDocumentStore;

View File

@ -1,52 +0,0 @@
const memcached = require('memcached');
const winston = require('winston');
class MemcachedDocumentStore {
// Create a new store with options
constructor(options) {
this.expire = options.expire;
const host = options.host || '127.0.0.1';
const port = options.port || 11211;
const url = `${host}:${port}`;
this.connect(url);
}
// Create a connection
connect(url) {
this.client = new memcached(url);
winston.info(`connecting to memcached on ${url}`);
this.client.on('failure', function(error) {
winston.info('error connecting to memcached', {error});
});
}
// Save file in a key
set(key, data, callback, skipExpire) {
this.client.set(key, data, skipExpire ? 0 : this.expire, (error) => {
callback(!error);
});
}
// Get a file from a key
get(key, callback, skipExpire) {
this.client.get(key, (error, data) => {
callback(error ? false : data);
// Update the key so that the expiration is pushed forward
if (!skipExpire) {
this.set(key, data, (updateSucceeded) => {
if (!updateSucceeded) {
winston.error('failed to update expiration on GET', {key});
}
}, skipExpire);
}
});
}
}
module.exports = MemcachedDocumentStore;

View File

@ -1,79 +0,0 @@
/*global require,module,process*/
var postgres = require('pg');
var winston = require('winston');
// create table entries (id serial primary key, key varchar(255) not null, value text not null, expiration int, unique(key));
// A postgres document store
var PostgresDocumentStore = function (options) {
this.expireJS = options.expire;
this.connectionUrl = process.env.DATABASE_URL || options.connectionUrl;
};
PostgresDocumentStore.prototype = {
// Set a given key
set: function (key, data, callback, skipExpire) {
var now = Math.floor(new Date().getTime() / 1000);
var that = this;
this.safeConnect(function (err, client, done) {
if (err) { return callback(false); }
client.query('INSERT INTO entries (key, value, expiration) VALUES ($1, $2, $3)', [
key,
data,
that.expireJS && !skipExpire ? that.expireJS + now : null
], function (err) {
if (err) {
winston.error('error persisting value to postgres', { error: err });
return callback(false);
}
callback(true);
done();
});
});
},
// Get a given key's data
get: function (key, callback, skipExpire) {
var now = Math.floor(new Date().getTime() / 1000);
var that = this;
this.safeConnect(function (err, client, done) {
if (err) { return callback(false); }
client.query('SELECT id,value,expiration from entries where KEY = $1 and (expiration IS NULL or expiration > $2)', [key, now], function (err, result) {
if (err) {
winston.error('error retrieving value from postgres', { error: err });
return callback(false);
}
callback(result.rows.length ? result.rows[0].value : false);
if (result.rows.length && that.expireJS && !skipExpire) {
client.query('UPDATE entries SET expiration = $1 WHERE ID = $2', [
that.expireJS + now,
result.rows[0].id
], function (err) {
if (!err) {
done();
}
});
} else {
done();
}
});
});
},
// A connection wrapper
safeConnect: function (callback) {
postgres.connect(this.connectionUrl, function (err, client, done) {
if (err) {
winston.error('error connecting to postgres', { error: err });
callback(err);
} else {
callback(undefined, client, done);
}
});
}
};
module.exports = PostgresDocumentStore;

View File

@ -1,89 +0,0 @@
var redis = require('redis');
var winston = require('winston');
// For storing in redis
// options[type] = redis
// options[host] - The host to connect to (default localhost)
// options[port] - The port to connect to (default 5379)
// options[db] - The db to use (default 0)
// options[expire] - The time to live for each key set (default never)
var RedisDocumentStore = function(options, client) {
this.expire = options.expire;
if (client) {
winston.info('using predefined redis client');
RedisDocumentStore.client = client;
} else if (!RedisDocumentStore.client) {
winston.info('configuring redis');
RedisDocumentStore.connect(options);
}
};
// Create a connection according to config
RedisDocumentStore.connect = function(options) {
var host = options.host || '127.0.0.1';
var port = options.port || 6379;
var index = options.db || 0;
RedisDocumentStore.client = redis.createClient(port, host);
// authenticate if password is provided
if (options.password) {
RedisDocumentStore.client.auth(options.password);
}
RedisDocumentStore.client.on('error', function(err) {
winston.error('redis disconnected', err);
});
RedisDocumentStore.client.select(index, function(err) {
if (err) {
winston.error(
'error connecting to redis index ' + index,
{ error: err }
);
process.exit(1);
}
else {
winston.info('connected to redis on ' + host + ':' + port + '/' + index);
}
});
};
// Save file in a key
RedisDocumentStore.prototype.set = function(key, data, callback, skipExpire) {
var _this = this;
RedisDocumentStore.client.set(key, data, function(err) {
if (err) {
callback(false);
}
else {
if (!skipExpire) {
_this.setExpiration(key);
}
callback(true);
}
});
};
// Expire a key in expire time if set
RedisDocumentStore.prototype.setExpiration = function(key) {
if (this.expire) {
RedisDocumentStore.client.expire(key, this.expire, function(err) {
if (err) {
winston.error('failed to set expiry on key: ' + key);
}
});
}
};
// Get a file from a key
RedisDocumentStore.prototype.get = function(key, callback, skipExpire) {
var _this = this;
RedisDocumentStore.client.get(key, function(err, reply) {
if (!err && !skipExpire) {
_this.setExpiration(key);
}
callback(err ? false : reply);
});
};
module.exports = RedisDocumentStore;

View File

@ -1,46 +0,0 @@
const crypto = require('crypto');
const rethink = require('rethinkdbdash');
const winston = require('winston');
const md5 = (str) => {
const md5sum = crypto.createHash('md5');
md5sum.update(str);
return md5sum.digest('hex');
};
class RethinkDBStore {
constructor(options) {
this.client = rethink({
silent: true,
host: options.host || '127.0.0.1',
port: options.port || 28015,
db: options.db || 'haste',
user: options.user || 'admin',
password: options.password || ''
});
}
set(key, data, callback) {
this.client.table('uploads').insert({ id: md5(key), data: data }).run((error) => {
if (error) {
callback(false);
winston.error('failed to insert to table', error);
return;
}
callback(true);
});
}
get(key, callback) {
this.client.table('uploads').get(md5(key)).run((error, result) => {
if (error || !result) {
callback(false);
if (error) winston.error('failed to insert to table', error);
return;
}
callback(result.data);
});
}
}
module.exports = RethinkDBStore;

19
lib/key_generator.js Normal file
View File

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

View File

@ -1,32 +0,0 @@
const fs = require('fs');
module.exports = class DictionaryGenerator {
constructor(options, readyCallback) {
// Check options format
if (!options) throw Error('No options passed to generator');
if (!options.path) throw Error('No dictionary path specified in options');
// Load dictionary
fs.readFile(options.path, 'utf8', (err, data) => {
if (err) throw err;
this.dictionary = data.split(/[\n\r]+/);
if (readyCallback) readyCallback();
});
}
// Generates a dictionary-based key, of keyLength words
createKey(keyLength) {
let text = '';
for (let i = 0; i < keyLength; i++) {
const index = Math.floor(Math.random() * this.dictionary.length);
text += this.dictionary[index];
}
return text;
}
};

View File

@ -1,27 +0,0 @@
// Draws inspiration from pwgen and http://tools.arantius.com/password
const randOf = (collection) => {
return () => {
return collection[Math.floor(Math.random() * collection.length)];
};
};
// Helper methods to get an random vowel or consonant
const randVowel = randOf('aeiou');
const randConsonant = randOf('bcdfghjklmnpqrstvwxyz');
module.exports = class PhoneticKeyGenerator {
// Generate a phonetic key of alternating consonant & vowel
createKey(keyLength) {
let text = '';
const start = Math.round(Math.random());
for (let i = 0; i < keyLength; i++) {
text += (i % 2 == start) ? randConsonant() : randVowel();
}
return text;
}
};

View File

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

527
package-lock.json generated
View File

@ -1,5 +1,5 @@
{ {
"name": "haste", "name": "butlerbin",
"version": "0.1.0", "version": "0.1.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
@ -10,75 +10,43 @@
"integrity": "sha1-D8GqoIig4+8Ovi2IMbqw3PiEUGE=" "integrity": "sha1-D8GqoIig4+8Ovi2IMbqw3PiEUGE="
}, },
"async-cache": { "async-cache": {
"version": "1.0.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/async-cache/-/async-cache-1.0.0.tgz", "resolved": "https://registry.npmjs.org/async-cache/-/async-cache-1.1.0.tgz",
"integrity": "sha1-yH9tgMcrOU7g+QYe3rJNjEtiKto=", "integrity": "sha1-SppaidBl7F2OUlS9nulrp2xTK1o=",
"requires": { "requires": {
"lru-cache": "2.3.1" "lru-cache": "^4.0.0"
} }
}, },
"balanced-match": { "base64-js": {
"version": "1.0.0", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
"dev": true
}, },
"bl": { "bl": {
"version": "1.0.3", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/bl/-/bl-1.0.3.tgz", "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.2.tgz",
"integrity": "sha1-/FQhoo/UImA2w7OJGmaiW8ZNIm4=", "integrity": "sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==",
"requires": { "requires": {
"readable-stream": "2.0.6" "buffer": "^5.5.0",
}, "inherits": "^2.0.4",
"dependencies": { "readable-stream": "^3.4.0"
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
},
"readable-stream": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz",
"integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=",
"requires": {
"core-util-is": "1.0.2",
"inherits": "2.0.3",
"isarray": "1.0.0",
"process-nextick-args": "1.0.7",
"string_decoder": "0.10.31",
"util-deprecate": "1.0.2"
}
}
} }
}, },
"brace-expansion": { "buffer": {
"version": "1.1.8", "version": "5.6.0",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz",
"integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==",
"dev": true,
"requires": { "requires": {
"balanced-match": "1.0.0", "base64-js": "^1.0.2",
"concat-map": "0.0.1" "ieee754": "^1.1.4"
} }
}, },
"browser-stdout": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz",
"integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=",
"dev": true
},
"buffer-writer": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-1.0.0.tgz",
"integrity": "sha1-bCnDst6gyeRVofJhoZmkigT4iwg="
},
"busboy": { "busboy": {
"version": "0.2.4", "version": "0.3.1",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.4.tgz", "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.3.1.tgz",
"integrity": "sha1-GXfpbh7ohGSWUevfVIypAHWLp/M=", "integrity": "sha512-y7tTxhGKXcyBxRKAni+awqx8uqaJKrSFSNFSeRG5CsWNdmy2BIK+6VGWEW7TZnIO/533mtMEA4rOevQV815YJw==",
"requires": { "requires": {
"dicer": "0.2.3", "dicer": "0.3.0"
"readable-stream": "1.1.14"
} }
}, },
"colors": { "colors": {
@ -86,248 +54,121 @@
"resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz",
"integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=" "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w="
}, },
"commander": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz",
"integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=",
"dev": true,
"requires": {
"graceful-readlink": "1.0.1"
}
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
},
"connect": { "connect": {
"version": "3.4.1", "version": "3.7.0",
"resolved": "https://registry.npmjs.org/connect/-/connect-3.4.1.tgz", "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz",
"integrity": "sha1-ohNh0/QJnvdhzabcSpc7seuwo00=", "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==",
"requires": { "requires": {
"debug": "2.2.0", "debug": "2.6.9",
"finalhandler": "0.4.1", "finalhandler": "1.1.2",
"parseurl": "1.3.1", "parseurl": "~1.3.3",
"utils-merge": "1.0.0" "utils-merge": "1.0.1"
} }
}, },
"connect-ratelimit": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/connect-ratelimit/-/connect-ratelimit-0.0.7.tgz",
"integrity": "sha1-5uCclQZJ6ElJnKsYcKQVoH9zFWg="
},
"connect-route": { "connect-route": {
"version": "0.1.5", "version": "0.1.5",
"resolved": "https://registry.npmjs.org/connect-route/-/connect-route-0.1.5.tgz", "resolved": "https://registry.npmjs.org/connect-route/-/connect-route-0.1.5.tgz",
"integrity": "sha1-48IYMZ0uiKiprgsOD+Cacpw5dEo=" "integrity": "sha1-48IYMZ0uiKiprgsOD+Cacpw5dEo="
}, },
"core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
},
"cycle": { "cycle": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz",
"integrity": "sha1-IegLK+hYD5i0aPN5QwZisEbDStI=" "integrity": "sha1-IegLK+hYD5i0aPN5QwZisEbDStI="
}, },
"debug": { "debug": {
"version": "2.2.0", "version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"requires": { "requires": {
"ms": "0.7.1" "ms": "2.0.0"
} }
}, },
"dicer": { "dicer": {
"version": "0.2.3", "version": "0.3.0",
"resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.3.tgz", "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz",
"integrity": "sha1-8AKBGJpVwjUe+ASQpP6fssWcSTk=", "integrity": "sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==",
"requires": { "requires": {
"readable-stream": "1.1.14",
"streamsearch": "0.1.2" "streamsearch": "0.1.2"
} }
}, },
"diff": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz",
"integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=",
"dev": true
},
"ee-first": { "ee-first": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
}, },
"encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
},
"escape-html": { "escape-html": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
}, },
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
"dev": true
},
"eyes": { "eyes": {
"version": "0.1.8", "version": "0.1.8",
"resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz",
"integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=" "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A="
}, },
"fd": { "fd": {
"version": "0.0.2", "version": "0.0.3",
"resolved": "https://registry.npmjs.org/fd/-/fd-0.0.2.tgz", "resolved": "https://registry.npmjs.org/fd/-/fd-0.0.3.tgz",
"integrity": "sha1-4O2yvXqIzIbdnxY5HLqDJBj9h+4=" "integrity": "sha512-iAHrIslQb3U68OcMSP0kkNWabp7sSN6d2TBSb2JO3gcLJVDd4owr/hKM4SFJovFOUeeXeItjYgouEDTMWiVAnA=="
}, },
"finalhandler": { "finalhandler": {
"version": "0.4.1", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-0.4.1.tgz", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
"integrity": "sha1-haF8bFmpRxfSYtYSMNSw6+PUoU0=", "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
"requires": { "requires": {
"debug": "2.2.0", "debug": "2.6.9",
"escape-html": "1.0.3", "encodeurl": "~1.0.2",
"on-finished": "2.3.0", "escape-html": "~1.0.3",
"unpipe": "1.0.0" "on-finished": "~2.3.0",
} "parseurl": "~1.3.3",
}, "statuses": "~1.5.0",
"fs.realpath": { "unpipe": "~1.0.0"
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
},
"generic-pool": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.1.1.tgz",
"integrity": "sha1-rwTcLDJc/Ll1Aj+lK/zpYXp0Nf0="
},
"glob": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz",
"integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=",
"dev": true,
"requires": {
"fs.realpath": "1.0.0",
"inflight": "1.0.6",
"inherits": "2.0.3",
"minimatch": "3.0.4",
"once": "1.4.0",
"path-is-absolute": "1.0.1"
} }
}, },
"graceful-fs": { "graceful-fs": {
"version": "4.1.11", "version": "4.2.4",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
"integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==",
"optional": true "optional": true
}, },
"graceful-readlink": { "ieee754": {
"version": "1.0.1", "version": "1.1.13",
"resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
"integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
"dev": true
},
"growl": {
"version": "1.9.2",
"resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz",
"integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=",
"dev": true
},
"has-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz",
"integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=",
"dev": true
},
"he": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz",
"integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=",
"dev": true
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"requires": {
"once": "1.4.0",
"wrappy": "1.0.2"
}
}, },
"inherits": { "inherits": {
"version": "2.0.3", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
}, },
"lru-cache": { "lru-cache": {
"version": "2.3.1", "version": "4.1.5",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.3.1.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
"integrity": "sha1-s632s9hW6VTiw5DmzvIggSRaU9Y=" "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
"requires": {
"pseudomap": "^1.0.2",
"yallist": "^2.1.2"
}
}, },
"mime": { "mime": {
"version": "1.3.6", "version": "2.4.6",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.3.6.tgz", "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz",
"integrity": "sha1-WR2E02U6awtKO5343lqoEI5y5eA=" "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA=="
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"requires": {
"brace-expansion": "1.1.8"
}
},
"minimist": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
"dev": true
},
"mkdirp": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"dev": true,
"requires": {
"minimist": "0.0.8"
}
},
"mocha": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/mocha/-/mocha-4.0.1.tgz",
"integrity": "sha512-evDmhkoA+cBNiQQQdSKZa2b9+W2mpLoj50367lhy+Klnx9OV8XlCIhigUnn1gaTFLQCa0kdNhEGDr0hCXOQFDw==",
"dev": true,
"requires": {
"browser-stdout": "1.3.0",
"commander": "2.9.0",
"debug": "2.2.0",
"diff": "3.2.0",
"escape-string-regexp": "1.0.5",
"glob": "7.1.1",
"growl": "1.9.2",
"he": "1.1.1",
"mkdirp": "0.5.1",
"supports-color": "3.1.2"
}
}, },
"ms": { "ms": {
"version": "0.7.1", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
}, },
"negotiator": { "negotiator": {
"version": "0.6.1", "version": "0.6.2",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
"integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
}, },
"on-finished": { "on-finished": {
"version": "2.3.0", "version": "2.3.0",
@ -337,95 +178,29 @@
"ee-first": "1.1.1" "ee-first": "1.1.1"
} }
}, },
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"requires": {
"wrappy": "1.0.2"
}
},
"packet-reader": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-0.2.0.tgz",
"integrity": "sha1-gZ300BC4LV6lZx+KGjrPA5vNdwA="
},
"parseurl": { "parseurl": {
"version": "1.3.1", "version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha1-yKuMkiO6NIiKpkopeyiFO+wY2lY=" "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true
},
"pg": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/pg/-/pg-4.1.1.tgz",
"integrity": "sha1-mEgKz8089qP5Yhyl1FiUFVgqVzI=",
"requires": {
"buffer-writer": "1.0.0",
"generic-pool": "2.1.1",
"packet-reader": "0.2.0",
"pg-connection-string": "0.1.3",
"pg-types": "1.6.0",
"pgpass": "0.0.3",
"semver": "4.3.6"
}
},
"pg-connection-string": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-0.1.3.tgz",
"integrity": "sha1-2hhHsglA5C7hSSvq9l1J2RskXfc="
},
"pg-types": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-1.6.0.tgz",
"integrity": "sha1-OHKg8ZkUMCVJf07ipl/a8A1+qLM="
},
"pgpass": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-0.0.3.tgz",
"integrity": "sha1-EuZ+NDsxicLzEgbrycwL7//PkUA=",
"requires": {
"split": "0.3.3"
}
}, },
"pkginfo": { "pkginfo": {
"version": "0.2.3", "version": "0.2.3",
"resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.2.3.tgz", "resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.2.3.tgz",
"integrity": "sha1-cjnEKl72wwuPMoQ52bn/cQQkkPg=" "integrity": "sha1-cjnEKl72wwuPMoQ52bn/cQQkkPg="
}, },
"process-nextick-args": { "pseudomap": {
"version": "1.0.7", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
"integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM="
}, },
"readable-stream": { "readable-stream": {
"version": "1.1.14", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"requires": { "requires": {
"core-util-is": "1.0.2", "inherits": "^2.0.3",
"inherits": "2.0.3", "string_decoder": "^1.1.1",
"isarray": "0.0.1", "util-deprecate": "^1.0.1"
"string_decoder": "0.10.31"
}
},
"redis": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/redis/-/redis-0.8.1.tgz",
"integrity": "sha1-FZ8hMFmaL3GeRLA/C0t2EvmS/LI="
},
"redis-url": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/redis-url/-/redis-url-0.1.0.tgz",
"integrity": "sha1-TaXlsYG2wMrW4aVcf1Co5u53ebs=",
"requires": {
"redis": "0.8.1"
} }
}, },
"request": { "request": {
@ -433,35 +208,22 @@
"resolved": "https://registry.npmjs.org/request/-/request-2.9.203.tgz", "resolved": "https://registry.npmjs.org/request/-/request-2.9.203.tgz",
"integrity": "sha1-bBcRpUB/uUoRQhlWPkQUW8v0cjo=" "integrity": "sha1-bBcRpUB/uUoRQhlWPkQUW8v0cjo="
}, },
"semver": { "safe-buffer": {
"version": "4.3.6", "version": "5.2.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=" "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
},
"split": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz",
"integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=",
"requires": {
"through": "2.3.8"
}
}, },
"st": { "st": {
"version": "1.1.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/st/-/st-1.1.0.tgz", "resolved": "https://registry.npmjs.org/st/-/st-2.0.0.tgz",
"integrity": "sha1-c7ltsLdkTZp4zjg0o+T37G6Hz3Y=", "integrity": "sha512-drN+aGYnrZPNYIymmNwIY7LXYJ8MqsqXj4fMRue3FOgGMdGjSX10fhJ3qx0sVQPhcWxhEaN4U/eWM4O4dbYNAw==",
"requires": { "requires": {
"async-cache": "1.0.0", "async-cache": "^1.1.0",
"bl": "1.0.3", "bl": "^4.0.0",
"fd": "0.0.2", "fd": "~0.0.2",
"graceful-fs": "4.1.11", "graceful-fs": "^4.2.3",
"mime": "1.3.6", "mime": "^2.4.4",
"negotiator": "0.6.1" "negotiator": "~0.6.2"
} }
}, },
"stack-trace": { "stack-trace": {
@ -469,44 +231,22 @@
"resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
"integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA="
}, },
"statuses": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
},
"streamsearch": { "streamsearch": {
"version": "0.1.2", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz",
"integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo="
}, },
"string_decoder": { "string_decoder": {
"version": "0.10.31", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
},
"supports-color": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz",
"integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=",
"dev": true,
"requires": { "requires": {
"has-flag": "1.0.0" "safe-buffer": "~5.2.0"
}
},
"through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
},
"uglify-js": {
"version": "3.1.6",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.1.6.tgz",
"integrity": "sha512-/rseyxEKEVMBo8279lqpoJgD6C/i/CIi+9TJDvWmb+Xo6mqMKwjA8Io3IMHlcXQzj99feR6zrN8m3wqqvm/nYA==",
"requires": {
"commander": "2.11.0",
"source-map": "0.6.1"
},
"dependencies": {
"commander": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz",
"integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ=="
}
} }
}, },
"unpipe": { "unpipe": {
@ -520,29 +260,28 @@
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
}, },
"utils-merge": { "utils-merge": {
"version": "1.0.0", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=" "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
}, },
"winston": { "winston": {
"version": "0.6.2", "version": "0.6.2",
"resolved": "https://registry.npmjs.org/winston/-/winston-0.6.2.tgz", "resolved": "https://registry.npmjs.org/winston/-/winston-0.6.2.tgz",
"integrity": "sha1-QUT+JYbNwZphK/jANVkBMskGS9I=", "integrity": "sha1-QUT+JYbNwZphK/jANVkBMskGS9I=",
"requires": { "requires": {
"async": "0.1.22", "async": "0.1.x",
"colors": "0.6.2", "colors": "0.x.x",
"cycle": "1.0.3", "cycle": "1.0.x",
"eyes": "0.1.8", "eyes": "0.1.x",
"pkginfo": "0.2.3", "pkginfo": "0.2.x",
"request": "2.9.203", "request": "2.9.x",
"stack-trace": "0.0.10" "stack-trace": "0.0.x"
} }
}, },
"wrappy": { "yallist": {
"version": "1.0.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI="
"dev": true
} }
} }
} }

View File

@ -1,37 +1,30 @@
{ {
"name": "haste", "name": "butlerbin",
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"description": "Private Pastebin Server", "description": "Butlersaurus Pastebin",
"keywords": [ "keywords": [
"paste", "paste",
"pastebin" "pastebin"
], ],
"author": { "author": {
"name": "John Crepezzi", "name": "Butlersaurus",
"email": "john.crepezzi@gmail.com", "url": "https://butlersaur.us/",
"url": "http://seejohncode.com/" "credits": "Orignal source courtesy of John Crepezzi, Hastebin developer. https://seejohncode.com/"
}, },
"main": "haste", "main": "butlerbin",
"dependencies": { "dependencies": {
"connect-ratelimit": "0.0.7", "busboy": "0.3.1",
"connect": "3.7.0",
"connect-route": "0.1.5", "connect-route": "0.1.5",
"connect": "3.4.1", "mongodb": "3.5.8",
"st": "1.1.0", "st": "2.0.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",
"pg": "4.1.1"
},
"devDependencies": {
"mocha": "^4.0.1"
}, },
"bundledDependencies": [], "bundledDependencies": [],
"engines": { "engines": {
"node": "8.1.4", "node": "14.4.0",
"npm": "5.2.0" "npm": "6.14.5"
}, },
"bin": { "bin": {
"haste-server": "./server.js" "haste-server": "./server.js"
@ -45,7 +38,6 @@
"lib": "./lib" "lib": "./lib"
}, },
"scripts": { "scripts": {
"start": "node server.js", "start": "node server.js"
"test": "mocha --recursive"
} }
} }

167
server.js
View File

@ -1,155 +1,96 @@
var http = require('http'); var Connect = require("connect");
var fs = require('fs'); var ConnectSt = require("st");
var Fs = require("fs");
var Http = require("http");
var Route = require("connect-route");
var Winston = require("winston");
var uglify = require('uglify-js'); var DocumentHandler = require("./lib/document_handler");
var winston = require('winston'); var KeyGenerator = require("./lib/key_generator");
var connect = require('connect'); var Store = require("./lib/document_store");
var route = require('connect-route');
var connect_st = require('st');
var connect_rate_limit = require('connect-ratelimit');
var DocumentHandler = require('./lib/document_handler'); // Load the configuration and set some defaults.
var config = JSON.parse(Fs.readFileSync("./config.js", "utf8"));
config.host = config.host || "127.0.0.1";
config.port = config.port || 7777;
// Load the configuration and set some defaults // Set up the logger.
var config = JSON.parse(fs.readFileSync('./config.js', 'utf8'));
config.port = process.env.PORT || config.port || 7777;
config.host = process.env.HOST || config.host || 'localhost';
// 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) {
/* was not present */ // 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++) {
detail = config.logging[i]; detail = config.logging[i];
type = detail.type; type = detail.type;
delete detail.type; delete detail.type;
winston.add(winston.transports[type], detail); Winston.add(Winston.transports[type], detail);
} }
} }
// build the store from the config on-demand - so that we don't load it // Configure the data store.
// for statics store = new Store({
if (!config.storage) { username: config.mongodbUsername,
config.storage = { type: 'file' }; password: config.mongodbPassword,
} dbName: config.mongodbDbName,
if (!config.storage.type) { collectionName: config.mongodbCollectionName
config.storage.type = 'file'; });
}
var Store, preferredStore; // Pick up a key generator.
var keyGenerator = new KeyGenerator({
keyLength: config.keyLength,
keySpace: config.keySpace
});
if (process.env.REDISTOGO_URL && config.storage.type === 'redis') { // Configure the document handler.
var redisClient = require('redis-url').connect(process.env.REDISTOGO_URL);
Store = require('./lib/document_stores/redis');
preferredStore = new Store(config.storage, redisClient);
}
else {
Store = require('./lib/document_stores/' + config.storage.type);
preferredStore = new Store(config.storage);
}
// Compress the static javascript assets
if (config.recompressStaticAssets) {
var list = fs.readdirSync('./static');
for (var j = 0; j < list.length; j++) {
var item = list[j];
if ((item.indexOf('.js') === item.length - 3) && (item.indexOf('.min.js') === -1)) {
var dest = item.substring(0, item.length - 3) + '.min' + item.substring(item.length - 3);
var orig_code = fs.readFileSync('./static/' + item, 'utf8');
fs.writeFileSync('./static/' + dest, uglify.minify(orig_code).code, 'utf8');
winston.info('compressed ' + item + ' into ' + dest);
}
}
}
// Send the static documents into the preferred store, skipping expirations
var path, data;
for (var name in config.documents) {
path = config.documents[name];
data = fs.readFileSync(path, 'utf8');
winston.info('loading static document', { name: name, path: path });
if (data) {
preferredStore.set(name, data, function(cb) {
winston.debug('loaded static document', { success: cb });
}, true);
}
else {
winston.warn('failed to load static document', { name: name, path: path });
}
}
// Pick up a key generator
var pwOptions = config.keyGenerator || {};
pwOptions.type = pwOptions.type || 'random';
var gen = require('./lib/key_generators/' + pwOptions.type);
var keyGenerator = new gen(pwOptions);
// Configure the document handler
var documentHandler = new DocumentHandler({ var documentHandler = new DocumentHandler({
store: preferredStore, store: store,
maxLength: config.maxLength,
keyLength: config.keyLength, keyLength: config.keyLength,
keyGenerator: keyGenerator keyGenerator: keyGenerator
}); });
var app = connect(); var app = Connect();
// Rate limit all requests // Configure the API routes.
if (config.rateLimits) { app.use(Route(function(router) {
config.rateLimits.end = true; router.get("/raw/:id", function(request, response) {
app.use(connect_rate_limit(config.rateLimits)); var key = request.params.id.split(".")[0];
} return documentHandler.handleRawGet(key, response);
// first look at API calls
app.use(route(function(router) {
// get raw documents - support getting with extension
router.get('/raw/:id', function(request, response) {
var key = request.params.id.split('.')[0];
var skipExpire = !!config.documents[key];
return documentHandler.handleRawGet(key, response, skipExpire);
}); });
// add documents router.post("/documents", function(request, response) {
router.post('/documents', function(request, response) {
return documentHandler.handlePost(request, response); return documentHandler.handlePost(request, response);
}); });
// get documents 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);
var skipExpire = !!config.documents[key];
return documentHandler.handleGet(key, response, skipExpire);
}); });
})); }));
// Otherwise, try to match static files // Route static files.
app.use(connect_st({ 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
})); }));
// Then we can loop back - and everything else should be a token, // All the pastebin IDs.
// so route it back to / 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();
}); });
})); }));
// And match index // Route index.html.
app.use(connect_st({ 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);

View File

@ -1,26 +0,0 @@
/* global describe, it */
var assert = require('assert');
var DocumentHandler = require('../lib/document_handler');
var Generator = require('../lib/key_generators/random');
describe('document_handler', function() {
describe('randomKey', function() {
it('should choose a key of the proper length', function() {
var gen = new Generator();
var dh = new DocumentHandler({ keyLength: 6, keyGenerator: gen });
assert.equal(6, dh.acceptableKey().length);
});
it('should choose a default key length', function() {
var gen = new Generator();
var dh = new DocumentHandler({ keyGenerator: gen });
assert.equal(dh.keyLength, DocumentHandler.defaultKeyLength);
});
});
});

View File

@ -1,33 +0,0 @@
/* global describe, it */
const assert = require('assert');
const fs = require('fs');
const Generator = require('../../lib/key_generators/dictionary');
describe('RandomKeyGenerator', function() {
describe('randomKey', function() {
it('should throw an error if given no options', () => {
assert.throws(() => {
new Generator();
}, Error);
});
it('should throw an error if given no path', () => {
assert.throws(() => {
new Generator({});
}, Error);
});
it('should return a key of the proper number of words from the given dictionary', () => {
const path = '/tmp/haste-server-test-dictionary';
const words = ['cat'];
fs.writeFileSync(path, words.join('\n'));
const gen = new Generator({path}, () => {
assert.equal('catcatcat', gen.createKey(3));
});
});
});
});

View File

@ -1,27 +0,0 @@
/* global describe, it */
const assert = require('assert');
const Generator = require('../../lib/key_generators/phonetic');
const vowels = 'aeiou';
const consonants = 'bcdfghjklmnpqrstvwxyz';
describe('RandomKeyGenerator', () => {
describe('randomKey', () => {
it('should return a key of the proper length', () => {
const gen = new Generator();
assert.equal(6, gen.createKey(6).length);
});
it('should alternate consonants and vowels', () => {
const gen = new Generator();
const key = gen.createKey(3);
assert.ok(consonants.includes(key[0]));
assert.ok(consonants.includes(key[2]));
assert.ok(vowels.includes(key[1]));
});
});
});

View File

@ -1,19 +0,0 @@
/* global describe, it */
const assert = require('assert');
const Generator = require('../../lib/key_generators/random');
describe('RandomKeyGenerator', () => {
describe('randomKey', () => {
it('should return a key of the proper length', () => {
const gen = new Generator();
assert.equal(6, gen.createKey(6).length);
});
it('should use a key from the given keyset if given', () => {
const gen = new Generator({keyspace: 'A'});
assert.equal('AAAAAA', gen.createKey(6));
});
});
});

View File

@ -1,54 +0,0 @@
/* global it, describe, afterEach */
var assert = require('assert');
var winston = require('winston');
winston.remove(winston.transports.Console);
var RedisDocumentStore = require('../lib/document_stores/redis');
describe('redis_document_store', function() {
/* reconnect to redis on each test */
afterEach(function() {
if (RedisDocumentStore.client) {
RedisDocumentStore.client.quit();
RedisDocumentStore.client = false;
}
});
describe('set', function() {
it('should be able to set a key and have an expiration set', function(done) {
var store = new RedisDocumentStore({ expire: 10 });
store.set('hello1', 'world', function() {
RedisDocumentStore.client.ttl('hello1', function(err, res) {
assert.ok(res > 1);
done();
});
});
});
it('should not set an expiration when told not to', function(done) {
var store = new RedisDocumentStore({ expire: 10 });
store.set('hello2', 'world', function() {
RedisDocumentStore.client.ttl('hello2', function(err, res) {
assert.equal(-1, res);
done();
});
}, true);
});
it('should not set an expiration when expiration is off', function(done) {
var store = new RedisDocumentStore({ expire: false });
store.set('hello3', 'world', function() {
RedisDocumentStore.client.ttl('hello3', function(err, res) {
assert.equal(-1, res);
done();
});
});
});
});
});