Initial commit.

This commit is contained in:
Jonathan Cremin 2015-07-09 23:01:43 +01:00
commit b48a4e92e1
169 changed files with 7538 additions and 0 deletions

52
lib/format.js Normal file
View file

@ -0,0 +1,52 @@
import moment from 'moment';
import { sniff } from './type';
const fileHost = process.env.FILE_HOST || 'http://localhost:4040';
export function formatDate(timestamp) {
return moment.unix(timestamp).format('D MMM YY [at] h:mm A');
}
export function formatSize(size) {
if (size >= 1073741824) {
size = Math.round((size / 1073741824) * 10) / 10 + 'GB';
} else {
if (size >= 1048576) {
size = Math.round((size / 1048576) * 10) / 10 + 'MB';
} else {
if (size >= 1024) {
size = Math.round((size / 1024) * 10) / 10 + 'KB';
} else {
size = Math.round(size) + 'B';
}
}
}
return size;
};
export function formatFile(file) {
const formattedFile = {
added: moment.unix(file.time_added).format(),
readableAdded: formatDate(file.time_added),
downloads: file.downloads !== undefined ? file.downloads : 0,
href: fileHost + '/' + file._id, // eslint-disable-line no-underscore-dangle
id: file._id, // eslint-disable-line no-underscore-dangle
name: file.file_name,
size: file.file_size,
readableSize: formatSize(file.file_size),
type: sniff(file.file_name),
trashed: (file.status === 'trashed'),
status: file.status
};
if (file.width) {
formattedFile.height = file.height;
formattedFile.width = file.width;
const ext = (file.file_name.split('.').pop().toLowerCase() === 'psd' ? '.png' : '');
formattedFile.direct = {
'150x': fileHost + '/file/150/' + file._id + '/' + file.file_name + ext, // eslint-disable-line no-underscore-dangle
'970x': fileHost + '/file/970/' + file._id + '/' + file.file_name + ext // eslint-disable-line no-underscore-dangle
};
}
return formattedFile;
}

36
lib/hostr-file-stream.js Normal file
View file

@ -0,0 +1,36 @@
import fs from 'fs';
import path from 'path';
import createError from 'http-errors';
import { get as getFile } from './s3';
import debugname from 'debug';
const debug = debugname('hostr:file-stream');
export default function* hostrFileStream(localPath, remotePath) {
const localRead = fs.createReadStream(localPath);
return new Promise((resolve, reject) => {
localRead.once('error', () => {
debug('local error');
const remoteRead = getFile(remotePath);
remoteRead.once('readable', () => {
debug('remote readable');
const localWrite = fs.createWriteStream(localPath);
localWrite.once('finish', () => {
debug('local write end');
resolve(fs.createReadStream(localPath));
});
remoteRead.pipe(localWrite);
});
remoteRead.once('error', () => {
debug('remote error');
reject(createError(404));
});
});
localRead.once('readable', () => {
debug('local readable');
resolve(localRead);
});
});
}

26
lib/hostr-id.js Normal file
View file

@ -0,0 +1,26 @@
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
function randomID() {
let rand = '';
for (let i = 0; i < 12; i++) {
rand += chars.charAt(Math.floor((Math.random() * chars.length)));
}
return rand;
}
function* checkId(Files, fileId, attempts) {
if (attempts > 10) {
return false;
}
const file = yield Files.findOne({'_id': fileId});
if(file === null) {
return fileId;
} else {
return checkId(randomID(), attempts++);
}
}
export default function* (Files) {
let attempts = 0;
return yield checkId(Files, randomID(), attempts);
}

75
lib/koa-error.js Normal file
View file

@ -0,0 +1,75 @@
/**
* Module dependencies.
*/
var swig = require('swig');
var http = require('http');
/**
* Expose `error`.
*/
module.exports = error;
/**
* Error middleware.
*
* - `template` defaults to ./error.html
*
* @param {Object} opts
* @api public
*/
function error(opts) {
opts = opts || {};
// template
var path = opts.template || __dirname + '/error.html';
var render = swig.compileFile(path);
// env
var env = process.env.NODE_ENV || 'development';
return function *error(next){
try {
yield next;
if (404 == this.response.status && !this.response.body) this.throw(404);
} catch (err) {
this.status = err.status || 500;
// application
this.app.emit('error', err, this);
// accepted types
switch (this.accepts('html', 'text', 'json')) {
case 'text':
this.type = 'text/plain';
if ('development' == env) this.body = err.message
else if (err.expose) this.body = err.message
else throw err;
break;
case 'json':
this.type = 'application/json';
if ('development' == env) this.body = { error: err.message }
else if (err.expose) this.body = { error: err.message }
else this.body = { error: http.STATUS_CODES[this.status] }
break;
case 'html':
this.type = 'text/html';
this.body = render({
env: env,
ctx: this,
request: this.request,
response: this.response,
error: err.message,
stack: err.stack,
status: this.status,
code: err.code
});
break;
}
}
}
}

