diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js index 666820bdaf..51a8d0a64d 100644 --- a/public/src/ajaxify.js +++ b/public/src/ajaxify.js @@ -24,17 +24,18 @@ $(document).ready(function() { function onAjaxError(err, url, callback, quiet) { - var data = err.data, textStatus = err.textStatus; - - $('#content, #footer').removeClass('ajaxifying'); + var data = err.data, + textStatus = err.textStatus; if (data) { - if (data.status === 404) { - return ajaxify.go('404'); + if (data.status === 404 || data.status === 500) { + $('#footer, #content').removeClass('hide').addClass('ajaxifying'); + return renderTemplate(url, data.status.toString(), data.responseJSON, (new Date()).getTime(), callback); } else if (data.status === 401) { app.alertError('[[global:please_log_in]]'); return ajaxify.go('login'); } else if (data.status === 403) { + $('#content, #footer').removeClass('ajaxifying'); app.alertError('[[error:no-privileges]]'); } else if (data.status === 302) { return ajaxify.go(data.responseJSON.slice(1), callback, quiet); @@ -56,10 +57,8 @@ $(document).ready(function() { apiXHR.abort(); } - // Remove trailing slash - url = url.replace(/\/$/, ""); - - url = ajaxify.removeRelativePath(url); + // Remove relative path and trailing slash + url = ajaxify.removeRelativePath(url.replace(/\/$/, '')); var tpl_url = ajaxify.getTemplateMapping(url); @@ -80,8 +79,8 @@ $(document).ready(function() { translator.load(config.defaultLang, tpl_url); $('#footer, #content').removeClass('hide').addClass('ajaxifying'); - var animationDuration = parseFloat($('#content').css('transition-duration')) || 0.2, - startTime = (new Date()).getTime(); + + var startTime = (new Date()).getTime(); ajaxify.variables.flush(); ajaxify.loadData(url, function(err, data) { @@ -89,48 +88,54 @@ $(document).ready(function() { return onAjaxError(err, url, callback, quiet); } - $(window).trigger('action:ajaxify.loadingTemplates', {}); + renderTemplate(url, tpl_url, data, startTime, callback); - templates.parse(tpl_url, data, function(template) { - translator.translate(template, function(translatedTemplate) { - setTimeout(function() { - $('#content').html(translatedTemplate); + require(['search'], function(search) { + search.topicDOM.end(); + }); + }); - ajaxify.variables.parse(); + return true; + } - ajaxify.widgets.render(tpl_url, url, function() { - $(window).trigger('action:ajaxify.end', {url: url}); - }); + return false; + }; - $(window).trigger('action:ajaxify.contentLoaded', {url: url}); + function renderTemplate(url, tpl_url, data, startTime, callback) { + var animationDuration = parseFloat($('#content').css('transition-duration')) || 0.2; + $(window).trigger('action:ajaxify.loadingTemplates', {}); - ajaxify.loadScript(tpl_url); + templates.parse(tpl_url, data, function(template) { + translator.translate(template, function(translatedTemplate) { + setTimeout(function() { + $('#content').html(translatedTemplate); - if (typeof callback === 'function') { - callback(); - } + ajaxify.variables.parse(); - app.processPage(); + ajaxify.widgets.render(tpl_url, url, function() { + $(window).trigger('action:ajaxify.end', {url: url}); + }); - $('#content, #footer').removeClass('ajaxifying'); - ajaxify.initialLoad = false; + $(window).trigger('action:ajaxify.contentLoaded', {url: url}); - app.refreshTitle(url); - }, animationDuration * 1000 - ((new Date()).getTime() - startTime)); + ajaxify.loadScript(tpl_url); - }); - }); + if (typeof callback === 'function') { + callback(); + } - require(['search'], function(search) { - search.topicDOM.end(); - }); - }); + app.processPage(); - return true; - } + $('#content, #footer').removeClass('ajaxifying'); + ajaxify.initialLoad = false; - return false; - }; + app.refreshTitle(url); + }, animationDuration * 1000 - ((new Date()).getTime() - startTime)); + + }); + }); + + } ajaxify.removeRelativePath = function(url) { if (url.indexOf(RELATIVE_PATH.slice(1)) === 0) { diff --git a/src/controllers/accounts.js b/src/controllers/accounts.js index e0d15c446c..01f63b345d 100644 --- a/src/controllers/accounts.js +++ b/src/controllers/accounts.js @@ -140,7 +140,7 @@ accountsController.getAccount = function(req, res, next) { } if (!userData) { - return helpers.notFound(res); + return helpers.notFound(req, res); } async.parallel({ @@ -198,7 +198,7 @@ function getFollow(route, name, req, res, next) { function(data, next) { userData = data; if (!userData) { - return helpers.notFound(res); + return helpers.notFound(req, res); } var method = name === 'following' ? 'getFollowing' : 'getFollowers'; user[method](userData.uid, next); @@ -223,7 +223,7 @@ accountsController.getFavourites = function(req, res, next) { } if (!userData) { - return helpers.notFound(res); + return helpers.notFound(req, res); } if (parseInt(userData.uid, 10) !== callerUID) { @@ -252,7 +252,7 @@ accountsController.getPosts = function(req, res, next) { } if (!userData) { - return helpers.notFound(res); + return helpers.notFound(req, res); } posts.getPostsFromSet('uid:' + userData.uid + ':posts', callerUID, 0, 19, function(err, userPosts) { if (err) { @@ -276,7 +276,7 @@ accountsController.getTopics = function(req, res, next) { } if (!userData) { - return helpers.notFound(res); + return helpers.notFound(req, res); } var set = 'uid:' + userData.uid + ':topics'; @@ -359,7 +359,7 @@ accountsController.accountSettings = function(req, res, next) { } if (!userData) { - return helpers.notFound(res); + return helpers.notFound(req, res); } async.parallel({ @@ -502,7 +502,7 @@ accountsController.getNotifications = function(req, res, next) { accountsController.getChats = function(req, res, next) { if (parseInt(meta.config.disableChat) === 1) { - return helpers.notFound(res); + return helpers.notFound(req, res); } async.parallel({ contacts: async.apply(user.getFollowing, req.user.uid), @@ -536,7 +536,7 @@ accountsController.getChats = function(req, res, next) { async.apply(user.getUidByUserslug, req.params.userslug), function(toUid, next) { if (!toUid) { - return helpers.notFound(res); + return helpers.notFound(req, res); } async.parallel({ toUser: async.apply(user.getUserFields, toUid, ['uid', 'username']), diff --git a/src/controllers/categories.js b/src/controllers/categories.js index 40b87f4db4..2a7b903529 100644 --- a/src/controllers/categories.js +++ b/src/controllers/categories.js @@ -102,7 +102,7 @@ categoriesController.get = function(req, res, next) { userPrivileges; if (req.params.topic_index && !utils.isNumber(req.params.topic_index)) { - return helpers.notFound(res); + return helpers.notFound(req, res); } async.waterfall([ @@ -124,11 +124,11 @@ categoriesController.get = function(req, res, next) { }, function(results, next) { if (!results.exists || (results.categoryData && parseInt(results.categoryData.disabled, 10) === 1)) { - return helpers.notFound(res); + return helpers.notFound(req, res); } if (cid + '/' + req.params.slug !== results.categoryData.slug) { - return helpers.notFound(res); + return helpers.notFound(req, res); } if (!results.privileges.read) { diff --git a/src/controllers/groups.js b/src/controllers/groups.js index 254845fe8f..3843672a09 100644 --- a/src/controllers/groups.js +++ b/src/controllers/groups.js @@ -3,6 +3,7 @@ var groups = require('../groups'), async = require('async'), nconf = require('nconf'), + helpers = require('./helpers'), groupsController = {}; groupsController.list = function(req, res, next) { @@ -19,7 +20,7 @@ groupsController.list = function(req, res, next) { }); }; -groupsController.details = function(req, res) { +groupsController.details = function(req, res, next) { var uid = req.user ? parseInt(req.user.uid, 10) : 0; async.parallel({ group: function(next) { @@ -31,11 +32,15 @@ groupsController.details = function(req, res) { groups.getLatestMemberPosts(req.params.name, 10, uid, next); } }, function(err, results) { - if (!err) { - res.render('groups/details', results); - } else { - res.redirect(nconf.get('relative_path') + '/404'); + if (err) { + return next(err); } + + if (!results.group) { + return helpers.notFound(req, res); + } + + res.render('groups/details', results); }); }; diff --git a/src/controllers/helpers.js b/src/controllers/helpers.js index 7df83e5829..7d7c8f321d 100644 --- a/src/controllers/helpers.js +++ b/src/controllers/helpers.js @@ -4,11 +4,11 @@ var nconf = require('nconf'); var helpers = {}; -helpers.notFound = function(res) { +helpers.notFound = function(req, res, error) { if (res.locals.isAPI) { - res.status(404).json('not-found'); + res.status(404).json({path: req.path.replace(/^\/api/, ''), error: error}); } else { - res.status(404).render('404'); + res.status(404).render('404', {path: req.path, error: error}); } }; diff --git a/src/controllers/index.js b/src/controllers/index.js index 5631170474..2398ae7b00 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -104,6 +104,10 @@ Controllers.home = function(req, res, next) { }; Controllers.search = function(req, res, next) { + if (!plugins.hasListeners('filter:search.query')) { + return helpers.notFound(req, res); + } + if (!req.params.term) { return res.render('search', { time: 0, @@ -115,10 +119,6 @@ Controllers.search = function(req, res, next) { var uid = req.user ? req.user.uid : 0; - if (!plugins.hasListeners('filter:search.query')) { - return res.redirect('/404'); - } - req.params.term = validator.escape(req.params.term); search.search(req.params.term, uid, function(err, results) { @@ -198,8 +198,8 @@ Controllers.confirmEmail = function(req, res, next) { }; Controllers.sitemap = function(req, res, next) { - if (meta.config['feeds:disableSitemap'] === '1') { - return res.redirect(nconf.get('relative_path') + '/404'); + if (parseInt(meta.config['feeds:disableSitemap'], 10) === 1) { + return helpers.notFound(req, res); } var sitemap = require('../sitemap.js'); @@ -238,7 +238,7 @@ Controllers.outgoing = function(req, res, next) { Controllers.termsOfUse = function(req, res, next) { if (!meta.config.termsOfUse) { - return helpers.notFound(res); + return helpers.notFound(req, res); } res.render('tos', {termsOfUse: meta.config.termsOfUse}); }; diff --git a/src/controllers/topics.js b/src/controllers/topics.js index 5aae9774cb..76fcb3da91 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -22,7 +22,7 @@ topicsController.get = function(req, res, next) { userPrivileges; if (req.params.post_index && !utils.isNumber(req.params.post_index)) { - return helpers.notFound(res); + return helpers.notFound(req, res); } async.waterfall([ @@ -42,19 +42,11 @@ topicsController.get = function(req, res, next) { function (results, next) { userPrivileges = results.privileges; - if (userPrivileges.disabled) { - return helpers.notFound(res); + if (userPrivileges.disabled || tid + '/' + req.params.slug !== results.topic.slug) { + return helpers.notFound(req, res); } - if (tid + '/' + req.params.slug !== results.topic.slug) { - return helpers.notFound(res); - } - - if (!userPrivileges.read) { - return helpers.notAllowed(req, res); - } - - if (parseInt(results.topic.deleted, 10) && !userPrivileges.view_deleted) { + if (!userPrivileges.read || (parseInt(results.topic.deleted, 10) && !userPrivileges.view_deleted)) { return helpers.notAllowed(req, res); } @@ -71,7 +63,7 @@ topicsController.get = function(req, res, next) { } if (settings.usePagination && (req.query.page < 1 || req.query.page > pageCount)) { - return helpers.notFound(res); + return helpers.notFound(req, res); } var set = 'tid:' + tid + ':posts', @@ -114,7 +106,7 @@ topicsController.get = function(req, res, next) { topics.getTopicWithPosts(tid, set, uid, start, end, reverse, function (err, topicData) { if (err && err.message === '[[error:no-topic]]' && !topicData) { - return helpers.notFound(res); + return helpers.notFound(req, res); } if (err && !topicData) { diff --git a/src/groups.js b/src/groups.js index 780b38a77d..47e7002db9 100644 --- a/src/groups.js +++ b/src/groups.js @@ -85,15 +85,7 @@ var async = require('async'), async.parallel({ base: function (next) { if (ephemeralGroups.indexOf(groupName) === -1) { - db.getObject('group:' + groupName, function(err, groupObj) { - if (err) { - next(err); - } else if (!groupObj) { - next('group-not-found'); - } else { - next(err, groupObj); - } - }); + db.getObject('group:' + groupName, next); } else { internals.getEphemeralGroup(groupName, options, next); } @@ -120,7 +112,7 @@ var async = require('async'), }); } }, function (err, results) { - if (err) { + if (err || !results.base) { return callback(err); } @@ -498,7 +490,7 @@ var async = require('async'), // If this is a hidden group, and it is now empty, delete it Groups.get(groupName, {}, function(err, group) { - if (err) { + if (err || !group) { return callback(err); } diff --git a/src/install.js b/src/install.js index 2104161a67..ebb219d9b9 100644 --- a/src/install.js +++ b/src/install.js @@ -261,7 +261,11 @@ function enableDefaultTheme(next) { function createAdministrator(next) { var Groups = require('./groups'); Groups.get('administrators', {}, function (err, groupObj) { - if (!err && groupObj && groupObj.memberCount > 0) { + if (err) { + return next(err); + } + + if (groupObj && groupObj.memberCount > 0) { winston.info('Administrator found, skipping Admin setup'); next(); } else { diff --git a/src/middleware/middleware.js b/src/middleware/middleware.js index 3a173be4d7..5eb4b062bd 100644 --- a/src/middleware/middleware.js +++ b/src/middleware/middleware.js @@ -22,19 +22,16 @@ var app, ensureLoggedIn = require('connect-ensure-login'), controllers = { - api: require('./../controllers/api') + api: require('./../controllers/api'), + helpers: require('../controllers/helpers') }; middleware.authenticate = function(req, res, next) { - if(!req.user) { - if (res.locals.isAPI) { - return res.status(403).json('not-allowed'); - } else { - return res.redirect(nconf.get('url') + '/403'); - } - } else { - next(); + if (req.user) { + return next(); } + + helpers.notAllowed(req, res); }; middleware.applyCSRF = csrf(); @@ -135,23 +132,16 @@ middleware.prepareAPI = function(req, res, next) { }; middleware.guestSearchingAllowed = function(req, res, next) { - if (!req.user && meta.config.allowGuestSearching !== '1') { - return res.redirect('/403'); + if (!req.user && parseInt(meta.config.allowGuestSearching, 10) !== 1) { + return controllers.helpers.notAllowed(req, res); } next(); }; middleware.checkGlobalPrivacySettings = function(req, res, next) { - var callerUID = req.user ? parseInt(req.user.uid, 10) : 0; - - if (!callerUID && !!parseInt(meta.config.privateUserInfo, 10)) { - if (res.locals.isAPI) { - return res.status(403).json('not-allowed'); - } else { - req.session.returnTo = req.url; - return res.redirect('/login'); - } + if (!req.user && !!parseInt(meta.config.privateUserInfo, 10)) { + return controllers.helpers.notAllowed(req, res); } next(); @@ -162,8 +152,7 @@ middleware.checkAccountPermissions = function(req, res, next) { var callerUID = req.user ? parseInt(req.user.uid, 10) : 0; if (callerUID === 0) { - req.session.returnTo = req.url; - return res.redirect('/login'); + return controllers.helpers.notAllowed(req, res); } user.getUidByUserslug(req.params.userslug, function (err, uid) { @@ -172,7 +161,7 @@ middleware.checkAccountPermissions = function(req, res, next) { } if (!uid) { - return res.locals.isAPI ? res.status(404).json('not-found') : res.redirect(nconf.get('relative_path') + '/404'); + return controllers.helpers.notFound(req, res); } if (parseInt(uid, 10) === callerUID) { @@ -180,19 +169,11 @@ middleware.checkAccountPermissions = function(req, res, next) { } user.isAdministrator(callerUID, function(err, isAdmin) { - if(err) { + if (err || isAdmin) { return next(err); } - if(isAdmin) { - return next(); - } - - if (res.locals.isAPI) { - res.status(403).json('not-allowed'); - } else { - res.redirect(nconf.get('relative_path') + '/403'); - } + controllers.helpers.notAllowed(req, res); }); }); }; diff --git a/src/routes/feeds.js b/src/routes/feeds.js index a39f6874a8..7472f5f233 100644 --- a/src/routes/feeds.js +++ b/src/routes/feeds.js @@ -8,6 +8,7 @@ var async = require('async'), topics = require('../topics'), categories = require('../categories'), meta = require('../meta'), + helpers = require('../controllers/helpers'), privileges = require('../privileges'); function hasTopicPrivileges(req, res, next) { @@ -31,7 +32,7 @@ function hasPrivileges(method, id, req, res, next) { } if (!canRead) { - return res.redirect(nconf.get('relative_path') + '/403'); + return helpers.notAllowed(req, res); } return next(); @@ -53,7 +54,7 @@ function generateForTopic(req, res, next) { } if (topicData.deleted && !userPrivileges.view_deleted) { - return res.redirect(nconf.get('relative_path') + '/404'); + return helpers.notFound(req, res); } var description = topicData.posts.length ? topicData.posts[0].content : ''; @@ -137,8 +138,8 @@ function generateForPopular(req, res, next) { } function disabledRSS(req, res, next) { - if (meta.config['feeds:disableRSS'] === '1') { - return res.redirect(nconf.get('relative_path') + '/404'); + if (parseInt(meta.config['feeds:disableRSS'], 10) === 1) { + return helpers.notFound(req, res); } next(); diff --git a/src/routes/index.js b/src/routes/index.js index ff1d4f0eab..b9f9b70ff8 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -164,59 +164,62 @@ module.exports = function(app, middleware) { maxAge: app.enabled('cache') ? 5184000000 : 0 })); - app.use(catch404); - app.use(handleErrors); + handle404(app, middleware); + handleErrors(app, middleware); + // Add plugin routes plugins.init(app, middleware); authRoutes.reloadRoutes(); }; -function handleErrors(err, req, res, next) { - // we may use properties of the error object - // here and next(err) appropriately, or if - // we possibly recovered from the error, simply next(). - //console.error(err.stack, req.path); - winston.error(req.path + '\n', err.stack); - - if (err.code === 'EBADCSRFTOKEN') { - return res.sendStatus(403); - } - - var status = err.status || 500; - res.status(status); - - req.flash('errorMessage', err.message); - - res.redirect(nconf.get('relative_path') + '/500'); +function handle404(app, middleware) { + app.use(function(req, res, next) { + var relativePath = nconf.get('relative_path'); + var isLanguage = new RegExp('^' + relativePath + '/language/[\\w]{2,}/.*.json'), + isClientScript = new RegExp('^' + relativePath + '\\/src\\/.+\\.js'); + + if (isClientScript.test(req.url)) { + res.type('text/javascript').status(200).send(''); + } else if (isLanguage.test(req.url)) { + res.status(200).json({}); + } else if (req.accepts('html')) { + if (process.env.NODE_ENV === 'development') { + winston.warn('Route requested but not found: ' + req.url); + } + + res.status(404); + + if (res.locals.isAPI) { + return res.json({path: req.path, error: 'not-found'}); + } + + middleware.buildHeader(req, res, function() { + res.render('404', {path: req.path}); + }); + } else { + res.status(404).type('txt').send('Not found'); + } + }); } -function catch404(req, res, next) { - var relativePath = nconf.get('relative_path'); - var isLanguage = new RegExp('^' + relativePath + '/language/[\\w]{2,}/.*.json'), - isClientScript = new RegExp('^' + relativePath + '\\/src\\/.+\\.js'); - - res.status(404); +function handleErrors(app, middleware) { + app.use(function(err, req, res, next) { + winston.error(req.path + '\n', err.stack); - if (isClientScript.test(req.url)) { - res.type('text/javascript').status(200).send(''); - } else if (isLanguage.test(req.url)) { - res.status(200).json({}); - } else if (req.accepts('html')) { - if (process.env.NODE_ENV === 'development') { - winston.warn('Route requested but not found: ' + req.url); + if (err.code === 'EBADCSRFTOKEN') { + return res.sendStatus(403); } - res.redirect(relativePath + '/404'); - } else if (req.accepts('json')) { - if (process.env.NODE_ENV === 'development') { - winston.warn('Route requested but not found: ' + req.url); - } + res.status(err.status || 500); - res.json({ - error: 'Not found' - }); - } else { - res.type('txt').send('Not found'); - } + if (res.locals.isAPI) { + return res.json({path: req.path, error: err.message}); + } else { + middleware.buildHeader(req, res, function() { + res.render('500', {path: req.path, error: err.message}); + }); + } + }); } + diff --git a/src/routes/plugins.js b/src/routes/plugins.js index 7615aaef00..a035b853aa 100644 --- a/src/routes/plugins.js +++ b/src/routes/plugins.js @@ -8,12 +8,13 @@ var _ = require('underscore'), async = require('async'), winston = require('winston'), - plugins = require('../plugins'); + plugins = require('../plugins'), + helpers = require('../controllers/helpers'); module.exports = function(app, middleware, controllers) { // Static Assets - app.get('/plugins/:id/*', middleware.addExpiresHeaders, function(req, res) { + app.get('/plugins/:id/*', middleware.addExpiresHeaders, function(req, res, next) { var relPath = req._parsedUrl.pathname.replace('/plugins/', ''), matches = _.map(plugins.staticDirs, function(realPath, mappedPath) { if (relPath.match(mappedPath)) { @@ -35,19 +36,19 @@ module.exports = function(app, middleware, controllers) { } }); }, function(err, matches) { - // Filter out the nulls - matches = matches.filter(function(a) { - return a; - }); + if (err) { + return next(err); + } + matches = matches.filter(Boolean); if (matches.length) { res.sendFile(matches[0]); } else { - res.redirect(nconf.get('relative_path') + '/404'); + helpers.notFound(req, res); } }); } else { - res.redirect(nconf.get('relative_path') + '/404'); + helpers.notFound(req, res); } }); }; diff --git a/src/socket.io/admin/categories.js b/src/socket.io/admin/categories.js index 9c70e1bdf6..00992fc4b4 100644 --- a/src/socket.io/admin/categories.js +++ b/src/socket.io/admin/categories.js @@ -67,13 +67,18 @@ Categories.getPrivilegeSettings = function(socket, cid, callback) { async.reduce(privileges, [], function(members, privilege, next) { groups.get('cid:' + cid + ':privileges:' + privilege, { expand: true }, function(err, groupObj) { - if (!err) { - members = members.concat(groupObj.members); + if (err || !groupObj) { + return next(err, members); } + members = members.concat(groupObj.members); + next(null, members); }); }, function(err, members) { + if (err) { + return callback(err); + } // Remove duplicates var present = [], x = members.length, diff --git a/src/socket.io/admin/groups.js b/src/socket.io/admin/groups.js index c6254a2631..9aec355986 100644 --- a/src/socket.io/admin/groups.js +++ b/src/socket.io/admin/groups.js @@ -20,9 +20,7 @@ Groups.delete = function(socket, groupName, callback) { Groups.get = function(socket, groupName, callback) { groups.get(groupName, { expand: true - }, function(err, groupObj) { - callback(err, groupObj || undefined); - }); + }, callback); }; Groups.join = function(socket, data, callback) { diff --git a/src/views/404.tpl b/src/views/404.tpl index 2c2e4a89e9..5573720d0e 100644 --- a/src/views/404.tpl +++ b/src/views/404.tpl @@ -1,5 +1,5 @@
{error}
diff --git a/src/views/500.tpl b/src/views/500.tpl index 4680ee6642..13b0e9de89 100644 --- a/src/views/500.tpl +++ b/src/views/500.tpl @@ -1,5 +1,7 @@[[global:500.message]]
-{errorMessage}
+{path}
+
{error}
+