2015-07-09 23:01:43 +01:00
|
|
|
import path from 'path';
|
|
|
|
import crypto from 'crypto';
|
2016-05-25 21:03:07 +01:00
|
|
|
import fs from 'mz/fs';
|
2015-09-01 14:09:52 +02:00
|
|
|
import redis from 'redis';
|
2016-05-25 21:03:07 +01:00
|
|
|
|
2015-07-09 23:01:43 +01:00
|
|
|
import { sniff } from '../../lib/type';
|
|
|
|
import malware from '../../lib/malware';
|
|
|
|
import { formatFile } from '../../lib/format';
|
2016-05-25 21:03:07 +01:00
|
|
|
import { accept, processImage } from '../../lib/upload';
|
2015-07-09 23:01:43 +01:00
|
|
|
|
|
|
|
import debugname from 'debug';
|
|
|
|
const debug = debugname('hostr-api:file');
|
|
|
|
|
2015-08-30 18:35:05 +02:00
|
|
|
const redisUrl = process.env.REDIS_URL;
|
2015-07-09 23:01:43 +01:00
|
|
|
|
2015-08-30 18:35:05 +02:00
|
|
|
const storePath = process.env.UPLOAD_STORAGE_PATH;
|
2015-07-09 23:01:43 +01:00
|
|
|
|
|
|
|
export function* post(next) {
|
|
|
|
if (!this.request.is('multipart/*')) {
|
|
|
|
return yield next;
|
|
|
|
}
|
|
|
|
const Files = this.db.Files;
|
|
|
|
|
|
|
|
const expectedSize = this.request.headers['content-length'];
|
|
|
|
const remoteIp = this.request.headers['x-real-ip'] || this.req.connection.remoteAddress;
|
|
|
|
const md5sum = crypto.createHash('md5');
|
|
|
|
|
|
|
|
let lastPercent = 0;
|
|
|
|
let percentComplete = 0;
|
|
|
|
let lastTick = 0;
|
|
|
|
let receivedSize = 0;
|
|
|
|
|
2016-05-25 21:03:07 +01:00
|
|
|
const upload = yield accept.call(this);
|
2015-07-09 23:01:43 +01:00
|
|
|
|
2016-05-25 21:03:07 +01:00
|
|
|
upload.path = path.join(upload.id[0], upload.id + '_' + upload.filename);
|
|
|
|
const localStream = fs.createWriteStream(path.join(storePath, upload.path));
|
2015-07-09 23:01:43 +01:00
|
|
|
|
|
|
|
upload.pipe(localStream);
|
|
|
|
|
|
|
|
upload.on('data', (data) => {
|
|
|
|
receivedSize += data.length;
|
|
|
|
if (receivedSize > this.user.max_filesize) {
|
|
|
|
fs.unlink(path.join(storePath, key));
|
|
|
|
this.throw(413, '{"error": {"message": "The file you tried to upload is too large.", "code": 601}}');
|
|
|
|
}
|
|
|
|
|
|
|
|
percentComplete = Math.floor(receivedSize * 100 / expectedSize);
|
|
|
|
if (percentComplete > lastPercent && lastTick < Date.now() - 1000) {
|
2016-05-25 21:03:07 +01:00
|
|
|
const progressEvent = `{"type": "file-progress", "data": {"id": "${upload.id}", "complete": ${percentComplete}}}`;
|
|
|
|
this.redis.publish('/file/' + upload.id, progressEvent);
|
2015-07-09 23:01:43 +01:00
|
|
|
this.redis.publish('/user/' + this.user.id, progressEvent);
|
|
|
|
lastTick = Date.now();
|
|
|
|
}
|
|
|
|
lastPercent = percentComplete;
|
|
|
|
|
|
|
|
md5sum.update(data);
|
|
|
|
});
|
|
|
|
|
|
|
|
const dbFile = {
|
|
|
|
owner: this.user.id,
|
|
|
|
ip: remoteIp,
|
2016-05-25 21:03:07 +01:00
|
|
|
'system_name': upload.id,
|
2015-07-09 23:01:43 +01:00
|
|
|
'file_name': upload.filename,
|
|
|
|
'original_name': upload.originalName,
|
|
|
|
'file_size': receivedSize,
|
|
|
|
'time_added': Math.ceil(Date.now() / 1000),
|
|
|
|
status: 'active',
|
|
|
|
'last_accessed': null,
|
|
|
|
s3: false,
|
2015-08-23 22:12:32 +01:00
|
|
|
type: sniff(upload.filename),
|
2015-07-09 23:01:43 +01:00
|
|
|
};
|
|
|
|
|
2016-05-25 21:03:07 +01:00
|
|
|
yield Files.insertOne({_id: upload.id, ...dbFile});
|
|
|
|
|
|
|
|
yield upload.promise;
|
2015-07-09 23:01:43 +01:00
|
|
|
|
2016-05-25 21:03:07 +01:00
|
|
|
const completeEvent = `{"type": "file-progress", "data": {"id": "${upload.id}", "complete": 100}}`;
|
|
|
|
this.redis.publish('/file/' + upload.id, completeEvent);
|
|
|
|
this.redis.publish('/user/' + this.user.id, completeEvent);
|
|
|
|
this.statsd.incr('file.upload.complete', 1);
|
2015-07-09 23:01:43 +01:00
|
|
|
|
2016-05-25 21:03:07 +01:00
|
|
|
const size = yield processImage(upload);
|
|
|
|
|
|
|
|
dbFile.width = size.width;
|
|
|
|
dbFile.height = size.height;
|
2015-07-09 23:01:43 +01:00
|
|
|
dbFile.file_size = receivedSize; // eslint-disable-line camelcase
|
|
|
|
dbFile.status = 'active';
|
|
|
|
dbFile.md5 = md5sum.digest('hex');
|
|
|
|
|
2016-05-25 21:03:07 +01:00
|
|
|
const formattedFile = formatFile({_id: upload.id, ...dbFile});
|
2015-07-09 23:01:43 +01:00
|
|
|
|
2016-05-25 21:03:07 +01:00
|
|
|
yield Files.updateOne({_id: upload.id}, {$set: dbFile});
|
2015-07-09 23:01:43 +01:00
|
|
|
|
2015-08-08 20:37:49 +01:00
|
|
|
const addedEvent = `{"type": "file-added", "data": ${JSON.stringify(formattedFile)}}`;
|
2016-05-25 21:03:07 +01:00
|
|
|
this.redis.publish('/file/' + upload.id, addedEvent);
|
2015-07-09 23:01:43 +01:00
|
|
|
this.redis.publish('/user/' + this.user.id, addedEvent);
|
2016-05-25 21:03:07 +01:00
|
|
|
|
2015-07-09 23:01:43 +01:00
|
|
|
this.status = 201;
|
|
|
|
this.body = formattedFile;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function* list() {
|
|
|
|
const Files = this.db.Files;
|
|
|
|
|
|
|
|
let status = 'active';
|
|
|
|
if (this.request.query.trashed) {
|
|
|
|
status = 'trashed';
|
|
|
|
} else if (this.request.query.all) {
|
|
|
|
status = {'$in': ['active', 'trashed']};
|
|
|
|
}
|
|
|
|
|
|
|
|
let limit = 20;
|
|
|
|
if (this.request.query.perpage === '0') {
|
|
|
|
limit = false;
|
2015-08-23 22:12:32 +01:00
|
|
|
} else if (this.request.query.perpage > 0) {
|
|
|
|
limit = parseInt(this.request.query.perpage / 1, 10);
|
2015-07-09 23:01:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
let skip = 0;
|
|
|
|
if (this.request.query.page) {
|
2015-08-23 22:12:32 +01:00
|
|
|
skip = parseInt(this.request.query.page - 1, 10) * limit;
|
2015-07-09 23:01:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const queryOptions = {
|
|
|
|
limit: limit, skip: skip, sort: [['time_added', 'desc']],
|
|
|
|
hint: {
|
2015-08-23 22:12:32 +01:00
|
|
|
owner: 1, status: 1, 'time_added': -1,
|
|
|
|
},
|
2015-07-09 23:01:43 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
const userFiles = yield Files.find({owner: this.user.id, status: status}, queryOptions).toArray();
|
2015-08-09 17:21:39 +01:00
|
|
|
this.statsd.incr('file.list', 1);
|
2015-07-09 23:01:43 +01:00
|
|
|
this.body = userFiles.map(formatFile);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-08-22 16:16:15 +01:00
|
|
|
export function* get() {
|
2015-07-09 23:01:43 +01:00
|
|
|
const Files = this.db.Files;
|
|
|
|
const Users = this.db.Users;
|
2015-08-22 16:16:15 +01:00
|
|
|
const file = yield Files.findOne({_id: this.params.id, status: {'$in': ['active', 'uploading']}});
|
2015-07-09 23:01:43 +01:00
|
|
|
this.assert(file, 404, '{"error": {"message": "File not found", "code": 604}}');
|
|
|
|
const user = yield Users.findOne({_id: file.owner});
|
|
|
|
this.assert(user && !user.banned, 404, '{"error": {"message": "File not found", "code": 604}}');
|
2015-08-09 17:21:39 +01:00
|
|
|
this.statsd.incr('file.get', 1);
|
2015-07-09 23:01:43 +01:00
|
|
|
this.body = formatFile(file);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-08-22 16:16:15 +01:00
|
|
|
export function* put() {
|
2015-07-09 23:01:43 +01:00
|
|
|
if (this.request.body.trashed) {
|
|
|
|
const Files = this.db.Files;
|
|
|
|
const status = this.request.body.trashed ? 'trashed' : 'active';
|
2015-08-22 16:16:15 +01:00
|
|
|
yield Files.updateOne({'_id': this.params.id, owner: this.user.id}, {$set: {status: status}}, {w: 1});
|
2015-07-09 23:01:43 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-08-22 16:16:15 +01:00
|
|
|
export function* del() {
|
2015-09-01 14:15:00 +02:00
|
|
|
yield this.db.Files.updateOne({'_id': this.params.id, owner: this.db.objectId(this.user.id)}, {$set: {status: 'deleted'}}, {w: 1});
|
2015-08-22 16:16:15 +01:00
|
|
|
const event = {type: 'file-deleted', data: {'id': this.params.id}};
|
2015-07-09 23:01:43 +01:00
|
|
|
yield this.redis.publish('/user/' + this.user.id, JSON.stringify(event));
|
2015-08-22 16:16:15 +01:00
|
|
|
yield this.redis.publish('/file/' + this.params.id, JSON.stringify(event));
|
2015-08-09 17:21:39 +01:00
|
|
|
this.statsd.incr('file.delete', 1);
|
2015-08-22 16:16:15 +01:00
|
|
|
this.status = 204;
|
2015-07-09 23:01:43 +01:00
|
|
|
this.body = '';
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function* events() {
|
2015-09-01 14:09:52 +02:00
|
|
|
const pubsub = redis.createClient(redisUrl);
|
2015-08-08 20:37:49 +01:00
|
|
|
pubsub.on('ready', () => {
|
2015-07-09 23:01:43 +01:00
|
|
|
pubsub.subscribe(this.path);
|
2015-08-08 20:37:49 +01:00
|
|
|
});
|
2015-07-09 23:01:43 +01:00
|
|
|
|
2015-08-08 20:37:49 +01:00
|
|
|
pubsub.on('message', (channel, message) => {
|
2015-07-09 23:01:43 +01:00
|
|
|
this.websocket.send(message);
|
2015-08-08 20:37:49 +01:00
|
|
|
});
|
2015-08-23 22:12:32 +01:00
|
|
|
this.websocket.on('close', () => {
|
2015-07-09 23:01:43 +01:00
|
|
|
pubsub.quit();
|
|
|
|
});
|
|
|
|
}
|