diff --git a/.gitignore b/.gitignore deleted file mode 100644 index ba9e398..0000000 --- a/.gitignore +++ /dev/null @@ -1 +0,0 @@ -EUS/ \ No newline at end of file diff --git a/EUS.js b/EUS.js index cdb4762..fbd6c8f 100644 --- a/EUS.js +++ b/EUS.js @@ -1,552 +1,371 @@ -const config = require("../config/config.json"), crypto = require("crypto"), emoji = require("../misc/emoji_list.json"), fs = require("fs"); +const fs = require("fs"), + config = require("../config/config.json"), + chalk = require("chalk"), + busboy = require("connect-busboy"), + randomstring = require("randomstring"), + getSize = require("get-folder-size"), + diskUsage = require("diskusage"), + emoji = require("../misc/emoji_list.json"); // Defines the function of this module const MODULE_FUNCTION = "handle_requests", - // Base path for module folder creation and navigation - BASE_PATH = "/EUS", - API_CACHE_LIFESPAN = 3600000; - -let node_modules = {}; + // Base path for module folder creation and navigation + BASE_PATH = "/EUS"; let eusConfig = {}, - useUploadKey = true, - cacheJSON = "", - startupFinished = false, - timeSinceLastCache = Date.now(); + image_json = {}, + d = new Date(), + startTime, + endTime, + useUploadKey = true, + cacheJSON = ""; -class Database { - constructor(databaseAddress, databasePort = 3306, databaseUsername, databasePassword, databaseName, connectedCallback) { - this.connectionPool = node_modules["mysql2"].createPool({ - connectionLimit: 128, - host: databaseAddress, - port: databasePort, - user: databaseUsername, - password: databasePassword, - database: databaseName - }); - - const classCreationTime = Date.now(); - this.dbActive = false; - if (connectedCallback == null) { - this.dbActive = true; - } else { - const connectionCheckInterval = setInterval(() => { - this.query("SELECT id FROM images LIMIT 1") - .then(data => { - global.modules.consoleHelper.printInfo(emoji.globe_europe, `Connected to database. Took ${Date.now() - classCreationTime}ms`); - this.dbActive = true; - clearInterval(connectionCheckInterval); - - connectedCallback(); - }) - .catch(err => { - console.error(err); - }); - }, 167); // Roughly 6 times per sec - } - } - - dataReceived(resolveCallback, data, limited = false) { - if (limited) resolveCallback(data[0]); - else resolveCallback(data); - } - - query(query = "", data) { - const limited = query.includes("LIMIT 1"); - - return new Promise((resolve, reject) => { - this.connectionPool.getConnection((err, connection) => { - if (err) { - reject(err); - try { connection.release();} - catch (e) { - console.error("Failed to release mysql connection", err); - } - } else { - // Use old query - if (data == null) { - connection.query(query, (err, data) => { - if (err) { - reject(err); - connection.release(); - } else { - this.dataReceived(resolve, data, limited); - connection.release(); - } - }); - } - // Use new prepared statements w/ placeholders - else { - connection.execute(query, data, (err, data) => { - if (err) { - reject(err); - connection.release(); - } else { - this.dataReceived(resolve, data, limited); - connection.release(); - } - }); - } - } - }); - }); - } +// Only ran on startup so using sync functions is fine +// Makes the folder for files of the module +if (!fs.existsSync(__dirname + BASE_PATH)) { + fs.mkdirSync(__dirname + BASE_PATH); + console.log(`[EUS] Made EUS module folder`); +} +// Makes the folder for frontend files +if (!fs.existsSync(__dirname + BASE_PATH + "/files")) { + fs.mkdirSync(__dirname + BASE_PATH + "/files"); + console.log(`[EUS] Made EUS web files folder`); +} +// Makes the folder for images +if (!fs.existsSync(__dirname + BASE_PATH + "/i")) { + fs.mkdirSync(__dirname + BASE_PATH + "/i"); + console.log(`[EUS] Made EUS images folder`); +} +// Makes the image-type file +if (!fs.existsSync(__dirname + BASE_PATH + "/image-type.json")) { + // Doesn't exist, create it. + fs.writeFileSync(`${__dirname}${BASE_PATH}/image-type.json`, '{}'); + console.log("[EUS] Made EUS image-type File!"); + // File has been created, load it. + image_json = require(`${__dirname}${BASE_PATH}/image-type.json`); +} else { + // File already exists, load it. + const ijLoadStartTime = new Date().getTime(); + image_json = require(`${__dirname}${BASE_PATH}/image-type.json`); + console.log(`[EUS] Loaded image-type file, took ${new Date().getTime() - ijLoadStartTime}ms`); } -let dbConnection; -function init() { - // Require node modules - node_modules["chalk"] = require("chalk"); - node_modules["busboy"] = require("connect-busboy"); - node_modules["randomstring"] = require("randomstring"); - node_modules["streamMeter"] = require("stream-meter"); - node_modules["mysql2"] = require("mysql2"); - - // Only ran on startup so using sync functions is fine - - // Makes the folder for files of the module - if (!fs.existsSync(__dirname + BASE_PATH)) { - fs.mkdirSync(__dirname + BASE_PATH); - console.log(`[EUS] Made EUS module folder`); - } - // Makes the folder for frontend files - if (!fs.existsSync(__dirname + BASE_PATH + "/files")) { - fs.mkdirSync(__dirname + BASE_PATH + "/files"); - console.log(`[EUS] Made EUS web files folder`); - } - // Makes the folder for images - if (!fs.existsSync(__dirname + BASE_PATH + "/i")) { - fs.mkdirSync(__dirname + BASE_PATH + "/i"); - console.log(`[EUS] Made EUS images folder`); - } - - if (!fs.existsSync("/tmp/EUS_UPLOADS")) { - fs.mkdirSync("/tmp/EUS_UPLOADS"); - console.log("[EUS] Made EUS temp upload folder"); - } - - // Makes the config file - if (!fs.existsSync(__dirname + BASE_PATH + "/config.json")) { - // Config doesn't exist, make it. - fs.writeFileSync(`${__dirname}${BASE_PATH}/config.json`, '{\n\t"baseURL":"http://example.com/",\n\t"acceptedTypes": [\n\t\t".png",\n\t\t".jpg",\n\t\t".jpeg",\n\t\t".gif"\n\t],\n\t"uploadKey": "",\n\t"database": {\n\t\t"databaseAddress": "127.0.0.1",\n\t\t"databasePort": 3306,\n\t\t"databaseUsername": "root",\n\t\t"databasePassword": "password",\n\t\t"databaseName": "EUS"\n\t}\n}'); - console.log("[EUS] Made EUS config File!"); - console.log("[EUS] Please edit the EUS Config file before restarting."); - // Config has been made, close framework. - process.exit(0); - } else { - eusConfig = require(`${__dirname}${BASE_PATH}/config.json`); - if (validateConfig(eusConfig)) console.log("[EUS] EUS config passed all checks"); - } - - // This is using a callback but that's fine, the server will just react properly to the db not being ready yet. - dbConnection = new Database(eusConfig["database"]["databaseAddress"], eusConfig["database"]["databasePort"], eusConfig["database"]["databaseUsername"], eusConfig["database"]["databasePassword"], eusConfig["database"]["databaseName"], async () => { - cacheJSON = JSON.stringify(await cacheFilesAndSpace()); - cacheIsReady = true; - }); - - console.log("[EUS] Finished loading."); +// Makes the config file +if (!fs.existsSync(__dirname + BASE_PATH + "/config.json")) { + // Config doesn't exist, make it. + fs.writeFileSync(`${__dirname}${BASE_PATH}/config.json`, '{\n\t"baseURL":"http://example.com/",\n\t"acceptedTypes": [\n\t\t".png",\n\t\t".jpg",\n\t\t".jpeg",\n\t\t".gif"\n\t],\n\t"uploadKey": ""\n}'); + console.log("[EUS] Made EUS config File!"); + console.log("[EUS] Please edit the EUS Config file before restarting."); + // Config has been made, close framework. + process.exit(0); +} else { + eusConfig = require(`${__dirname}${BASE_PATH}/config.json`); + if (validateConfig(eusConfig)) console.log("[EUS] EUS config passed all checks"); } // Cache for the file count and space usage, this takes a while to do so it's best to cache the result let cacheIsReady = false; -function cacheFilesAndSpace() { - timeSinceLastCache = Date.now(); - return new Promise(async (resolve, reject) => { - const startCacheTime = Date.now(); - cacheIsReady = false; - let cachedFilesAndSpace = { - fileCounts: {}, - }; +async function cacheFilesAndSpace() { + const startCacheTime = new Date().getTime(); + let cachedFilesAndSpace = { + files: {} + }, - const dbData = await dbConnection.query(`SELECT imageType, COUNT(imageType) AS "count" FROM images GROUP BY imageType`); - let totalFiles = 0; - dbData.forEach(fileType => { - cachedFilesAndSpace["fileCounts"][fileType.imageType] = fileType.count; - totalFiles += fileType.count; - }); - cachedFilesAndSpace["filesTotal"] = totalFiles; + // Cache file totals + total = 0; + // Add each accepted file type to the json + for (var i2 = 0; i2 < eusConfig.acceptedTypes.length; i2++) { + cachedFilesAndSpace["files"][`${eusConfig.acceptedTypes[i2]}`.replace(".", "")] = 0; + } + // Read all files from the images directory + fs.readdir(__dirname + BASE_PATH + "/i", async (err, files) => { + if (err) throw err; + // Loop through all files + for (var i = 0; i < files.length; i++) { + // Loop through all accepted file types to check for a match + for (var i1 = 0; i1 < eusConfig.acceptedTypes.length; i1++) { + const jsudfg = files[i].split("."); + if (`.${jsudfg[jsudfg.length-1]}` == eusConfig.acceptedTypes[i1]) { + // There is a match! Add it to the json + cachedFilesAndSpace["files"][eusConfig.acceptedTypes[i1].replace(".", "")]++; + // Also increase the total + total++; + } + } + } + // Set the total in the json to the calculated total value + cachedFilesAndSpace["files"]["total"] = total; - cachedFilesAndSpace["size"] = {}; + // Cache usage + cachedFilesAndSpace["space"] = { + usage: {} + }; + // Get the space used on the disk + getSize(__dirname + BASE_PATH + "/i", async (err, size) => { + if (err) throw err; + // Calculate in different units the space taken up on disk + let sizeOfFolder = (size / 1024 / 1024); + cachedFilesAndSpace["space"]["usage"]["mb"] = sizeOfFolder; + sizeOfFolder = (size / 1024 / 1024 / 1024); + cachedFilesAndSpace["space"]["usage"]["gb"] = sizeOfFolder; + cachedFilesAndSpace["space"]["usage"]["string"] = spaceToLowest(size, true); + // Get total disk space + diskUsage.check(__dirname, async (err, data) => { + if (err) throw err; + cachedFilesAndSpace["space"]["total"] = { + value: spaceToLowest(data["total"], false), + mbvalue: (data["total"] / 1024 / 1024), + gbvalue: (data["total"] / 1024 / 1024 / 1024), + stringValue: spaceToLowest(data["total"], true).split(" ")[1].toLowerCase(), + string: spaceToLowest(data["total"], true) + }; - const dbSize = (await dbConnection.query(`SELECT SUM(fileSize) FROM images LIMIT 1`))["SUM(fileSize)"]; - const totalSizeBytes = dbSize == null ? 0 : dbSize; - const mbSize = totalSizeBytes / 1024 / 1024; - cachedFilesAndSpace["size"]["mb"] = parseFloat(mbSize.toFixed(4)); - cachedFilesAndSpace["size"]["gb"] = parseFloat((mbSize / 1024).toFixed(4)); - cachedFilesAndSpace["size"]["string"] = await spaceToLowest(totalSizeBytes, true); - - resolve(cachedFilesAndSpace); - global.modules.consoleHelper.printInfo(emoji.folder, `Stats api cache took ${Date.now() - startCacheTime}ms`); - }); + cacheIsReady = true; + cacheJSON = JSON.stringify(cachedFilesAndSpace); + global.modules.consoleHelper.printInfo(emoji.folder, `Stats api cache took ${new Date().getTime() - startCacheTime}ms`); + }); + }); + }); } function validateConfig(json) { - let performShutdownAfterValidation = false; - // URL Tests - if (json["baseURL"] == null) { - console.error("EUS baseURL property does not exist!"); - performShutdownAfterValidation = true; - } else { - if (json["baseURL"] == "") console.warn("EUS baseURL property is blank"); - const bURL = `${json["baseURL"]}`.split(""); - if (bURL.length > 1) { - if (bURL[bURL.length-1] != "/") console.warn("EUS baseURL property doesn't have a / at the end, this can lead to unpredictable results!"); - } - else { - if (json["baseURL"] != "http://" || json["baseURL"] != "https://") console.warn("EUS baseURL property is possibly invalid!"); - } - } - // acceptedTypes checks - if (json["acceptedTypes"] == null) { - console.error("EUS acceptedTypes list does not exist!"); - performShutdownAfterValidation = true; - } else { - if (json["acceptedTypes"].length < 1) console.warn("EUS acceptedTypes array has no extentions in it, users will not be able to upload images!"); - } - // uploadKey checks - if (json["uploadKey"] == null) { - console.error("EUS uploadKey property does not exist!"); - performShutdownAfterValidation = true; - } else { - if (json["uploadKey"] == "") useUploadKey = false; - } - // database checks - if (json["database"] == null) { - console.error("EUS database properties do not exist!"); - performShutdownAfterValidation = true; - } else { - // databaseAddress - if (json["database"]["databaseAddress"] == null) { - console.error("EUS database.databaseAddress property does not exist!"); - performShutdownAfterValidation = true; - } - // databasePort - if (json["database"]["databasePort"] == null) { - console.error("EUS database.databasePort property does not exist!"); - performShutdownAfterValidation = true; - } - // databaseUsername - if (json["database"]["databaseUsername"] == null) { - console.error("EUS database.databaseUsername property does not exist!"); - performShutdownAfterValidation = true; - } - // databasePassword - if (json["database"]["databasePassword"] == null) { - console.error("EUS database.databasePassword property does not exist!"); - performShutdownAfterValidation = true; - } - // databaseName - if (json["database"]["databaseName"] == null) { - console.error("EUS database.databaseName property does not exist!"); - performShutdownAfterValidation = true; - } - } + let performShutdownAfterValidation = false; + // URL Tests + if (json["baseURL"] == null) { + console.error("EUS baseURL property does not exist!"); + performShutdownAfterValidation = true; + } else { + if (json["baseURL"] == "") console.warn("EUS baseURL property is blank"); + const bURL = `${json["baseURL"]}`.split(""); + if (bURL.length > 1) { + if (bURL[bURL.length-1] != "/") console.warn("EUS baseURL property doesn't have a / at the end, this can lead to unpredictable results!"); + } + else { + if (json["baseURL"] != "http://" || json["baseURL"] != "https://") console.warn("EUS baseURL property is possibly invalid!"); + } + } + // acceptedTypes checks + if (json["acceptedTypes"] == null) { + console.error("EUS acceptedTypes array does not exist!"); + performShutdownAfterValidation = true; + } else { + if (json["acceptedTypes"].length < 1) console.warn("EUS acceptedTypes array has no extentions in it, users will not be able to upload images!"); + } + // uploadKey checks + if (json["uploadKey"] == null) { + console.error("EUS uploadKey property does not exist!"); + performShutdownAfterValidation = true; + } else { + if (json["uploadKey"] == "") useUploadKey = false; + } - // Check if server needs to be shutdown - if (performShutdownAfterValidation) { - console.error("EUS config properties are missing, refer to example config on GitHub (https://github.com/tgpholly/EUS)"); - process.exit(1); - } - else return true; -} - -function cleanURL(url = "") { - return url.split("%20").join(" ").split("%22").join("\"").split("%23").join("#").split("%24").join("$").split("%25").join("%").split("%26").join("&").split("%27").join("'").split("%2B").join("+").split("%2C").join(",").split("%2F").join("/") - .split("%3A").join(":").split("%3B").join(";").split("%3C").join("<").split("%3D").join("=").split("%3E").join(">").split("%3F").join("?") - .split("%40").join("@") - .split("%5B").join("[").split("%5C").join("\\").split("%5D").join("]").split("%5E").join("^") - .split("%60").join("`") - .split("%7B").join("{").split("%7C").join("|").split("%7D").join("}").split("%7E").join("~") - .split("%CE%A9").join("Ω"); -} - -let existanceCache = {}; - -function regularFile(req, res, urs = "", startTime = 0) { - if (req.url === "/") { urs = "/index.html" } else { urs = req.url } - fs.access(`${__dirname}${BASE_PATH}/files${urs}`, (error) => { - if (error) { - // Doesn't exist, send a 404 to the client. - error404Page(res); - global.modules.consoleHelper.printInfo(emoji.cross, `${req.method}: ${node_modules.chalk.red("[404]")} ${req.url} ${Date.now() - startTime}ms`); - } else { - // File does exist, send it back to the client. - res.sendFile(`${__dirname}${BASE_PATH}/files${req.url}`); - global.modules.consoleHelper.printInfo(emoji.heavy_check, `${req.method}: ${node_modules.chalk.green("[200]")} ${req.url} ${Date.now() - startTime}ms`); - } - }); -} - -function imageFile(req, res, file, startTime = 0) { - res.sendFile(`${__dirname}${BASE_PATH}/i/${file}`); - global.modules.consoleHelper.printInfo(emoji.heavy_check, `${req.method}: ${node_modules.chalk.green("[200]")} (ImageReq) ${req.url} ${Date.now() - startTime}ms`); -} - -function error404Page(res) { - res.status(404).send("404!
EUS"); + // Check if server needs to be shutdown + if (performShutdownAfterValidation) { + console.error("EUS config properties are missing, refer to docs for more details (https://docs.ethanus.ml)"); + process.exit(1); + } + else return true; } module.exports = { - init: init, - extras:async function() { - // Setup express to use busboy - global.app.use(node_modules.busboy()); - startupFinished = true; - }, - get:async function(req, res) { - /* - req - Request from client - res - Response from server - */ - - // Set some headers - res.set("Strict-Transport-Security", "max-age=31536000; includeSubDomains"); - res.set("X-XSS-Protection", "1; mode=block"); - res.set("Permissions-Policy", "microphone=(), geolocation=(), magnetometer=(), camera=(), payment=(), usb=(), accelerometer=(), gyroscope=()"); - res.set("Referrer-Policy", "strict-origin-when-cross-origin"); - res.set("Content-Security-Policy", "block-all-mixed-content;frame-ancestors 'self'"); - res.set("X-Frame-Options", "SAMEORIGIN"); - res.set("X-Content-Type-Options", "nosniff"); + extras:function() { + // Setup express to use busboy + global.app.use(busboy()); + cacheFilesAndSpace(); + }, + get:function(req, res) { + /* + req - Request from client + res - Response from server + */ + + // Set some headers + res.set("Strict-Transport-Security", "max-age=31536000; includeSubDomains"); + res.set("X-XSS-Protection", "1; mode=block"); + res.set("Feature-Policy", "fullscreen 'none'"); + res.set("Referrer-Policy", "strict-origin-when-cross-origin"); + res.set("Content-Security-Policy", "block-all-mixed-content;frame-ancestors 'self'"); + res.set("X-Frame-Options", "SAMEORIGIN"); + res.set("X-Content-Type-Options", "nosniff"); - req.url = decodeURIComponent(req.url.split("?")[0]); + // Check if returned value is true. + if (req.url.includes("/api/")) return handleAPI(req, res); - // The Funny Response - if (req.url.includes(".php") || req.url.includes("/wp-")) { - global.modules.consoleHelper.printWarn(emoji.globe_europe, `${req.method}: SUSSY ${req.headers["cf-connecting-ip"]} ${req.url}`); - return res.status(418).send("Unfortunately for you this server does not use PHP or WordPress.
EUS"); - } + // Register the time at the start of the request + d = new Date(); + startTime = d.getTime(); + // Get the requested image + let urs = ""+req.url; urs = urs.split("/")[1]; + // Get the file type of the image from image_json and make sure it exists + fs.access(__dirname + BASE_PATH + "/i/"+urs+image_json[urs], error => { + if (error) { + // Doesn't exist, handle request normaly + if (req.url === "/") { urs = "/index.html" } else { urs = req.url } + fs.access(__dirname + BASE_PATH + "/files"+urs, error => { + if (error) { + // Doesn't exist, send a 404 to the client. + res.status(404).end("404!"); + d = new Date(); + endTime = d.getTime(); + global.modules.consoleHelper.printInfo(emoji.cross, `${req.method}: ${chalk.red("[404]")} ${req.url} ${endTime - startTime}ms`); + } else { + // File does exist, send it back to the client. + res.sendFile(__dirname + BASE_PATH + "/files"+req.url); + d = new Date(); + endTime = d.getTime(); + global.modules.consoleHelper.printInfo(emoji.heavy_check, `${req.method}: ${chalk.green("[200]")} ${req.url} ${endTime - startTime}ms`); + } + }); + } else { + // Image does exist, send it back. + res.sendFile(__dirname + BASE_PATH + "/i/"+urs+image_json[urs]); + d = new Date(); + endTime = d.getTime(); + global.modules.consoleHelper.printInfo(emoji.heavy_check, `${req.method}: ${chalk.green("[200]")} ${req.url} ${endTime - startTime}ms`); + } + }); + }, + post:function(req, res) { + /* + req - Request from client + res - Response from server + */ - // Check if returned value is true. - if (req.url.includes("/api/")) return handleAPI(req, res); + // Make sure the endpoint is /upload + // If it isn't upload send an empty response + if (req.url != "/upload") return res.end(""); - // Register the time at the start of the request - const startTime = Date.now(); + // Get time at the start of upload - // Get the requested image - const urs = req.url.split("/")[1]; - // Check if we even need to query the DB - const dbAble = (!/[^0-9A-Za-z]/.test(urs)) && urs != "" && urs != "index" && (req.url.split("/").length == 2); + if (useUploadKey && eusConfig["uploadKey"] != req.header("key")) return res.end("Incorrect key provided for upload"); - if (dbAble) { - if (urs in existanceCache) { - const cachedFile = existanceCache[urs]; - imageFile(req, res, `${cachedFile.fileHash}.${cachedFile.fileType}`, startTime); - } else { - if (dbConnection.dbActive) { - // Try to get what we think is an image's details from the DB - const dbEntry = await dbConnection.query(`SELECT hash, imageType FROM images WHERE imageId = ? LIMIT 1`, [urs]); - - // There's an entry in the DB for this, send the file back. - if (dbEntry != null) { - existanceCache[urs] = { - fileHash: dbEntry.hash, - fileType: dbEntry.imageType - }; - imageFile(req, res, `${dbEntry.hash}.${dbEntry.imageType}`, startTime); - } - // There's no entry, so treat this as a regular file. - else regularFile(req, res, urs, startTime); - } - else res.status(400).end("EUS is restarting, please try again in a few secs."); - } - } - // We can still serve files if they are not dbable - // since we don't need to check the db - else regularFile(req, res, urs, startTime); - }, - post:async function(req, res) { - /* - req - Request from client - res - Response from server - */ + d = new Date(); startTime = d.getTime(); + var fstream; + var thefe; + // Pipe the request to busboy + req.pipe(req.busboy); + req.busboy.on('file', function (fieldname, file, filename) { + // Get the image-type json + image_json = require(`${__dirname}${BASE_PATH}/image-type.json`); + // Make a new file name + fileOutName = randomstring.generate(14); + global.modules.consoleHelper.printInfo(emoji.fast_up, `${req.method}: Upload of ${fileOutName} started.`); + // Check the file is within the accepted file types + if (eusConfig.acceptedTypes.includes(`.${filename.split(".")[filename.split(".").length-1]}`)) { + // File is accepted, set the extention of the file in thefe for later use. + thefe = `.${filename.split(".")[filename.split(".").length-1]}`; + } else { + // File isn't accepted, send response back to client stating so. + res.status(403).end("This file type isn't accepted currently."); + return; + } + // Create a write stream for the file + fstream = fs.createWriteStream(__dirname + BASE_PATH + "/i/" + fileOutName + thefe); + file.pipe(fstream); + fstream.on('close', function () { + // Get the time at the end of the upload + d = new Date(); endTime = d.getTime(); + // Add image file type to the image_json array + image_json[fileOutName] = `.${filename.split(".")[filename.split(".").length-1]}`; + // Save image_json array to the file + fs.writeFile(`${__dirname}${BASE_PATH}/image-type.json`, JSON.stringify(image_json), function(err) { + if (err) throw err; + global.modules.consoleHelper.printInfo(emoji.heavy_check, `${req.method}: Upload of ${fileOutName} finished. Took ${endTime - startTime}ms`); + // Send URL of the uploaded image to the client + res.end(eusConfig.baseURL+""+fileOutName); - // Make sure the endpoint is /upload - // If it isn't upload send an empty response - if (req.url != "/upload") return res.end(""); - - // Get time at the start of upload - - res.header("Access-Control-Allow-Origin", "*"); - - if (useUploadKey && eusConfig["uploadKey"] != req.header("key")) return res.end("Incorrect key provided for upload"); - - const startTime = Date.now(); - // Pipe the request to busboy - req.pipe(req.busboy); - req.busboy.on('file', function (fieldname, file, info) { - // Make a new file name - fileOutName = node_modules.randomstring.generate(14); - global.modules.consoleHelper.printInfo(emoji.fast_up, `${req.method}: Upload of ${fileOutName} started.`); - // Check the file is within the accepted file types - let fileType = info.filename.split(".").slice(-1); - if (info.filename === "blob") { - fileType = info.mimeType.split("/")[1]; - } - var thefe = ""; - if (eusConfig.acceptedTypes.includes(`.${fileType}`)) { - // File is accepted, set the extention of the file in thefe for later use. - thefe = fileType; - } else { - // File isn't accepted, send response back to client stating so. - res.status(403).end("This file type isn't accepted currently."); - return; - } - // Create a write stream for the file - let fstream = fs.createWriteStream("/tmp/EUS_UPLOADS/" + fileOutName); - file.pipe(fstream); - - // Get all file data for the file MD5 - let fileData = []; - file.on("data", (chunk) => { - fileData.push(chunk); - }); - - fstream.on('close', async () => { - let md5Buffer = Buffer.concat(fileData); - // bye - fileData = null; - - // Create MD5 hash of file - const hash = crypto.createHash("md5"); - hash.setEncoding("hex"); - hash.write(md5Buffer); - hash.end(); - - const fileHash = hash.read(); - const dataOnHash = await dbConnection.query("SELECT imageId FROM images WHERE hash = ? LIMIT 1", [fileHash]); - if (dataOnHash !== undefined) - { - fs.unlink(`/tmp/EUS_UPLOADS/${fileOutName}`, () => {}); - res.end(`${eusConfig.baseURL}${dataOnHash.imageId}`); - global.modules.consoleHelper.printInfo(emoji.heavy_check, `${req.method}: Hash matched! Sending ${dataOnHash.imageId} instead. Took ${Date.now() - startTime}ms`); - return; - } else { - fs.rename(`/tmp/EUS_UPLOADS/${fileOutName}`, `${__dirname}${BASE_PATH}/i/${fileHash}.${thefe[0]}`, async () => { - // Add to the existance cache - existanceCache[fileOutName] = { - fileHash: fileHash, - fileType: thefe[0] - }; - - // Store image data in db - await dbConnection.query(`INSERT INTO images (id, imageId, imageType, hash, fileSize) VALUES (NULL, ?, ?, ?, ?)`, [fileOutName, thefe[0], fileHash, md5Buffer.length]); - - // Send URL of the uploaded image to the client - res.end(eusConfig.baseURL + fileOutName); - global.modules.consoleHelper.printInfo(emoji.heavy_check, `${req.method}: Upload of ${fileOutName} finished. Took ${Date.now() - startTime}ms`); - - // Update cached files & space - if ((Date.now() - timeSinceLastCache) >= API_CACHE_LIFESPAN) { - cacheJSON = JSON.stringify(await cacheFilesAndSpace()); - } else { - const tempJson = JSON.parse(cacheJSON); - tempJson.fileCounts[thefe[0]]++; - cacheJSON = JSON.stringify(tempJson); - global.modules.consoleHelper.printInfo(emoji.folder, `Skiped api cache`); - } - cacheIsReady = true; - }); - } - }); - - /*fstream.on('close', async () => { - // Add this image to the database - await dbConnection.query(`INSERT INTO images (id, imageId, imageType, hash, imageSize) VALUES (NULL, "${fileOutName}", "${thefe}", ${meter.bytes})`); - - // Send URL of the uploaded image to the client - res.end(eusConfig.baseURL + fileOutName); - global.modules.consoleHelper.printInfo(emoji.heavy_check, `${req.method}: Upload of ${fileOutName} finished. Took ${Date.now() - startTime}ms`); - - // Update cached files & space - cacheJSON = JSON.stringify(await cacheFilesAndSpace()); - cacheIsReady = true; - });*/ - }); - } + // Update cached files & space + cacheFilesAndSpace(); + }); + }); + }); + } } -async function handleAPI(req, res) { - const startTime = Date.now(); - let jsonaa = {}, filesaa = 0, spaceaa = 0; - switch (req.url.split("?")[0]) { - // Status check to see the online status of EUS - // Used by ESL to make sure EUS is online - case "/api/get-server-status": - global.modules.consoleHelper.printInfo(emoji.heavy_check, `${req.method}: ${node_modules.chalk.green("[200]")} (APIReq) ${req.url} ${Date.now() - startTime}ms`); - return res.end('{"status":1,"version":"'+global.internals.version+'"}'); - - /* Stats api endpoint - Query inputs - f : Values [0,1] - s : Values [0,1] - */ - case "/api/get-stats": - filesaa = req.query["f"]; - spaceaa = req.query["s"]; - if (!cacheIsReady) return res.end("Cache is not ready"); - jsonaa = JSON.parse(cacheJSON); - // If total files is asked for - if (filesaa == 1) { - // If getting the space used on the server isn't required send the json - if (spaceaa != 1) { - global.modules.consoleHelper.printInfo(emoji.heavy_check, `${req.method}: ${node_modules.chalk.green("[200]")} (APIReq) ${req.url} ${Date.now() - startTime}ms`); - delete jsonaa["space"]; - return res.end(JSON.stringify(jsonaa)); - } - } - // Getting space is required - if (spaceaa == 1) { - global.modules.consoleHelper.printInfo(emoji.heavy_check, `${req.method}: ${node_modules.chalk.green("[200]")} (APIReq) ${req.url} ${Date.now() - startTime}ms`); - if (filesaa != 1) delete jsonaa["files"]; - return res.end(JSON.stringify(jsonaa)); - } +function handleAPI(req, res) { + d = new Date(); startTime = d.getTime(); + let jsonaa = {}, filesaa = 0, spaceaa = 0; + switch (req.url.split("?")[0]) { + // Status check to see the onlint status of EUS + // Used by ESL to make sure EUS is online + case "/api/get-server-status": + d = new Date(); endTime = d.getTime(); + global.modules.consoleHelper.printInfo(emoji.heavy_check, `${req.method}: ${chalk.green("[200]")} ${req.url} ${endTime - startTime}ms`); + return res.end('{ "status":1, "version":"'+global.internals.version+'" }'); + + /* Stats api endpoint + Query inputs + f : Values [0,1] + s : Values [0,1] + */ + case "/api/get-stats": + filesaa = req.query["f"]; + spaceaa = req.query["s"]; + if (!cacheIsReady) return res.end("Cache is not ready"); + jsonaa = JSON.parse(cacheJSON); + // If total files is asked for + if (filesaa == 1) { + // If getting the space used on the server isn't required send the json + if (spaceaa != 1) { + d = new Date(); endTime = d.getTime(); + global.modules.consoleHelper.printInfo(emoji.heavy_check, `${req.method}: ${chalk.green("[200]")} ${req.url} ${endTime - startTime}ms`); + delete jsonaa["space"]; + return res.end(JSON.stringify(jsonaa)); + } + } + // Getting space is required + if (spaceaa == 1) { + d = new Date(); endTime = d.getTime(); + global.modules.consoleHelper.printInfo(emoji.heavy_check, `${req.method}: ${chalk.green("[200]")} ${req.url} ${endTime - startTime}ms`); + if (filesaa != 1) delete jsonaa["files"]; + return res.end(JSON.stringify(jsonaa)); + } - if (filesaa != 1 && spaceaa != 1) { - global.modules.consoleHelper.printInfo(emoji.heavy_check, `${req.method}: ${node_modules.chalk.green("[200]")} (APIReq) ${req.url} ${Date.now() - startTime}ms`); - return res.end("Please add f and or s to your queries to get the files and space"); - } - break; + if (filesaa != 1 && spaceaa != 1) { + d = new Date(); endTime = d.getTime(); + global.modules.consoleHelper.printInfo(emoji.heavy_check, `${req.method}: ${chalk.green("[200]")} ${req.url} ${endTime - startTime}ms`); + return res.end("Please add f and or s to your queries to get the files and space"); + } + break; - // Information API - case "/api/get-info": - global.modules.consoleHelper.printInfo(emoji.heavy_check, `${req.method}: ${node_modules.chalk.green("[200]")} (APIReq) ${req.url} ${Date.now() - startTime}ms`); - return res.end(JSON.stringify({ - version: global.internals.version, - instance: config["server"]["instance_type"] - })); + // Information API + case "/api/get-info": + d = new Date(); endTime = d.getTime(); + global.modules.consoleHelper.printInfo(emoji.heavy_check, `${req.method}: ${chalk.green("[200]")} ${req.url} ${endTime - startTime}ms`); + return res.end(JSON.stringify({ + version: global.internals.version, + instance: config["server"]["instance_type"] + })); - default: - global.modules.consoleHelper.printInfo(emoji.heavy_check, `${req.method}: ${node_modules.chalk.green("[200]")} (APIReq) ${req.url} ${Date.now() - startTime}ms`); - return res.send(` -

All currently avaliable api endpoints

- /api/get-server-status - /api/get-stats - /api/get-info - `); - } + default: + d = new Date(); endTime = d.getTime(); + global.modules.consoleHelper.printInfo(emoji.heavy_check, `${req.method}: ${chalk.green("[200]")} ${req.url} ${endTime - startTime}ms`); + return res.send(` +

All currently avaliable api endpoints

+ /api/get-server-status + /api/get-stats + /api/get-info + `); + } } -const spaceValues = ["B", "KB", "MB", "GB", "TB", "PB", "EB"]; // Futureproofing™ +const spaceValues = ["B", "KB", "MB", "GB", "TB", "PB", "EB"]; // Futureproofing:tm: -async function spaceToLowest(spaceValue, includeStringValue) { - return new Promise((resolve, reject) => { - // Converts space values to lower values e.g MB, GB, TB etc depending on the size of the number - let i1 = 1; - // Loop through until value is at it's lowest - while (spaceValue >= 1024) { - spaceValue = spaceValue / 1024; - if (spaceValue >= 1024) i1++; - } +function spaceToLowest(spaceValue, includeStringValue) { + // Converts space values to lower values e.g MB, GB, TB etc depending on the size of the number + let i1 = 1; + // Loop through until value is at it's lowest + for (let i = 0; i < i1; i++) { + if (spaceValue >= 1024) { + spaceValue = spaceValue / 1024; + } - if (includeStringValue) resolve(`${spaceValue.toFixed(2)} ${spaceValues[i1]}`); - else resolve(spaceValue); - }); + if (spaceValue >= 1024) i1++; + } + + if (includeStringValue) return `${spaceValue.toFixed(2)} ${spaceValues[i1]}`; + else return spaceValue; } -module.exports.MOD_FUNC = MODULE_FUNCTION; - -module.exports.REQUIRED_NODE_MODULES = [ - "chalk", "connect-busboy", "randomstring", - "stream-meter", "mysql2" -]; +module.exports.MOD_FUNC = MODULE_FUNCTION; \ No newline at end of file diff --git a/LICENSE b/LICENSE index 3498e1d..ced896f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 Holly Stubbs (tgpholly) +Copyright (c) 2020 Ethan Stubbs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 66330af..d9e49af 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@

- +

- EUS is my public screenshot server built using Revolution
+ EUS is my public screenshot server built using Revolution

- CodeFactor + CodeFactor

@@ -14,23 +14,24 @@ EUS has extra dependencies other than those of [Revolution](https://github.com/tgpethan/Revolution), the server EUS is made on, of which include: - [connect-busboy](https://www.npmjs.com/package/connect-busboy) - [randomstring](https://www.npmjs.com/package/randomstring) + - [get-folder-size](https://www.npmjs.com/package/get-folder-size) - [diskusage](https://www.npmjs.com/package/diskusage) - - [stream-meter](https://www.npmjs.com/package/stream-meter) - - [mysql](https://www.npmjs.com/package/mysql) -Simply drop the EUS.js into a Revolution instance's modules folder **(If you still have [example_request_handler.js](https://github.com/tgpethan/Revolution/blob/master/modules/example_request_handler.js) be sure to delete it!)** and the extra required modules should be automatically installed. +Install the dependencies and then simply drop the EUS.js into a Revolution instance's modules folder **(If you still have [example_request_handler.js](https://github.com/tgpethan/Revolution/blob/master/modules/example_request_handler.js) be sure to delete it!)** ## Config On first startup EUS will create a new config file in the **modules/EUS/** folder, some of these values may need to be changed depending on your use case. -The value of **baseURL** will need to be changed to what you access the server from, for example if the server's ip is 192.168.1.100 and you are not planning to use EUS at a url you would change the value to **http://192.168.1.100/**. **baseURL** is used to construct the response url for file uploads, for example the value of **baseURL** on my instance of EUS is **https://eusv.net/**. +The value of **baseURL** will need to be changed to what you access the server from, for example if the server's ip is 192.168.1.100 and you are not planning to use EUS at a url you would change the value to **http://192.168.1.100/**. **baseURL** is used to construct the response url for file uploads, for example the value of **baseURL** on my instance of EUS is **https://eusv.ml/**. If you want to expand the files that the server allows to be sent to it this can be done in the **allowedTypes** array. By default the array contains **png, jpg and gif**. The value of **uploadKey** is used to restrict who can upload to your server, set this to something and the server will restrict who can upload depending on if they provided the key or not. If this field is left blank EUS will asume you don't want an upload key and uploads to it will be unrestricted ## API -EUS has 3 api endpoints, they are **[/api/get-stats](https://eusv.net/api/get-stats)**, **[/api/get-info](https://eusv.net/api/get-info)** and **[/api/get-server-status](https://eusv.net/api/get-server-status)** +EUS has 3 apis, they are located at **[/api/get-stats](https://eusv.ml/api/get-stats)**, **[/api/get-info](https://eusv.ml/api/get-info)** and **[/api/get-server-status](https://eusv.ml/api/get-server-status)** + +[These are better documented on EUS Docs](https://docs.eusv.ml) ## Websites that use EUS -[EUS](https://eusv.net) +[EUS](https://eusv.ml) diff --git a/config.example.json b/config.example.json deleted file mode 100755 index c63b560..0000000 --- a/config.example.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "baseURL":"https://example.com/", - "acceptedTypes": [ - ".png", - ".jpg", - ".jpeg", - ".gif", - ".mp4" - ], - "uploadKey":"", - "database": { - "databaseAddress": "127.0.0.1", - "databasePort": 3306, - "databaseUsername": "root", - "databasePassword": "password", - "databaseName": "EUS" - } -} diff --git a/quick-setup.sh b/quick-setup.sh new file mode 100755 index 0000000..9c4c87a --- /dev/null +++ b/quick-setup.sh @@ -0,0 +1,8 @@ +#!/bin/bash +git clone https://github.com/tgpethan/Revolution.git +cd Revolution +npm i +wget https://raw.githubusercontent.com/tgpethan/EUS/master/EUS.js -P modules/ +rm modules/example_request_handler.js +cp config/config.example.json config/config.json +npm i connect-busboy randomstring diskusage get-folder-size