const express = require('express') const app = express() const rateLimit = require('express-rate-limit') const helmet = require('helmet') const bp = require('body-parser') const cookieParser = require('cookie-parser') const createDOMPurify = require('dompurify') const { JSDOM } = require('jsdom') const Controller = require('./source/controller') const readfile = require('./source/readfile') const session = require('./source/session') const logger = require('./source/logger')() const mime = { html: 'text/html', txt: 'text/plain', css: 'text/css', xml: 'text/xml', gif: 'image/gif', jpg: 'image/jpeg', png: 'image/png', webp: 'image/webp', avif: 'image/avif', ico: 'image/ico', svg: 'image/svg+xml', js: 'application/javascript', mp4: 'video/mp4', webm: 'video/webm' } const assetDir = [ 'js', 'css', 'img', 'vid' ] const limiter = rateLimit({ windowMs: 30 * 1000, max: 100, message: '
You have exceeded the allowed number of requests. Please try again later.
', }) require('dotenv').config() const rss = require('./source/rss')({baseUrl: process.env.baseUrl}) // Handles the routes class App { constructor() { this.readfile = new readfile({ baseUrl: process.env.baseUrl, mime: mime, header: 'header.html', footer: 'footer.html' }) this.controller = new Controller() } App() { app.use(bp.json()) app.use(cookieParser()) if (process.env.NODE_ENV === 'prod') { app.use(helmet()) app.use(limiter) } app.route('*') .get(this.#ValidateCookie, this.#Logger, (req, res) => { this.controller.Main().then(output => { this.path = { split: req.path.split('/'), string: req.path } if (assetDir.includes(this.path.split[1])) { const data = this.readfile.GetFile(req.path) data.mime.then(output => this.#FileOpen({data: data, mime: output, res: res})) } else { switch (this.path.split[1]) { case 'favicon.ico': this.data = this.readfile.GetFavicon() this.data.mime.then(output => this.#FileOpen({data: this.data, mime: output, res: res})) break case 'robots.txt': this.data = this.readfile.GetRobots() this.data.mime.then(output => this.#FileOpen({data: this.data, mime: output, res: res})) break case 'sitemap.xml': this.data = this.readfile.Sitemap({keys: Array.from(output.keys()), url: process.env.baseUrl}) this.data.then(data => { res.set('Content-Type', mime.xml) res.send(data) }) break case 'rss': res.set('Content-Type', mime.xml) rss.GetFeed({ title: process.env.rssTitle, description: process.env.rssDescription, author: process.env.rssAuthor }).then(output => res.send(output)) break default: if (this.path.string.endsWith('/') && this.path.string.length > 1) this.path.string = this.path.string.substring(0, this.path.string.length - 1) if (output.has(this.path.string)) { try { output.get(this.path.string).then(output => res.send(this.readfile.GetMain(output.meta))) } catch { res.send(this.readfile.GetMain(output.get(this.path.string).meta)) } } else { res.status(404).send(this.readfile.GetMain({title: 'Page not found', description: '', keywords: ''})) } break } } }) }) .post(this.#Logger, (req, res) => { this.path = { split: req.path.split('/'), string: this.#RemoveBaseUrl(req.body.url) } if (this.path.string.endsWith('/') && this.path.string.length > 1) this.path.string = this.path.string.substring(0, this.path.string.length - 1) this.controller.Main(req.body.value).then(output => { if (output.has(this.path.string)) { try { output.get(this.path.string).then(output => { if (output.hasOwnProperty('post') ? output.post : false) { const DOMPurify = createDOMPurify(new JSDOM('').window) const clean = DOMPurify.sanitize(output.html) res.send(JSON.stringify({post: output.post, html: clean, css: output.css, js: output.js})) } else { const object = { js: output.assets.js, css: output.assets.css } this.readfile.Create(output.html).then(output => { const DOMPurify = createDOMPurify(new JSDOM('').window) const clean = DOMPurify.sanitize(output.layouts.header + output.layouts.data + output.layouts.footer, { ADD_TAGS: ['iframe'], ADD_ATTR: ['allow', 'allowfullscreen', 'frameborder', 'scrolling'] }) res.send(JSON.stringify({html: clean, css: object.css, js: object.js})) }) } }) } catch { const object = {} if (output.get(this.path.string).hasOwnProperty('assets')) { this.assets = output.get(this.path.string).assets object['js'] = this.assets.hasOwnProperty('js') ? this.assets.js : [] object['css'] = this.assets.hasOwnProperty('css') ? this.assets.css : [] } if (output.hasOwnProperty('post') ? output.post : false) { const DOMPurify = createDOMPurify(new JSDOM('').window) const clean = DOMPurify.sanitize(output.html) res.send(JSON.stringify({post: output.post, html: clean, css: output.css, js: output.js})) } else { this.readfile.Create(output.get(this.path.string).html).then(output => { const DOMPurify = createDOMPurify(new JSDOM('').window) const clean = DOMPurify.sanitize(output.layouts.header + output.layouts.data + output.layouts.footer, { ADD_TAGS: ['iframe'], ADD_ATTR: ['allow', 'allowfullscreen', 'frameborder', 'scrolling'] }) res.send(JSON.stringify({html: clean, css: object.css, js: object.js})) }) } } } else { res.send(JSON.stringify({html: this.readfile.NotFound(), css: ['main.css'], js: ['purify.js', '404.js']})) } }) }) app.listen(3000) } #Logger(req, res, next) { const { cookies } = req try { logger.Info(`${req.method} ${req.path} from ${req.ip}:${cookies.session_id.replace('session_id', '')}`) } catch (err) { logger.Info(`${req.method} ${req.path} from ${req.ip}:n/a`) } next() } #FileOpen(x) { x.res.set('Content-Type', x.mime) x.data.file.on('open', () => x.data.file.pipe(x.res)).on('error', () => { x.res.set('Content-Type', mime.html) x.res.status(404).send('File not found') }) } #ValidateCookie(req, res, next) { const { cookies } = req if ('session_id' in cookies) { if (session().Exists(cookies.session_id.replace('session_id', ''))) { session().Expired() } else { res.clearCookie('session_id') res.cookie('session_id', session().Create()) } } else res.cookie('session_id', session().Create()) next() } #RemoveBaseUrl(x) { const object = { baseUrlPattern: /^https?:\/\/[a-z\:0-9.]+/, result: '', url: '' } const match = object.baseUrlPattern.exec(x) if (match !== null) object.result = match[0] if (object.result.length > 0) object.result = match[0] if (object.result.length > 0) object.url = x.replace(object.result, '') return object.url } } new App().App()