From 9ebfdeb7ee860a3fd9da599b30bd66eafe7738f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 25 May 2021 12:44:17 -0400 Subject: [PATCH] fix: #9580, proper 404 when ajaxifying --- src/controllers/404.js | 21 ++++++++++++++++++--- src/middleware/helpers.js | 35 +++++++++++++++++++++++++++++++++++ src/middleware/render.js | 36 +++--------------------------------- 3 files changed, 56 insertions(+), 36 deletions(-) diff --git a/src/controllers/404.js b/src/controllers/404.js index da7a8141cd..c364ccd3fc 100644 --- a/src/controllers/404.js +++ b/src/controllers/404.js @@ -7,6 +7,7 @@ const validator = require('validator'); const meta = require('../meta'); const plugins = require('../plugins'); const middleware = require('../middleware'); +const helpers = require('../middleware/helpers'); exports.handle404 = function handle404(req, res) { const relativePath = nconf.get('relative_path'); @@ -22,7 +23,13 @@ exports.handle404 = function handle404(req, res) { if (isClientScript.test(req.url)) { res.type('text/javascript').status(404).send('Not Found'); - } else if (req.path.startsWith(`${relativePath}/assets/uploads`) || (req.get('accept') && !req.get('accept').includes('text/html')) || req.path === '/favicon.ico') { + } else if ( + !res.locals.isAPI && ( + req.path.startsWith(`${relativePath}/assets/uploads`) || + (req.get('accept') && !req.get('accept').includes('text/html')) || + req.path === '/favicon.ico' + ) + ) { meta.errors.log404(req.path || ''); res.sendStatus(404); } else if (req.accepts('html')) { @@ -41,7 +48,11 @@ exports.send404 = async function (req, res) { res.status(404); const path = String(req.path || ''); if (res.locals.isAPI) { - return res.json({ path: validator.escape(path.replace(/^\/api/, '')), title: '[[global:404.title]]' }); + return res.json({ + path: validator.escape(path.replace(/^\/api/, '')), + title: '[[global:404.title]]', + bodyClass: helpers.buildBodyClass(req, res), + }); } if (req.method === 'GET') { @@ -49,5 +60,9 @@ exports.send404 = async function (req, res) { } await middleware.buildHeaderAsync(req, res); - await res.render('404', { path: validator.escape(path), title: '[[global:404.title]]' }); + await res.render('404', { + path: validator.escape(path), + title: '[[global:404.title]]', + bodyClass: helpers.buildBodyClass(req, res), + }); }; diff --git a/src/middleware/helpers.js b/src/middleware/helpers.js index cb67b454c5..b0bb86cefe 100644 --- a/src/middleware/helpers.js +++ b/src/middleware/helpers.js @@ -1,5 +1,9 @@ 'use strict'; +const winston = require('winston'); +const validator = require('validator'); +const slugify = require('../slugify'); + const helpers = module.exports; helpers.try = function (middleware) { @@ -20,3 +24,34 @@ helpers.try = function (middleware) { } }; }; + +helpers.buildBodyClass = function (req, res, templateData = {}) { + const clean = req.path.replace(/^\/api/, '').replace(/^\/|\/$/g, ''); + const parts = clean.split('/').slice(0, 3); + parts.forEach((p, index) => { + try { + p = slugify(decodeURIComponent(p)); + } catch (err) { + winston.error(err.stack); + p = ''; + } + p = validator.escape(String(p)); + parts[index] = index ? `${parts[0]}-${p}` : `page-${p || 'home'}`; + }); + + if (templateData.template && templateData.template.topic) { + parts.push(`page-topic-category-${templateData.category.cid}`); + parts.push(`page-topic-category-${slugify(templateData.category.name)}`); + } + + if (Array.isArray(templateData.breadcrumbs)) { + templateData.breadcrumbs.forEach((crumb) => { + if (crumb && crumb.hasOwnProperty('cid')) { + parts.push(`parent-category-${crumb.cid}`); + } + }); + } + + parts.push(`page-status-${res.statusCode}`); + return parts.join(' '); +}; diff --git a/src/middleware/render.js b/src/middleware/render.js index 6e5ddb6074..9132b18f93 100644 --- a/src/middleware/render.js +++ b/src/middleware/render.js @@ -2,14 +2,14 @@ const nconf = require('nconf'); const validator = require('validator'); -const winston = require('winston'); + const plugins = require('../plugins'); const meta = require('../meta'); const translator = require('../translator'); const widgets = require('../widgets'); const utils = require('../utils'); -const slugify = require('../slugify'); +const helpers = require('./helpers'); const relative_path = nconf.get('relative_path'); @@ -31,7 +31,7 @@ module.exports = function (middleware) { options.relative_path = relative_path; options.template = { name: template, [template]: true }; options.url = (req.baseUrl + req.path.replace(/^\/api/, '')); - options.bodyClass = buildBodyClass(req, res, options); + options.bodyClass = helpers.buildBodyClass(req, res, options); const buildResult = await plugins.hooks.fire(`filter:${template}.build`, { req: req, res: res, templateData: options }); if (res.headersSent) { @@ -123,34 +123,4 @@ module.exports = function (middleware) { const translated = await translator.translate(str, language); return translator.unescape(translated); } - - function buildBodyClass(req, res, templateData) { - const clean = req.path.replace(/^\/api/, '').replace(/^\/|\/$/g, ''); - const parts = clean.split('/').slice(0, 3); - parts.forEach((p, index) => { - try { - p = slugify(decodeURIComponent(p)); - } catch (err) { - winston.error(err.stack); - p = ''; - } - p = validator.escape(String(p)); - parts[index] = index ? `${parts[0]}-${p}` : `page-${p || 'home'}`; - }); - - if (templateData.template.topic) { - parts.push(`page-topic-category-${templateData.category.cid}`); - parts.push(`page-topic-category-${slugify(templateData.category.name)}`); - } - if (templateData.breadcrumbs) { - templateData.breadcrumbs.forEach((crumb) => { - if (crumb.hasOwnProperty('cid')) { - parts.push(`parent-category-${crumb.cid}`); - } - }); - } - - parts.push(`page-status-${res.statusCode}`); - return parts.join(' '); - } };