const whiskers = require('whiskers'); const fs = require('fs'); const ApiKeyModel = require('../models/apiKey.js').ApiKeyModel; const PasteContent = require('../models/pasteContent.js').PasteContent; const mCrypto = require('../src/crypto.js'); const Security = require('../src/security.js'); const CONFIG = require('../src/config.js'); async function renderRawPage(app, req, res, entity) { if (entity.type === 'paste') return await app.routerUtils.staticServe(res, app.getData(entity.privId)); else if (entity.type === 'file') { let data = JSON.parse(entity.data); return await app.routerUtils.staticDownload(res, app.getData(entity.privId), data.name, data.type); } else if (entity.type === 'short') return res.end(entity.data); // FIXME stats + ?rel app.routerUtils.onInternalError(res, "Unknown type: " +entity.type); } async function renderPublicPage(app, req, res, entity) { if (entity.type === 'paste') return await app.routerUtils.staticServe(res, app.getData(entity.privId)); else if (entity.type === 'file') { let data = JSON.parse(entity.data); // FIXME viewer return await app.routerUtils.staticDownload(res, app.getData(entity.privId), data.name, data.type); } else if (entity.type === 'short') return app.routerUtils.redirect(res, entity.data); app.routerUtils.onInternalError(res, "Unknown type: " +entity.type); } function renderPrivatePage(app, res, entity) { let stat; try { stat = fs.statSync(app.dataDir+entity.privId); } catch (e) { stat = { error: e }; } app.routerUtils.jsonResponse(res, { ...entity.describe(), ...stat, ...{ path: app.getData(entity.privId) } }); } module.exports = { register: app => { // Root app.router.get("/", (req, res) => { app.routerUtils.redirect(res, '/pastit'); }); // Access page app.router.get("/x/:id", async (req, res) => { let entity = await app.databaseHelper.findOne(PasteContent, { privId: req.params.id, publicId: req.params.id }, " or "); if (entity && entity.privId === req.params.id) return renderPrivatePage(app, res, entity); if (entity && !entity.expired) return renderPublicPage(app, req, res, entity); app.routerUtils.onPageNotFound(res); }); app.router.get("/x/raw/:id", async (req, res) => { let entity = await app.databaseHelper.findOne(PasteContent, { privId: req.params.id, publicId: req.params.id }, " or "); if (entity && !entity.expired) return renderRawPage(app, req, res, entity); app.routerUtils.onPageNotFound(res); }); // pastebin tool app.router.get("/pastit", (req, res) => { let context = app.routerUtils.commonRenderInfos(); context.page_title += " - Pastit"; res.end(whiskers.render(require('../templates/pastit.js'), context)); }); app.router.post("/pastit", async (req, res) => { const content = "" + (req.body.content || req.post); const privId = mCrypto.string(content); if (req.body['g-recaptcha-response']) { const captchaOk = await Security.captchaCheck(req.body['g-recaptcha-response'], Security.getRequestIp(req)); if (!captchaOk) return app.routerUtils.jsonResponse(res, { err: "Invalid captcha input", id: null }); } else if (req.body['apiKey']) { if (!(await app.databaseHelper.findOne(ApiKeyModel, { apiKey: req.body['apiKey'] }))) return app.routerUtils.jsonResponse(res, { err: "Unauthorized access", id: null }); } else { return app.routerUtils.jsonResponse(res, { err: "Unauthorized access", id: null }); } let entity = await app.databaseHelper.findOne(PasteContent, { privId: privId }); if (!content || !content.length) return app.routerUtils.jsonResponse(res, { err: "Empty input", id: null }); if (content.length > CONFIG.maxPastebinSize) return app.routerUtils.jsonResponse(res, { err: "Input size is too large", id: null }); if (entity && !entity.expired) { entity.renew(); await app.databaseHelper.update({privId: privId}, entity); } else { entity = entity || new PasteContent(privId, "paste", Security.getRequestIp(req)); entity.expired = false; entity.apiKey = req.body['apiKey'] || null; entity.renew(); fs.writeFileSync(app.getData(privId), content); await app.databaseHelper.upsertOne(entity); } if (req.body.apiKey) res.end(CONFIG.url+"/x/" +entity.publicId+"\r\n"); else app.routerUtils.jsonResponse(res, { err: null, id: entity.publicId }); }); // URL shortener tool app.router.get("/short", (req, res) => { let context = app.routerUtils.commonRenderInfos(); context.page_title += " - Shortener"; res.end(whiskers.render(require('../templates/short.js'), context)); }); app.router.post("/short", async (req, res) => { const link = "" + req.body.content; const privId = mCrypto.string(await app.databaseHelper.count(PasteContent) + link); const captchaOk = await Security.captchaCheck(req.body['g-recaptcha-response'], Security.getRequestIp(req)); if (!captchaOk) return app.routerUtils.jsonResponse(res, { err: "Invalid captcha input", id: null }); if (!link || !link.length) return app.routerUtils.jsonResponse(res, { err: "Empty input", id: null }); if (link.length > CONFIG.maxUrlSize) return app.routerUtils.jsonResponse(res, { err: "Input size is too large", id: null }); entity = new PasteContent(privId, "short", Security.getRequestIp(req)); entity.data = link; await app.databaseHelper.insertOne(entity); app.routerUtils.jsonResponse(res, { err: null, id: entity.privId }); }); // Files tool app.router.get("/files", (req, res) => { let context = app.routerUtils.commonRenderInfos(); context.page_title += " - File host"; res.end(whiskers.render(require('../templates/files.js'), context)); }); app.router.post("/files", async (req, res) => { const formData = (req.body["multipart-data"] || []).reduce((res, x) => { res[x.fieldName] = x; return res; }, {}); const privId = mCrypto.string(await app.databaseHelper.count(PasteContent) + (req.content?.fileName || "")); const captchaOk = await Security.captchaCheck(formData["g-recaptcha-response"]?.fileData, req.headers['x-forwarded-for'] || req.socket.remoteAddress); if (!captchaOk) return app.routerUtils.jsonResponse(res, { err: "Invalid captcha input", id: null }); if (!formData.content?.fileData || !formData.content.fileData.length) return app.routerUtils.jsonResponse(res, { err: "Empty input", id: null }); if (formData.content.fileData.length > CONFIG.maxFileUploadSize) return app.routerUtils.jsonResponse(res, { err: "Input size is too large", id: null }); const entity = new PasteContent(privId, "file", Security.getRequestIp(req)); entity.data = JSON.stringify({ name: formData.content.fileName, type: formData.content.fileType }); fs.writeFileSync(app.getData(privId), formData.content.fileData, {encoding: formData.content.fileType.indexOf('text') >= 0 ? 'utf8' : 'binary'}); await app.databaseHelper.insertOne(entity); app.routerUtils.jsonResponse(res, { err: null, id: entity.privId }); }); // API page app.router.get("/api", (req, res) => { let context = app.routerUtils.commonRenderInfos(); context.page_title += " - API Usage"; res.end(whiskers.render(require('../templates/api.js'), context)); }); app.router.post("/api", async (req, res) => { const ipAddress = req.headers['x-forwarded-for'] || req.socket.remoteAddress; const captchaOk = await Security.captchaCheck(req.body['g-recaptcha-response'], Security.getRequestIp(req)); if (!captchaOk) return app.routerUtils.jsonResponse(res, { err: "Invalid captcha input", id: null }); const privKey = mCrypto.string(Date.now() + "" + await app.databaseHelper.count(ApiKeyModel) + "SALT_INPUT_API_KEY" +ipAddress); const model = new ApiKeyModel(privKey, ipAddress); await app.databaseHelper.insertOne(model); return app.routerUtils.jsonResponse(res, { err: null, id: privKey }); }); }};