2018-09-03 07:30:05 +00:00
|
|
|
"use strict";
|
|
|
|
|
|
|
|
const SocketIOFileUploadServer = require("socketio-file-upload/server");
|
|
|
|
const Helper = require("../helper");
|
|
|
|
const path = require("path");
|
|
|
|
const fsextra = require("fs-extra");
|
|
|
|
const fs = require("fs");
|
|
|
|
const fileType = require("file-type");
|
|
|
|
const readChunk = require("read-chunk");
|
|
|
|
const crypto = require("crypto");
|
|
|
|
const isUtf8 = require("is-utf8");
|
|
|
|
const log = require("../log");
|
|
|
|
|
|
|
|
const whitelist = [
|
|
|
|
"application/ogg",
|
|
|
|
"audio/midi",
|
|
|
|
"audio/mpeg",
|
|
|
|
"audio/ogg",
|
2018-12-19 15:20:47 +00:00
|
|
|
"audio/vnd.wave",
|
2018-09-03 07:30:05 +00:00
|
|
|
"image/bmp",
|
|
|
|
"image/gif",
|
|
|
|
"image/jpeg",
|
|
|
|
"image/png",
|
|
|
|
"image/webp",
|
|
|
|
"text/plain",
|
|
|
|
"video/mp4",
|
|
|
|
"video/ogg",
|
|
|
|
"video/webm",
|
|
|
|
];
|
|
|
|
|
|
|
|
class Uploader {
|
2018-09-04 09:08:30 +00:00
|
|
|
constructor(socket) {
|
2018-09-03 07:30:05 +00:00
|
|
|
const uploader = new SocketIOFileUploadServer();
|
|
|
|
const folder = path.join(Helper.getFileUploadPath(), ".tmp");
|
|
|
|
|
|
|
|
fsextra.ensureDir(folder, (err) => {
|
|
|
|
if (err) {
|
|
|
|
log.err(`Error ensuring ${folder} exists for uploads.`);
|
|
|
|
} else {
|
|
|
|
uploader.dir = folder;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
uploader.on("complete", (data) => {
|
2018-09-04 14:11:24 +00:00
|
|
|
let randomName;
|
|
|
|
let destPath;
|
|
|
|
|
|
|
|
// Generate a random file name for storage on disk
|
|
|
|
do {
|
|
|
|
randomName = crypto.randomBytes(8).toString("hex");
|
|
|
|
destPath = path.join(Helper.getFileUploadPath(), randomName.substring(0, 2), randomName);
|
2019-01-19 10:00:09 +00:00
|
|
|
} while (fs.existsSync(destPath));
|
2018-09-04 14:11:24 +00:00
|
|
|
|
|
|
|
fsextra.move(data.file.pathName, destPath).then(() => {
|
2018-10-13 21:23:41 +00:00
|
|
|
const slug = encodeURIComponent(path.basename(data.file.pathName));
|
2018-09-04 14:11:24 +00:00
|
|
|
const url = `uploads/${randomName}/${slug}`;
|
2018-09-04 09:08:30 +00:00
|
|
|
socket.emit("upload:success", url);
|
2018-09-04 14:11:24 +00:00
|
|
|
}).catch(() => {
|
|
|
|
log.warn(`Unable to move uploaded file "${data.file.pathName}"`);
|
|
|
|
|
|
|
|
// Emit failed upload to the client if file move fails
|
|
|
|
socket.emit("siofu_error", {
|
|
|
|
id: data.file.id,
|
|
|
|
message: "Unable to move uploaded file",
|
|
|
|
});
|
2018-09-03 07:30:05 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
uploader.on("error", (data) => {
|
2018-09-04 14:11:24 +00:00
|
|
|
log.error(`File upload failed: ${data.error}`);
|
2018-09-03 07:30:05 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
// maxFileSize is in bytes, but config option is passed in as KB
|
|
|
|
uploader.maxFileSize = Uploader.getMaxFileSize();
|
|
|
|
uploader.listen(socket);
|
|
|
|
}
|
|
|
|
|
|
|
|
static isValidType(mimeType) {
|
|
|
|
return whitelist.includes(mimeType);
|
|
|
|
}
|
|
|
|
|
|
|
|
static router(express) {
|
|
|
|
express.get("/uploads/:name/:slug*?", (req, res) => {
|
|
|
|
const name = req.params.name;
|
|
|
|
|
|
|
|
const nameRegex = /^[0-9a-f]{16}$/;
|
|
|
|
|
|
|
|
if (!nameRegex.test(name)) {
|
|
|
|
return res.status(404).send("Not found");
|
|
|
|
}
|
|
|
|
|
|
|
|
const folder = name.substring(0, 2);
|
|
|
|
const uploadPath = Helper.getFileUploadPath();
|
|
|
|
const filePath = path.join(uploadPath, folder, name);
|
|
|
|
const type = Uploader.getFileType(filePath);
|
|
|
|
const mimeType = type || "application/octet-stream";
|
|
|
|
const contentDisposition = Uploader.isValidType(type) ? "inline" : "attachment";
|
|
|
|
|
|
|
|
// doesn't exist
|
|
|
|
if (type === undefined) {
|
|
|
|
return res.status(404).send("Not found");
|
|
|
|
}
|
|
|
|
|
|
|
|
res.setHeader("Content-Disposition", contentDisposition);
|
|
|
|
res.setHeader("Cache-Control", "max-age=86400");
|
|
|
|
res.contentType(mimeType);
|
|
|
|
|
|
|
|
return res.sendFile(filePath);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
static getMaxFileSize() {
|
|
|
|
const configOption = Helper.config.fileUpload.maxFileSize;
|
|
|
|
|
|
|
|
if (configOption === -1) { // no file size limit
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return configOption * 1024;
|
|
|
|
}
|
|
|
|
|
|
|
|
static getFileType(filePath) {
|
|
|
|
let buffer;
|
|
|
|
let type;
|
|
|
|
|
|
|
|
try {
|
2018-11-13 11:29:29 +00:00
|
|
|
buffer = readChunk.sync(filePath, 0, fileType.minimumBytes);
|
2018-09-03 07:30:05 +00:00
|
|
|
} catch (e) {
|
|
|
|
if (e.code === "ENOENT") { // doesn't exist
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
log.warn(`Failed to read ${filePath}`);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// returns {ext, mime} if found, null if not.
|
|
|
|
const file = fileType(buffer);
|
|
|
|
|
|
|
|
if (file) {
|
|
|
|
type = file.mime;
|
|
|
|
} else if (isUtf8(buffer)) {
|
|
|
|
type = "text/plain";
|
|
|
|
}
|
|
|
|
|
|
|
|
return type;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = Uploader;
|