diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js index ff23e50527..76b1a545ba 100644 --- a/public/src/ajaxify.js +++ b/public/src/ajaxify.js @@ -160,7 +160,11 @@ ajaxify = window.ajaxify || {}; window.location.href = data.responseJSON.external; } else if (typeof data.responseJSON === 'string') { ajaxifyTimer = undefined; - ajaxify.go(data.responseJSON.slice(1), callback, quiet); + if (data.responseJSON.startsWith('http://') || data.responseJSON.startsWith('https://')) { + window.location.href = data.responseJSON; + } else { + ajaxify.go(data.responseJSON.slice(1), callback, quiet); + } } } } else if (textStatus !== 'abort') { diff --git a/src/controllers/category.js b/src/controllers/category.js index 1815f67f1a..53984d04a6 100644 --- a/src/controllers/category.js +++ b/src/controllers/category.js @@ -2,6 +2,7 @@ const nconf = require('nconf'); +const validator = require('validator'); const db = require('../database'); const privileges = require('../privileges'); @@ -29,7 +30,7 @@ categoryController.get = async function (req, res, next) { } const [categoryFields, userPrivileges, userSettings, rssToken] = await Promise.all([ - categories.getCategoryFields(cid, ['slug', 'disabled']), + categories.getCategoryFields(cid, ['slug', 'disabled', 'link']), privileges.categories.get(cid, req.uid), user.getSettings(req.uid), user.auth.getFeedToken(req.uid), @@ -52,6 +53,10 @@ categoryController.get = async function (req, res, next) { return helpers.redirect(res, '/category/' + categoryFields.slug, true); } + if (categoryFields.link) { + await db.incrObjectField('category:' + cid, 'timesClicked'); + return helpers.redirect(res, validator.unescape(categoryFields.link)); + } if (!userSettings.usePagination) { topicIndex = Math.max(0, topicIndex - (Math.ceil(userSettings.topicsPerPage / 2) - 1)); @@ -89,10 +94,7 @@ categoryController.get = async function (req, res, next) { } categories.modifyTopicsByPrivilege(categoryData.topics, userPrivileges); - if (categoryData.link) { - await db.incrObjectField('category:' + categoryData.cid, 'timesClicked'); - return helpers.redirect(res, categoryData.link); - } + await buildBreadcrumbs(req, categoryData); if (categoryData.children.length) { const allCategories = []; diff --git a/src/controllers/helpers.js b/src/controllers/helpers.js index 8b15ff4d2b..e5377b4b42 100644 --- a/src/controllers/helpers.js +++ b/src/controllers/helpers.js @@ -145,9 +145,11 @@ helpers.notAllowed = async function (req, res, error) { helpers.redirect = function (res, url, permanent) { if (res.locals.isAPI) { - res.set('X-Redirect', encodeURI(url)).status(200).json(url); + res.set('X-Redirect', encodeURI(url)).status(200).json(encodeURI(url)); } else { - res.redirect(permanent ? 308 : 307, relative_path + encodeURI(url)); + const redirectUrl = url.startsWith('http://') || url.startsWith('https://') ? + url : relative_path + url; + res.redirect(permanent ? 308 : 307, encodeURI(redirectUrl)); } }; diff --git a/test/controllers.js b/test/controllers.js index 5b4d2e0801..670ab3be34 100644 --- a/test/controllers.js +++ b/test/controllers.js @@ -2034,16 +2034,32 @@ describe('Controllers', function () { }); it('should redirect if category is a link', function (done) { + let cid; + let category; async.waterfall([ function (next) { categories.create({ name: 'redirect', link: 'https://nodebb.org' }, next); }, - function (category, next) { + function (_category, next) { + category = _category; + cid = category.cid; request(nconf.get('url') + '/api/category/' + category.slug, { jar: jar, json: true }, function (err, res, body) { assert.ifError(err); assert.equal(res.statusCode, 200); - assert.equal(res.headers['x-redirect'], 'https://nodebb.org'); - assert.equal(body, 'https://nodebb.org'); + assert.equal(res.headers['x-redirect'], 'https://nodebb.org'); + assert.equal(body, 'https://nodebb.org'); + next(); + }); + }, + function (next) { + categories.setCategoryField(cid, 'link', '/recent', next); + }, + function (next) { + request(nconf.get('url') + '/api/category/' + category.slug, { jar: jar, json: true }, function (err, res, body) { + assert.ifError(err); + assert.equal(res.statusCode, 200); + assert.equal(res.headers['x-redirect'], '/recent'); + assert.equal(body, '/recent'); next(); }); },