38
lib/malware.js Normal file
View file

@ -0,0 +1,38 @@
import virustotal from 'virustotal.js';
virustotal.setKey(process.env.VIRUSTOTAL);
const extensions = ['EXE', 'PIF', 'APPLICATION', 'GADGET', 'MSI', 'MSP', 'COM', 'SCR', 'HTA', 'CPL', 'MSC',
'JAR', 'BAT', 'CMD', 'VB', 'VBS', 'VBE', 'JS', 'JSE', 'WS', 'WSF', 'WSC', 'WSH', 'PS1', 'PS1XML', 'PS2',
'PS2XML', 'PSC1', 'PSC2', 'MSH', 'MSH1', 'MSH2', 'MSHXML', 'MSH1XML', 'MSH2XML', 'SCF', 'LNK', 'INF', 'REG',
'PDF', 'DOC', 'XLS', 'PPT', 'DOCM', 'DOTM', 'XLSM', 'XLTM', 'XLAM', 'PPTM', 'POTM', 'PPAM', 'PPSM', 'SLDM',
'RAR', 'TAR', 'ZIP', 'GZ'
];
function getExtension(filename) {
const i = filename.lastIndexOf('.');
return (i < 0) ? '' : filename.substr(i + 1);
};
export default function (file) {
const deferred = {};
deferred.promise = new Promise(function(resolve, reject) {
deferred.resolve = resolve;
deferred.reject = reject;
});
if (extensions.indexOf(getExtension(file.file_name.toUpperCase())) >= 0) {
virustotal.getFileReport(file.md5, function (err, res) {
if (err) {
return deferred.reject(err);
}
if (res.scans) {
deferred.resolve({positive: res.positives >= 5, result: res});
} else {
deferred.resolve();
}
});
} else {
deferred.resolve();
}
return deferred.promise;
};

10
lib/resize.js Normal file
View file

@ -0,0 +1,10 @@
import debugname from 'debug';
const debug = debugname('hostr-api:resize');
import gm from 'gm';
export default function(input, size) {
debug('Resizing');
const image = gm(input);
return image.resize(size.width, size.height, '>').stream();
}

19
lib/s3.js Normal file
View file

@ -0,0 +1,19 @@
import aws from 'aws-sdk';
import s3UploadStream from 's3-upload-stream';
import debugname from 'debug';
const debug = debugname('hostr:s3');
const bucket = process.env.AWS_BUCKET || 'hostrdotcodev';
const s3 = new aws.S3();
const s3Stream = s3UploadStream(s3);
export function get(key) {
debug('fetching file: %s', 'hostr_files/' + key);
return s3.getObject({Bucket: bucket, Key: 'hostr_files/' + key}).createReadStream();
}
export function upload(key, body) {
debug('Uploading file: %s', 'hostr_files/' + key);
return s3Stream.upload({Bucket: bucket, Key: 'hostr_files/' + key});
}

24
lib/storage.js Normal file
View file

@ -0,0 +1,24 @@
import fs from 'fs';
import path from 'path';
function range(start,stop) {
var result=[];
for (var idx=start.charCodeAt(0),end=stop.charCodeAt(0); idx <=end; ++idx){
result.push(String.fromCharCode(idx));
}
return result;
};
const storePath = process.env.FILE_PATH || path.join(process.env.HOME, '.hostr', 'uploads');
const directories = range('A', 'Z').concat(range('a', 'z'), range('0', '9'));
export function init() {
directories.forEach((directory) => {
if (!fs.existsSync(path.join(storePath, directory))) {
fs.mkdirSync(path.join(storePath, directory));
fs.mkdirSync(path.join(storePath, directory, '150'));
fs.mkdirSync(path.join(storePath, directory, '970'));
}
});
}

34
lib/type.js Normal file
View file

@ -0,0 +1,34 @@
const extensions = {
'jpg': 'image',
'jpeg': 'image',
'png': 'image',
'gif': 'image',
'bmp': 'image',
'tiff': 'image',
'psd': 'image',
'mp3': 'audio',
'm4a': 'audio',
'ogg': 'audio',
'flac': 'audio',
'aac': 'audio',
'mpg': 'video',
'mkv': 'video',
'avi': 'video',
'divx': 'video',
'mpeg': 'video',
'flv': 'video',
'mp4': 'video',
'mov': 'video',
'zip': 'archive',
'gz': 'archive',
'tgz': 'archive',
'bz2': 'archive',
'rar': 'archive'
};
export function sniff(filename) {
if (extensions[filename.split('.').pop().toLowerCase()]) {
return extensions[filename.split('.').pop().toLowerCase()];
}
return 'other';
}