From 1f82419f647d92d81b216b8d1e7cd729134cdbb0 Mon Sep 17 00:00:00 2001 From: Martii Date: Mon, 16 Nov 2015 19:19:23 -0700 Subject: [PATCH] More encoding and controller symmetry * Some whitespace adjustment for STYLEGUIDE.md * Drop `slug` terminology except where currently needed... this still might be completely going away but smaller steps to avoid mass breakage and increase mutual understanding * Partial fix for an encoding component issue ... there are more TODO's needed to fix all of it * Some controller symmetry * Using some ...`URIComponent` as indicated in #795 ... needs individual handling of username and scriptname and avoids some encoding/decoding issues **NOTES** * This is not done yet but getting closer * Should be a near parallel change with a partial bug fix Applies to #819 , #200, and Originally applies to #262 ... treads on https://github.com/OpenUserJs/OpenUserJS.org/issues/262#issuecomment-5759212 *(preauth)* --- controllers/flag.js | 27 +- controllers/issue.js | 718 ++++++++++++++++++----------------- controllers/script.js | 266 ++++++------- controllers/scriptStorage.js | 54 ++- controllers/user.js | 77 ++-- libs/modelParser.js | 10 +- 6 files changed, 598 insertions(+), 554 deletions(-) diff --git a/controllers/flag.js b/controllers/flag.js index 6ce3b7266..a0d9b73b9 100644 --- a/controllers/flag.js +++ b/controllers/flag.js @@ -48,8 +48,9 @@ exports.flag = function (aReq, aRes, aNext) { var type = aReq.params[0]; var isLib = null; + var installName = null; - var path = aReq.params[1]; + var username = null; var authedUser = aReq.session.user; @@ -83,15 +84,16 @@ exports.flag = function (aReq, aRes, aNext) { isLib = true; // fallthrough case 'scripts': - installName = scriptStorage.getInstallName({ - params: { - username: aReq.params[2], - scriptname: aReq.params[3] - }}); - path += type === 'libs' ? '.js' : '.user.js'; + aReq.params.username = aReq.params[2]; + aReq.params.scriptname = aReq.params[3] + + installName = scriptStorage.getScriptBaseName(aReq); - Script.findOne({ installName: path }, + Script.findOne({ + installName: scriptStorage.caseSensitive(installName + + (isLib ? '.js' : '.user.js')) + }, function (aErr, aScript) { var fn = flagLib[flag ? 'flag' : 'unflag']; @@ -101,13 +103,16 @@ exports.flag = function (aReq, aRes, aNext) { } fn(Script, aScript, authedUser, reason, function (aFlagged) { - aRes.redirect((isLib ? '/libs/' : '/scripts/') + encodeURI(installName)); + aRes.redirect((isLib ? '/libs/' : '/scripts/') + scriptStorage.getScriptBaseName( + aReq, { encoding: 'uri' })); }); }); break; case 'users': - User.findOne({ name: { $regex: new RegExp('^' + path + '$', "i") } }, + username = aReq.params[1]; + + User.findOne({ name: { $regex: new RegExp('^' + username + '$', "i") } }, function (aErr, aUser) { var fn = flagLib[flag ? 'flag' : 'unflag']; @@ -117,7 +122,7 @@ exports.flag = function (aReq, aRes, aNext) { } fn(User, aUser, authedUser, reason, function (aFlagged) { - aRes.redirect('/users/' + encodeURI(path)); + aRes.redirect('/users/' + encodeURIComponent(username)); }); }); diff --git a/controllers/issue.js b/controllers/issue.js index 743750ea9..e2c5fabd1 100644 --- a/controllers/issue.js +++ b/controllers/issue.js @@ -39,215 +39,48 @@ var orderDir = require('../libs/templateHelpers').orderDir; // List script issues exports.list = function (aReq, aRes, aNext) { // - var installName = scriptStorage.getInstallName(aReq); + var installName = scriptStorage.getScriptBaseName(aReq); var type = aReq.params.type; Script.findOne({ installName: scriptStorage.caseSensitive(installName + (type === 'libs' ? '.js' : '.user.js')) - }, function (aErr, aScript) { - function preRender() { - options.discussionList = _.map(options.discussionList, modelParser.parseDiscussion); + }, function (aErr, aScript) { + function preRender() { + options.discussionList = _.map(options.discussionList, modelParser.parseDiscussion); - // Script - options.issuesCount = pagination.numItems; + // Script + options.issuesCount = pagination.numItems; - // Pagination - options.paginationRendered = pagination.renderDefault(aReq); + // Pagination + options.paginationRendered = pagination.renderDefault(aReq); - // Empty list - if (options.searchBarValue) { - if (options.allIssues) { - options.discussionListIsEmptyMessage = 'We couldn\'t find any discussions with this search value.'; - } else { - if (open) { - options.discussionListIsEmptyMessage = 'We couldn\'t find any open discussions with this search value.'; + // Empty list + if (options.searchBarValue) { + if (options.allIssues) { + options.discussionListIsEmptyMessage = 'We couldn\'t find any discussions with this search value.'; } else { - options.discussionListIsEmptyMessage = 'We couldn\'t find any closed discussions with this search value.'; + if (open) { + options.discussionListIsEmptyMessage = 'We couldn\'t find any open discussions with this search value.'; + } else { + options.discussionListIsEmptyMessage = 'We couldn\'t find any closed discussions with this search value.'; + } } - } - } else { - if (options.allIssues) { - options.discussionListIsEmptyMessage = 'No discussions.'; } else { - if (open) { - options.discussionListIsEmptyMessage = 'No open discussions.'; + if (options.allIssues) { + options.discussionListIsEmptyMessage = 'No discussions.'; } else { - options.discussionListIsEmptyMessage = 'No closed discussions.'; + if (open) { + options.discussionListIsEmptyMessage = 'No open discussions.'; + } else { + options.discussionListIsEmptyMessage = 'No closed discussions.'; + } } } } - } - - function render() { - aRes.render('pages/scriptIssueListPage', options); - } - - function asyncComplete() { - preRender(); - render(); - } - - // - var options = {}; - var authedUser = aReq.session.user; - var open = aReq.params.open !== 'closed'; - var script = null; - var category = null; - var discussionListQuery = null; - var listAll = aReq.params.open === 'all'; - var pagination = null; - var scriptOpenIssueCountQuery = null; - var tasks = []; - - if (aErr || !aScript) { - aNext(); - return; - } - - // Session - options.authedUser = authedUser = modelParser.parseUser(authedUser); - options.isMod = authedUser && authedUser.isMod; - options.isAdmin = authedUser && authedUser.isAdmin; - - // - options.openIssuesOnly = open; - - // Script - options.script = script = modelParser.parseScript(aScript); - options.isOwner = authedUser && authedUser._id == script._authorId; - - // Category - category = {}; - category.slug = type + '/' + installName + '/issues'; - category.name = 'Issues'; - category.description = ''; - category = modelParser.parseCategory(category); - category.categoryPageUrl = script.scriptIssuesPageUrl; - category.categoryPostDiscussionPageUrl = script.scriptOpenIssuePageUrl; - options.category = category; - - options.isScriptIssuesPage = true; - - // Order dir - orderDir(aReq, options, 'topic', 'asc'); - orderDir(aReq, options, 'comments', 'desc'); - orderDir(aReq, options, 'created', 'desc'); - orderDir(aReq, options, 'updated', 'desc'); - - // discussionListQuery - discussionListQuery = Discussion.find(); - - // discussionListQuery: category - discussionListQuery.find({ category: category.slug }); - - // discussionListQuery: Optionally filter discussion list - options.allIssues = !aReq.params.open && !options.isOwner || listAll; - if (!options.allIssues) { - modelQuery.findOrDefaultToNull(discussionListQuery, 'open', options.openIssuesOnly, true); - } - - // Page metadata - pageMetadata(options, - [ - (options.allIssues ? 'All' : (open ? 'Open' : 'Closed')) + ' Issues', - script.name, - (script.isLib ? 'Libraries' : 'Userscripts') - ], - category.description); - - // discussionListQuery: Defaults - modelQuery.applyDiscussionListQueryDefaults(discussionListQuery, options, aReq); - - // discussionListQuery: Pagination - pagination = options.pagination; // is set in modelQuery.apply___ListQueryDefaults - - // SearchBar - options.searchBarPlaceholder = 'Search Issues'; - - //--- Tasks - - // Show the number of open issues - scriptOpenIssueCountQuery = Discussion.find({ - category: script.issuesCategorySlug, - open: { $ne: false } - }); - tasks.push(countTask(scriptOpenIssueCountQuery, options, 'issueCount')); - - // Pagination - tasks.push(pagination.getCountTask(discussionListQuery, options, 'issueCount')); - - // discussionListQuery - tasks.push(execQueryTask(discussionListQuery, options, 'discussionList')); - - //--- - async.parallel(tasks, asyncComplete); - }); -}; - -// Show the discussion on an issue -exports.view = function (aReq, aRes, aNext) { - // - var installName = scriptStorage.getInstallName(aReq); - var type = aReq.params.type; - - Script.findOne({ - installName: scriptStorage.caseSensitive(installName + - (type === 'libs' ? '.js' : '.user.js')) - }, function (aErr, aScript) { - // - var options = {}; - var authedUser = aReq.session.user; - var script = null; - var category = null; - var topic = aReq.params.topic; - var tasks = []; - - if (aErr || !aScript) { - aNext(); - return; - } - - // Session - options.authedUser = authedUser = modelParser.parseUser(authedUser); - options.isMod = authedUser && authedUser.isMod; - options.isAdmin = authedUser && authedUser.isAdmin; - - // Script - options.script = script = modelParser.parseScript(aScript); - options.isOwner = authedUser && authedUser._id == script._authorId; - - // Category - category = {}; - category.slug = type + '/' + installName + '/issues'; - category.name = 'Issues'; - category.description = ''; - category = modelParser.parseCategory(category); - category.categoryPageUrl = script.scriptIssuesPageUrl; - category.categoryPostDiscussionPageUrl = script.scriptOpenIssuePageUrl; - options.category = category; - - discussionLib.findDiscussion(category.slug, topic, function (aDiscussion) { - function preRender() { - // Page metadata - pageMetadata(options, [discussion.topic, 'Discussions'], discussion.topic); - - // commentList - options.commentList = _.map(options.commentList, modelParser.parseComment); - _.map(options.commentList, function (aComment) { - aComment.author = modelParser.parseUser(aComment._authorId); - }); - _.map(options.commentList, modelParser.renderComment); - - // Script - options.issuesCount = pagination.numItems; - - // Pagination - options.paginationRendered = pagination.renderDefault(aReq); - } function render() { - aRes.render('pages/scriptIssuePage', options); + aRes.render('pages/scriptIssueListPage', options); } function asyncComplete() { @@ -256,38 +89,82 @@ exports.view = function (aReq, aRes, aNext) { } // - var discussion = null; - var commentListQuery = null; + var options = {}; + var authedUser = aReq.session.user; + var open = aReq.params.open !== 'closed'; + var script = null; + var category = null; + var discussionListQuery = null; + var listAll = aReq.params.open === 'all'; var pagination = null; var scriptOpenIssueCountQuery = null; + var tasks = []; - if (aErr || !aDiscussion) { + if (aErr || !aScript) { aNext(); return; } - // Discussion - discussion = {}; - discussion = modelParser.parseDiscussion(aDiscussion); - discussion = modelParser.parseIssue(discussion); - options.discussion = discussion; - - options.canClose = authedUser && - (authedUser._id == script._authorId || authedUser._id == discussion._authorId); - options.canOpen = authedUser && authedUser._id == script._authorId; - - // commentListQuery - commentListQuery = Comment.find(); + // Session + options.authedUser = authedUser = modelParser.parseUser(authedUser); + options.isMod = authedUser && authedUser.isMod; + options.isAdmin = authedUser && authedUser.isAdmin; - // commentListQuery: discussion - commentListQuery.find({ _discussionId: discussion._id }); + // + options.openIssuesOnly = open; - // commentListQuery: Defaults - modelQuery.applyCommentListQueryDefaults(commentListQuery, options, aReq); + // Script + options.script = script = modelParser.parseScript(aScript); + options.isOwner = authedUser && authedUser._id == script._authorId; + + // Category + category = {}; + category.slug = type + '/' + installName + '/issues'; + category.name = 'Issues'; + category.description = ''; + category = modelParser.parseCategory(category); + category.categoryPageUrl = script.scriptIssuesPageUrl; + category.categoryPostDiscussionPageUrl = script.scriptOpenIssuePageUrl; + options.category = category; + + options.isScriptIssuesPage = true; + + // Order dir + orderDir(aReq, options, 'topic', 'asc'); + orderDir(aReq, options, 'comments', 'desc'); + orderDir(aReq, options, 'created', 'desc'); + orderDir(aReq, options, 'updated', 'desc'); + + // discussionListQuery + discussionListQuery = Discussion.find(); + + // discussionListQuery: category + discussionListQuery.find({ category: category.slug }); + + // discussionListQuery: Optionally filter discussion list + options.allIssues = !aReq.params.open && !options.isOwner || listAll; + if (!options.allIssues) { + modelQuery.findOrDefaultToNull(discussionListQuery, 'open', options.openIssuesOnly, true); + } - // commentListQuery: Pagination + // Page metadata + pageMetadata(options, + [ + (options.allIssues ? 'All' : (open ? 'Open' : 'Closed')) + ' Issues', + script.name, + (script.isLib ? 'Libraries' : 'Userscripts') + ], + category.description); + + // discussionListQuery: Defaults + modelQuery.applyDiscussionListQueryDefaults(discussionListQuery, options, aReq); + + // discussionListQuery: Pagination pagination = options.pagination; // is set in modelQuery.apply___ListQueryDefaults + // SearchBar + options.searchBarPlaceholder = 'Search Issues'; + //--- Tasks // Show the number of open issues @@ -298,199 +175,324 @@ exports.view = function (aReq, aRes, aNext) { tasks.push(countTask(scriptOpenIssueCountQuery, options, 'issueCount')); // Pagination - tasks.push(pagination.getCountTask(commentListQuery)); + tasks.push(pagination.getCountTask(discussionListQuery, options, 'issueCount')); - // commentListQuery - tasks.push(execQueryTask(commentListQuery, options, 'commentList')); + // discussionListQuery + tasks.push(execQueryTask(discussionListQuery, options, 'discussionList')); + //--- async.parallel(tasks, asyncComplete); }); - }); +}; + +// Show the discussion on an issue +exports.view = function (aReq, aRes, aNext) { + // + var installName = scriptStorage.getScriptBaseName(aReq); + var type = aReq.params.type; + + Script.findOne({ + installName: scriptStorage.caseSensitive(installName + + (type === 'libs' ? '.js' : '.user.js')) + }, function (aErr, aScript) { + // + var options = {}; + var authedUser = aReq.session.user; + var script = null; + var category = null; + var topic = aReq.params.topic; + var tasks = []; + + if (aErr || !aScript) { + aNext(); + return; + } + + // Session + options.authedUser = authedUser = modelParser.parseUser(authedUser); + options.isMod = authedUser && authedUser.isMod; + options.isAdmin = authedUser && authedUser.isAdmin; + + // Script + options.script = script = modelParser.parseScript(aScript); + options.isOwner = authedUser && authedUser._id == script._authorId; + + // Category + category = {}; + category.slug = type + '/' + installName + '/issues'; + category.name = 'Issues'; + category.description = ''; + category = modelParser.parseCategory(category); + category.categoryPageUrl = script.scriptIssuesPageUrl; + category.categoryPostDiscussionPageUrl = script.scriptOpenIssuePageUrl; + options.category = category; + + discussionLib.findDiscussion(category.slug, topic, function (aDiscussion) { + function preRender() { + // Page metadata + pageMetadata(options, [discussion.topic, 'Discussions'], discussion.topic); + + // commentList + options.commentList = _.map(options.commentList, modelParser.parseComment); + _.map(options.commentList, function (aComment) { + aComment.author = modelParser.parseUser(aComment._authorId); + }); + _.map(options.commentList, modelParser.renderComment); + + // Script + options.issuesCount = pagination.numItems; + + // Pagination + options.paginationRendered = pagination.renderDefault(aReq); + } + + function render() { + aRes.render('pages/scriptIssuePage', options); + } + + function asyncComplete() { + preRender(); + render(); + } + + // + var discussion = null; + var commentListQuery = null; + var pagination = null; + var scriptOpenIssueCountQuery = null; + + if (aErr || !aDiscussion) { + aNext(); + return; + } + + // Discussion + discussion = {}; + discussion = modelParser.parseDiscussion(aDiscussion); + discussion = modelParser.parseIssue(discussion); + options.discussion = discussion; + + options.canClose = authedUser && + (authedUser._id == script._authorId || authedUser._id == discussion._authorId); + options.canOpen = authedUser && authedUser._id == script._authorId; + + // commentListQuery + commentListQuery = Comment.find(); + + // commentListQuery: discussion + commentListQuery.find({ _discussionId: discussion._id }); + + // commentListQuery: Defaults + modelQuery.applyCommentListQueryDefaults(commentListQuery, options, aReq); + + // commentListQuery: Pagination + pagination = options.pagination; // is set in modelQuery.apply___ListQueryDefaults + + //--- Tasks + + // Show the number of open issues + scriptOpenIssueCountQuery = Discussion.find({ + category: script.issuesCategorySlug, + open: { $ne: false } + }); + tasks.push(countTask(scriptOpenIssueCountQuery, options, 'issueCount')); + + // Pagination + tasks.push(pagination.getCountTask(commentListQuery)); + + // commentListQuery + tasks.push(execQueryTask(commentListQuery, options, 'commentList')); + + async.parallel(tasks, asyncComplete); + }); + }); }; // Open a new issue exports.open = function (aReq, aRes, aNext) { // - var installName = scriptStorage.getInstallName(aReq); + var installName = scriptStorage.getScriptBaseName(aReq); var type = aReq.params.type; Script.findOne({ installName: scriptStorage.caseSensitive(installName + (type === 'libs' ? '.js' : '.user.js')) - }, function (aErr, aScript) { - function preRender() { - // Page metadata - pageMetadata(options, ['New Issue', script.name]); - } - - function render() { - aRes.render('pages/scriptNewIssuePage', options); - } - - function asyncComplete() { - preRender(); - render(); - } - - // --- - if (aErr || !aScript) { - aNext(); - return; - } - - // - var options = {}; - var authedUser = aReq.session.user; - var script = null; - var category = null; - var topic = aReq.body['discussion-topic']; - var content = aReq.body['comment-content']; - var tasks = []; - - // Session - options.authedUser = authedUser = modelParser.parseUser(authedUser); - options.isMod = authedUser && authedUser.isMod; - options.isAdmin = authedUser && authedUser.isAdmin; - - // Script - options.script = script = modelParser.parseScript(aScript); - options.isOwner = authedUser && authedUser._id == script._authorId; - - // Category - category = {}; - category.slug = type + '/' + installName + '/issues'; - category.name = 'Issues'; - category.description = ''; - category = modelParser.parseCategory(category); - category.categoryPageUrl = script.scriptIssuesPageUrl; - category.categoryPostDiscussionPageUrl = script.scriptOpenIssuePageUrl; - options.category = category; - - if (topic && content) { - if (!topic.trim() || !content.trim()) { - statusCodePage(aReq, aRes, aNext, { - statusCode: 403, - statusMessage: 'You cannot post an empty issue topic to this ' + - (type === 'libs' ? 'library' : 'script') - }); + }, function (aErr, aScript) { + function preRender() { + // Page metadata + pageMetadata(options, ['New Issue', script.name]); + } + + function render() { + aRes.render('pages/scriptNewIssuePage', options); + } + + function asyncComplete() { + preRender(); + render(); + } + + // --- + if (aErr || !aScript) { + aNext(); return; } - // Issue Submission - discussionLib.postTopic(authedUser, category.slug, topic, content, true, - function (aDiscussion) { - if (!aDiscussion) { - aRes.redirect('/' + encodeURI(category) + '/open'); - return; - } + // + var options = {}; + var authedUser = aReq.session.user; + var script = null; + var category = null; + var topic = aReq.body['discussion-topic']; + var content = aReq.body['comment-content']; + var tasks = []; + + // Session + options.authedUser = authedUser = modelParser.parseUser(authedUser); + options.isMod = authedUser && authedUser.isMod; + options.isAdmin = authedUser && authedUser.isAdmin; - aRes.redirect(encodeURI(aDiscussion.path + - (aDiscussion.duplicateId ? '_' + aDiscussion.duplicateId : ''))); + // Script + options.script = script = modelParser.parseScript(aScript); + options.isOwner = authedUser && authedUser._id == script._authorId; + + // Category + category = {}; + category.slug = type + '/' + installName + '/issues'; + category.name = 'Issues'; + category.description = ''; + category = modelParser.parseCategory(category); + category.categoryPageUrl = script.scriptIssuesPageUrl; + category.categoryPostDiscussionPageUrl = script.scriptOpenIssuePageUrl; + options.category = category; + + if (topic && content) { + if (!topic.trim() || !content.trim()) { + statusCodePage(aReq, aRes, aNext, { + statusCode: 403, + statusMessage: 'You cannot post an empty issue topic to this ' + + (type === 'libs' ? 'library' : 'script') + }); + return; } - ); - } else { - // New Issue Page - //--- Tasks - // ... + // Issue Submission + discussionLib.postTopic(authedUser, category.slug, topic, content, true, + function (aDiscussion) { + if (!aDiscussion) { + aRes.redirect('/' + encodeURI(category) + '/open'); + return; + } - //--- - async.parallel(tasks, asyncComplete); - } - }); + aRes.redirect(encodeURI(aDiscussion.path + + (aDiscussion.duplicateId ? '_' + aDiscussion.duplicateId : ''))); + } + ); + } else { + // New Issue Page + + //--- Tasks + // ... + + //--- + async.parallel(tasks, asyncComplete); + } + }); }; // Post route to add a new comment to a discussion on an issue exports.comment = function (aReq, aRes, aNext) { // - var installName = scriptStorage.getInstallName(aReq); + var installName = scriptStorage.getScriptBaseName(aReq); var type = aReq.params.type; - Script.findOne({ installName: scriptStorage.caseSensitive(installName + - (type === 'libs' ? '.js' : '.user.js')) - }, function (aErr, aScript) { - // - var content = aReq.body['comment-content']; - var category = type + '/' + installName + '/issues'; - var topic = aReq.params.topic; - - if (aErr || !aScript) { - aNext(); - return; - } - - if (!content || !content.trim()) { - statusCodePage(aReq, aRes, aNext, { - statusCode: 403, - statusMessage: 'You cannot post an empty comment to this issue' - }); - return; - } - - discussionLib.findDiscussion(category, topic, function (aIssue) { + Script.findOne({ + installName: scriptStorage.caseSensitive(installName + + (type === 'libs' ? '.js' : '.user.js')) + }, function (aErr, aScript) { // - var authedUser = aReq.session.user; + var content = aReq.body['comment-content']; + var category = type + '/' + installName + '/issues'; + var topic = aReq.params.topic; - if (!aIssue) { + if (aErr || !aScript) { aNext(); return; } - discussionLib.postComment(authedUser, aIssue, content, false, - function (aErr, aDiscussion) { - aRes.redirect(encodeURI(aDiscussion.path - + (aDiscussion.duplicateId ? '_' + aDiscussion.duplicateId : ''))); + if (!content || !content.trim()) { + statusCodePage(aReq, aRes, aNext, { + statusCode: 403, + statusMessage: 'You cannot post an empty comment to this issue' }); + return; + } + + discussionLib.findDiscussion(category, topic, function (aIssue) { + // + var authedUser = aReq.session.user; + + if (!aIssue) { + aNext(); + return; + } + + discussionLib.postComment(authedUser, aIssue, content, false, + function (aErr, aDiscussion) { + aRes.redirect(encodeURI(aDiscussion.path + + (aDiscussion.duplicateId ? '_' + aDiscussion.duplicateId : ''))); + }); + }); }); - }); }; // Open or close an issue you are allowed exports.changeStatus = function (aReq, aRes, aNext) { - var installName = scriptStorage.getInstallName(aReq); + var installName = scriptStorage.getScriptBaseName(aReq); var type = aReq.params.type; - Script.findOne({ installName: scriptStorage.caseSensitive(installName + - (type === 'libs' ? '.js' : '.user.js')) - }, function (aErr, aScript) { - var category = type + '/' + installName + '/issues'; - var topic = aReq.params.topic; - var action = aReq.params.action; - var changed = false; - - if (aErr || !aScript) { - aNext(); - return; - } - - discussionLib.findDiscussion(category, topic, function (aIssue) { - var authedUser = aReq.session.user; + Script.findOne({ + installName: scriptStorage.caseSensitive(installName + + (type === 'libs' ? '.js' : '.user.js')) + }, function (aErr, aScript) { + var category = type + '/' + installName + '/issues'; + var topic = aReq.params.topic; + var action = aReq.params.action; + var changed = false; - if (!aIssue) { + if (aErr || !aScript) { aNext(); return; } - // Both the script author and the issue creator can close the issue - // Only the script author can reopen a closed issue - if (action === 'close' && aIssue.open - && (authedUser.name === aIssue.author || authedUser.name === aScript.author)) { - aIssue.open = false; - changed = true; - } else if (action === 'reopen' && !aIssue.open - && authedUser.name === aScript.author) { - aIssue.open = true; - changed = true; - } + discussionLib.findDiscussion(category, topic, function (aIssue) { + var authedUser = aReq.session.user; - if (changed) { - aIssue.save(function (aErr, aDiscussion) { - aRes.redirect(encodeURI(aDiscussion.path - + (aDiscussion.duplicateId ? '_' + aDiscussion.duplicateId : ''))); - }); - } else { - aNext(); - } + if (!aIssue) { + aNext(); + return; + } + + // Both the script author and the issue creator can close the issue + // Only the script author can reopen a closed issue + if (action === 'close' && aIssue.open + && (authedUser.name === aIssue.author || authedUser.name === aScript.author)) { + aIssue.open = false; + changed = true; + } else if (action === 'reopen' && !aIssue.open + && authedUser.name === aScript.author) { + aIssue.open = true; + changed = true; + } + + if (changed) { + aIssue.save(function (aErr, aDiscussion) { + aRes.redirect(encodeURI(aDiscussion.path + + (aDiscussion.duplicateId ? '_' + aDiscussion.duplicateId : ''))); + }); + } else { + aNext(); + } + }); }); - }); }; diff --git a/controllers/script.js b/controllers/script.js index d68c785c6..bdba72747 100644 --- a/controllers/script.js +++ b/controllers/script.js @@ -321,173 +321,173 @@ var setupScriptSidePanel = function (aOptions) { // View a detailed description of a script // This is the most intensive page to render on the site exports.view = function (aReq, aRes, aNext) { - var installNameSlug = scriptStorage.getInstallName(aReq); + var installName = scriptStorage.getScriptBaseName(aReq); var isLib = aReq.params.isLib; Script.findOne({ - installName: scriptStorage - .caseSensitive(installNameSlug + (isLib ? '.js' : '.user.js')) - }, function (aErr, aScriptData) { - function preRender() { - if (script.groups) { - pageMetadata(options, ['About', script.name, (script.isLib ? 'Libraries' : 'Userscripts')], - script.description, _.pluck(script.groups, 'name')); + installName: scriptStorage.caseSensitive(installName + + (isLib ? '.js' : '.user.js')) + }, function (aErr, aScriptData) { + function preRender() { + if (script.groups) { + pageMetadata(options, ['About', script.name, (script.isLib ? 'Libraries' : 'Userscripts')], + script.description, _.pluck(script.groups, 'name')); + } } - } - function render() { - aRes.render('pages/scriptPage', options); - } + function render() { + aRes.render('pages/scriptPage', options); + } - function asyncComplete() { + function asyncComplete() { - async.parallel([ - function (aCallback) { - if (!options.isAdmin) { // NOTE: Watchpoint - aCallback(); - return; + async.parallel([ + function (aCallback) { + if (!options.isAdmin) { // NOTE: Watchpoint + aCallback(); + return; + } + getFlaggedListForContent('Script', options, aCallback); } - getFlaggedListForContent('Script', options, aCallback); - } - ], function (aErr) { - preRender(); - render(); - }); + ], function (aErr) { + preRender(); + render(); + }); - } + } - // - var options = {}; - var authedUser = aReq.session.user; - var script = null; - var tasks = []; + // + var options = {}; + var authedUser = aReq.session.user; + var script = null; + var tasks = []; - //--- - if (aErr || !aScriptData) { - aNext(); - return; - } + //--- + if (aErr || !aScriptData) { + aNext(); + return; + } - // Session - options.authedUser = authedUser = modelParser.parseUser(authedUser); - options.isMod = authedUser && authedUser.isMod; - options.isAdmin = authedUser && authedUser.isAdmin; + // Session + options.authedUser = authedUser = modelParser.parseUser(authedUser); + options.isMod = authedUser && authedUser.isMod; + options.isAdmin = authedUser && authedUser.isAdmin; - // Script - options.script = script = modelParser.parseScript(aScriptData); - options.isOwner = authedUser && authedUser._id == script._authorId; - modelParser.renderScript(script); - script.installNameSlug = installNameSlug; - script.scriptPermalinkInstallPageUrl = 'https://' + aReq.get('host') + - script.scriptInstallPageUrl; + // Script + options.script = script = modelParser.parseScript(aScriptData); + options.isOwner = authedUser && authedUser._id == script._authorId; + modelParser.renderScript(script); + script.installNameSlug = installName; + script.scriptPermalinkInstallPageUrl = 'https://' + aReq.get('host') + + script.scriptInstallPageUrl; - // Page metadata - pageMetadata(options, ['About', script.name, (script.isLib ? 'Libraries' : 'Userscripts')], - script.description); - options.isScriptPage = true; + // Page metadata + pageMetadata(options, ['About', script.name, (script.isLib ? 'Libraries' : 'Userscripts')], + script.description); + options.isScriptPage = true; - // SearchBar - options.searchBarPlaceholder = modelQuery.scriptListQueryDefaults.searchBarPlaceholder; - options.searchBarFormAction = modelQuery.scriptListQueryDefaults.searchBarFormAction; + // SearchBar + options.searchBarPlaceholder = modelQuery.scriptListQueryDefaults.searchBarPlaceholder; + options.searchBarFormAction = modelQuery.scriptListQueryDefaults.searchBarFormAction; - // SideBar - setupScriptSidePanel(options); + // SideBar + setupScriptSidePanel(options); - //--- Tasks - tasks = tasks.concat(getScriptPageTasks(options)); + //--- Tasks + tasks = tasks.concat(getScriptPageTasks(options)); - //--- - async.parallel(tasks, asyncComplete); - }); + //--- + async.parallel(tasks, asyncComplete); + }); }; // route to edit a script exports.edit = function (aReq, aRes, aNext) { // - var installNameSlug = scriptStorage.getInstallName(aReq); + var installName = scriptStorage.getScriptBaseName(aReq); var isLib = aReq.params.isLib; //--- Script.findOne({ - installName: scriptStorage - .caseSensitive(installNameSlug + (isLib ? '.js' : '.user.js')) - }, function (aErr, aScriptData) { - function preRender() { - var groupNameList = (options.script.groups || []).map(function (aGroup) { - return aGroup.name; - }); - options.groupNameListJSON = JSON.stringify(groupNameList); - } + installName: scriptStorage.caseSensitive(installName + + (isLib ? '.js' : '.user.js')) + }, function (aErr, aScriptData) { + function preRender() { + var groupNameList = (options.script.groups || []).map(function (aGroup) { + return aGroup.name; + }); + options.groupNameListJSON = JSON.stringify(groupNameList); + } - function render() { - aRes.render('pages/scriptEditMetadataPage', options); - } + function render() { + aRes.render('pages/scriptEditMetadataPage', options); + } - function asyncComplete() { - preRender(); - render(); - } + function asyncComplete() { + preRender(); + render(); + } - // - var options = {}; - var authedUser = aReq.session.user; - var script = null; - var scriptGroups = null; - var tasks = []; + // + var options = {}; + var authedUser = aReq.session.user; + var script = null; + var scriptGroups = null; + var tasks = []; - // --- - if (aErr || !aScriptData) { - aNext(); - return; - } + // --- + if (aErr || !aScriptData) { + aNext(); + return; + } - // Session - options.authedUser = authedUser = modelParser.parseUser(authedUser); - options.isMod = authedUser && authedUser.isMod; - options.isAdmin = authedUser && authedUser.isAdmin; + // Session + options.authedUser = authedUser = modelParser.parseUser(authedUser); + options.isMod = authedUser && authedUser.isMod; + options.isAdmin = authedUser && authedUser.isAdmin; - // Page metadata - options.script = script = modelParser.parseScript(aScriptData); - options.isOwner = authedUser && authedUser._id == script._authorId; - pageMetadata(options, ['Edit', script.name, (script.isLib ? 'Libraries' : 'Userscripts')], - script.name); + // Page metadata + options.script = script = modelParser.parseScript(aScriptData); + options.isOwner = authedUser && authedUser._id == script._authorId; + pageMetadata(options, ['Edit', script.name, (script.isLib ? 'Libraries' : 'Userscripts')], + script.name); - // If authed user is not the script author. - if (!options.isOwner) { - aNext(); - return; - } + // If authed user is not the script author. + if (!options.isOwner) { + aNext(); + return; + } - // SearchBar - options.searchBarPlaceholder = modelQuery.scriptListQueryDefaults.searchBarPlaceholder; - options.searchBarFormAction = modelQuery.scriptListQueryDefaults.searchBarFormAction; + // SearchBar + options.searchBarPlaceholder = modelQuery.scriptListQueryDefaults.searchBarPlaceholder; + options.searchBarFormAction = modelQuery.scriptListQueryDefaults.searchBarFormAction; - if (aReq.body.remove) { - // POST - scriptStorage.deleteScript(aScriptData.installName, function () { - aRes.redirect(authedUser.userScriptListPageUrl); - }); - } else if (typeof aReq.body.about !== 'undefined') { - // POST - aScriptData.about = aReq.body.about; - scriptGroups = (aReq.body.groups || ''); - scriptGroups = scriptGroups.split(/,/); - addScriptToGroups(aScriptData, scriptGroups, function () { - aRes.redirect(script.scriptPageUrl); - }); - } else { - // GET + if (aReq.body.remove) { + // POST + scriptStorage.deleteScript(aScriptData.installName, function () { + aRes.redirect(authedUser.userScriptListPageUrl); + }); + } else if (typeof aReq.body.about !== 'undefined') { + // POST + aScriptData.about = aReq.body.about; + scriptGroups = (aReq.body.groups || ''); + scriptGroups = scriptGroups.split(/,/); + addScriptToGroups(aScriptData, scriptGroups, function () { + aRes.redirect(script.scriptPageUrl); + }); + } else { + // GET - options.script = script; + options.script = script; - tasks = tasks.concat(getScriptPageTasks(options)); + tasks = tasks.concat(getScriptPageTasks(options)); - // Groups - options.canCreateGroup = (!script._groupId).toString(); + // Groups + options.canCreateGroup = (!script._groupId).toString(); - async.parallel(tasks, asyncComplete); - } - }); + async.parallel(tasks, asyncComplete); + } + }); }; // Script voting @@ -498,8 +498,7 @@ exports.vote = function (aReq, aRes, aNext) { var unvote = false; var isLib = aReq.params.isLib; - var installName = scriptStorage.getInstallName(aReq) - + (isLib ? '.js' : '.user.js'); + var installName = scriptStorage.getScriptBaseName(aReq); // --- if (url.length > 5) { @@ -520,8 +519,10 @@ exports.vote = function (aReq, aRes, aNext) { return; } - Script.findOne({ installName: scriptStorage.caseSensitive(installName) }, - function (aErr, aScript) { + Script.findOne({ + installName: scriptStorage.caseSensitive(installName + + (isLib ? '.js' : '.user.js')) + }, function (aErr, aScript) { // var authedUser = aReq.session.user; @@ -595,6 +596,5 @@ exports.vote = function (aReq, aRes, aNext) { aVoteModel.save(saveScript); } ); - } - ); + }); }; diff --git a/controllers/scriptStorage.js b/controllers/scriptStorage.js index 68fb13e09..64de8265a 100644 --- a/controllers/scriptStorage.js +++ b/controllers/scriptStorage.js @@ -65,10 +65,36 @@ if (isPro) { }); } -function getInstallName(aReq) { - return aReq.params.username + '/' + aReq.params.scriptname; +function getScriptBaseName(aReq, aOptions) { + // + var base = null; + + var username = aReq.params.username; + var scriptname = aReq.params.scriptname; + + var rKnownExtensions = /(?:\.(?:user|meta))?\.js(?:on)?$/; + + if (!aOptions) { + aOptions = {}; + } + + if (aOptions.hasExtension) { + scriptname = scriptname.replace(rKnownExtensions, ''); + } + + switch (aOptions.encoding) { + case 'uri': + base = encodeURIComponent(username) + '/' + encodeURIComponent(scriptname); + break; + + default: + base = username + '/' + scriptname; + } + + + return base; } -exports.getInstallName = getInstallName; +exports.getScriptBaseName = getScriptBaseName; function caseInsensitive(aInstallName) { return new RegExp('^' + aInstallName.replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1") + '$', 'i'); @@ -134,9 +160,13 @@ function caseSensitive(aInstallName, aMoreThanInstallName) { exports.caseSensitive = caseSensitive; exports.getSource = function (aReq, aCallback) { - var installName = getInstallName(aReq); - Script.findOne({ installName: caseSensitive(installName) }, - function (aErr, aScript) { + var installName = getScriptBaseName(aReq, { hasExtension: true }); + var isLib = aReq.params.isLib; + + Script.findOne({ + installName: caseSensitive(installName + + (isLib ? '.js' : '.user.js')) + }, function (aErr, aScript) { var s3Object = null; var s3 = new AWS.S3(); @@ -144,10 +174,11 @@ exports.getSource = function (aReq, aCallback) { if (!aScript) { aCallback(null); + console.warn('no script found' ); return; } - s3Object = s3.getObject({ Bucket: bucketName, Key: installName }).createReadStream(). + s3Object = s3.getObject({ Bucket: bucketName, Key: installName + (isLib ? '.js' : '.user.js') }).createReadStream(). on('error', function () { if (isPro) { console.error('S3 Key Not Found ' + installName); @@ -163,6 +194,11 @@ exports.getSource = function (aReq, aCallback) { }; exports.sendScript = function (aReq, aRes, aNext) { + + if (aReq.params.type === 'libs') { + aReq.params.isLib = true; + } + var accept = aReq.headers.accept; if (0 !== aReq.url.indexOf('/libs/') && accept === 'text/x-userscript-meta') { @@ -204,9 +240,9 @@ exports.sendScript = function (aReq, aRes, aNext) { // Send user script metadata block exports.sendMeta = function (aReq, aRes, aNext) { - var installName = getInstallName(aReq).replace(/\.meta\.(?:js|json)$/, '.user.js'); + var installName = getScriptBaseName(aReq, { hasExtension: true }); - Script.findOne({ installName: caseSensitive(installName) }, + Script.findOne({ installName: caseSensitive(installName + '.user.js') }, function (aErr, aScript) { var meta = null; var whitespace = '\u0020\u0020\u0020\u0020'; diff --git a/controllers/user.js b/controllers/user.js index 41e14a243..74bd3cb6b 100644 --- a/controllers/user.js +++ b/controllers/user.js @@ -1391,7 +1391,7 @@ exports.uploadScript = function (aReq, aRes, aNext) { } aRes.redirect('/libs/' + encodeURI(aScript.installName - .replace(rJS, ''))); + .replace(rJS, ''))); // TODO: Split handling }); } else { aRes.redirect(failUrl); @@ -1408,7 +1408,7 @@ exports.uploadScript = function (aReq, aRes, aNext) { } aRes.redirect('/scripts/' + encodeURI(aScript.installName - .replace(rUserJS, ''))); + .replace(rUserJS, ''))); // TODO: Split handling }); }); } @@ -1450,7 +1450,7 @@ exports.submitSource = function (aReq, aRes, aNext) { scriptStorage.storeScript(aUser, aMeta, aSource, function (aScript) { var redirectUrl = encodeURI(aScript ? (aScript.isLib ? '/libs/' + aScript.installName.replace(rJS, '') : '/scripts/' - + aScript.installName.replace(rUserJS, '')) : decodeURI(aReq.body.url)); + + aScript.installName.replace(rUserJS, '')) : decodeURI(aReq.body.url)); // TODO: Split handling if (!aScript || !aReq.body.original) { aRes.redirect(redirectUrl); @@ -1469,7 +1469,7 @@ exports.submitSource = function (aReq, aRes, aNext) { fork = aOrigScript.fork || []; fork.unshift({ author: aOrigScript.author, url: aOrigScript - .installName.replace(aOrigScript.isLib ? rJS : rUserJS, '') + .installName.replace(aOrigScript.isLib ? rJS : rUserJS, '') // TODO: Split handling }); aScript.fork = fork; @@ -1598,7 +1598,7 @@ exports.editScript = function (aReq, aRes, aNext) { var options = {}; var authedUser = aReq.session.user; var isNew = aReq.params.isNew; - var installNameSlug = null; + var installName = null; var isLib = aReq.params.isLib; var tasks = []; @@ -1619,47 +1619,48 @@ exports.editScript = function (aReq, aRes, aNext) { }); if (!isNew) { - installNameSlug = scriptStorage.getInstallName(aReq); + installName = scriptStorage.getScriptBaseName(aReq); Script.findOne({ - installName: scriptStorage - .caseSensitive(installNameSlug + (isLib ? '.js' : '.user.js')) - }, function (aErr, aScriptData) { - // - var script = null; - var scriptOpenIssueCountQuery = null; - - //--- - if (aErr || !aScriptData) { - aNext(); - return; - } + installName: scriptStorage.caseSensitive(installName + + (isLib ? '.js' : '.user.js')) + }, function (aErr, aScriptData) { + // + var script = null; + var scriptOpenIssueCountQuery = null; + + //--- + if (aErr || !aScriptData) { + aNext(); + return; + } - // Script - options.script = script = modelParser.parseScript(aScriptData); - options.isOwner = authedUser && authedUser._id == script._authorId; - modelParser.renderScript(script); - script.installNameSlug = installNameSlug; - script.scriptPermalinkInstallPageUrl = 'https://' + aReq.get('host') + - script.scriptInstallPageUrl; - script.scriptRawPageUrl = '/src/' + (isLib ? 'libs' : 'scripts') + '/' - + installNameSlug + (isLib ? '.js' : '.user.js#'); + // Script + options.script = script = modelParser.parseScript(aScriptData); + options.isOwner = authedUser && authedUser._id == script._authorId; + modelParser.renderScript(script); + script.installNameSlug = installName; + script.scriptPermalinkInstallPageUrl = 'https://' + aReq.get('host') + + script.scriptInstallPageUrl; + script.scriptRawPageUrl = '/src/' + (isLib ? 'libs' : 'scripts') + '/' + + scriptStorage.getScriptBaseName(aReq, { encoding: 'uri' }) + + (isLib ? '.js#' : '.user.js#'); - // Page metadata - pageMetadata(options); + // Page metadata + pageMetadata(options); - options.isScriptViewSourcePage = true; + options.isScriptViewSourcePage = true; - //--- Tasks + //--- Tasks - // Show the number of open issues - scriptOpenIssueCountQuery = Discussion.find({ category: scriptStorage - .caseSensitive(script.issuesCategorySlug, true), open: {$ne: false} }); - tasks.push(countTask(scriptOpenIssueCountQuery, options, 'issueCount')); + // Show the number of open issues + scriptOpenIssueCountQuery = Discussion.find({ category: scriptStorage + .caseSensitive(script.issuesCategorySlug, true), open: {$ne: false} }); + tasks.push(countTask(scriptOpenIssueCountQuery, options, 'issueCount')); - //--- - async.parallel(tasks, asyncComplete); - }); + //--- + async.parallel(tasks, asyncComplete); + }); } else { //--- async.parallel(tasks, asyncComplete); diff --git a/libs/modelParser.js b/libs/modelParser.js index 98c222910..49742f71d 100644 --- a/libs/modelParser.js +++ b/libs/modelParser.js @@ -85,7 +85,7 @@ var getScriptPageUrl = function (aScript) { var isLib = aScript.isLib || false; var scriptPath = aScript.installName .replace(isLib ? /\.js$/ : /\.user\.js$/, ''); - return (isLib ? '/libs/' : '/scripts/') + encodeURI(scriptPath); + return (isLib ? '/libs/' : '/scripts/') + encodeURI(scriptPath); // TODO: Split handling }; var getScriptViewSourcePageUrl = function (aScript) { @@ -102,7 +102,7 @@ var getScriptEditSourcePageUrl = function (aScript) { var getScriptInstallPageUrl = function (aScript) { var isLib = aScript.isLib || false; - return (isLib ? '/src/libs/' : '/install/') + aScript.installName; + return (isLib ? '/src/libs/' : '/install/') + encodeURI(aScript.installName); // TODO: Split handling }; // @@ -190,7 +190,7 @@ var parseScript = function (aScriptData) { // Urls: Slugs script.authorSlug = script.author.name; script.nameSlug = cleanFilename(script.name); - script.installNameSlug = script.author.slug + '/' + script.nameSlug; + script.installNameSlug = encodeURIComponent(script.author.slug) + '/' + encodeURIComponent(script.nameSlug); // Urls: Public script.scriptPageUrl = getScriptPageUrl(script); @@ -199,8 +199,8 @@ var parseScript = function (aScriptData) { // Urls: Issues var slug = (script.isLib ? 'libs' : 'scripts'); - slug += '/' + script.author.slug; - slug += '/' + script.nameSlug; + slug += '/' + encodeURIComponent(script.author.slug); + slug += '/' + encodeURIComponent(script.nameSlug); script.issuesCategorySlug = slug + '/issues'; script.scriptIssuesPageUrl = '/' + script.issuesCategorySlug; script.scriptOpenIssuePageUrl = '/' + slug + '/issue/new';