diff --git a/controllers/admin.js b/controllers/admin.js index 9317a471e..f7f0dc850 100644 --- a/controllers/admin.js +++ b/controllers/admin.js @@ -497,6 +497,6 @@ exports.authAsUser = function (aReq, aRes, aNext) { aReq.session.user = user; - aRes.redirect(encodeURI(user.userPageUrl)); // NOTE: Watchpoint + aRes.redirect(user.userPageUrl); // NOTE: Watchpoint }); }; diff --git a/controllers/discussion.js b/controllers/discussion.js index b5ada4f8b..50bf82d9f 100644 --- a/controllers/discussion.js +++ b/controllers/discussion.js @@ -24,6 +24,8 @@ var modelParser = require('../libs/modelParser'); var modelQuery = require('../libs/modelQuery'); var cleanFilename = require('../libs/helpers').cleanFilename; +var encode = require('../libs/helpers').encode; + var execQueryTask = require('../libs/tasks').execQueryTask; var statusCodePage = require('../libs/templateHelpers').statusCodePage; var pageMetadata = require('../libs/templateHelpers').pageMetadata; @@ -243,9 +245,12 @@ exports.list = function (aReq, aRes, aNext) { // Locate a discussion and deal with topic url collisions function findDiscussion(aCategory, aTopicUrl, aCallback) { + // To prevent collisions we add an incrementing id to the topic url var topic = /(.+?)(?:_(\d+))?$/.exec(aTopicUrl); - var query = { path: '/' + aCategory + '/' + topic[1] }; + var query = { path: '/' + aCategory.split('/').map(function (aStr) { + return encode(aStr); + }).join('/') + '/' + encode(topic[1]) }; // We only need to look for the proper duplicate if there is one if (topic[2]) { @@ -433,8 +438,10 @@ exports.postComment = postComment; // Does all the work of submitting a new topic and // resolving topic url collisions function postTopic(aUser, aCategory, aTopic, aContent, aIssue, aCallback) { - var urlTopic = cleanFilename(aTopic, '').replace(/_\d+$/, ''); - var path = '/' + aCategory + '/' + urlTopic; + var urlTopic = encode(cleanFilename(aTopic, '').replace(/_\d+$/, '')); + var path = '/' + aCategory.split('/').map(function (aStr) { + return encode(aStr); + }).join('/') + '/' + urlTopic; var params = { sort: {} }; params.sort.duplicateId = -1; @@ -525,8 +532,8 @@ exports.createTopic = function (aReq, aRes, aNext) { return; } - aRes.redirect(encodeURI(aDiscussion.path - + (aDiscussion.duplicateId ? '_' + aDiscussion.duplicateId : ''))); + aRes.redirect(aDiscussion.path + + (aDiscussion.duplicateId ? '_' + aDiscussion.duplicateId : '')); // NOTE: Watchpoint }); }; @@ -560,8 +567,8 @@ exports.createComment = function (aReq, aRes, aNext) { } postComment(authedUser, aDiscussion, content, false, function (aErr, aDiscussion) { - aRes.redirect(encodeURI(aDiscussion.path - + (aDiscussion.duplicateId ? '_' + aDiscussion.duplicateId : ''))); + aRes.redirect(aDiscussion.path + + (aDiscussion.duplicateId ? '_' + aDiscussion.duplicateId : '')); // NOTE: Watchpoint }); }); }; diff --git a/controllers/flag.js b/controllers/flag.js index 44a35ce73..d51b1ef9c 100644 --- a/controllers/flag.js +++ b/controllers/flag.js @@ -23,6 +23,7 @@ var scriptStorage = require('./scriptStorage'); var flagLib = require('../libs/flag'); var statusCodePage = require('../libs/templateHelpers').statusCodePage; +var encode = require('../libs/helpers').encode; //--- Configuration inclusions @@ -103,7 +104,7 @@ exports.flag = function (aReq, aRes, aNext) { fn(Script, aScript, authedUser, reason, function (aFlagged) { aRes.redirect((isLib ? '/libs/' : '/scripts/') + scriptStorage.getInstallNameBase( - aReq, { encoding: 'uri' })); + aReq, { encoding: 'url' })); // NOTE: Watchpoint }); }); @@ -121,7 +122,7 @@ exports.flag = function (aReq, aRes, aNext) { } fn(User, aUser, authedUser, reason, function (aFlagged) { - aRes.redirect('/users/' + encodeURIComponent(username)); + aRes.redirect('/users/' + encode(username)); // NOTE: Watchpoint }); }); diff --git a/controllers/issue.js b/controllers/issue.js index 94eb41e67..40c23cf93 100644 --- a/controllers/issue.js +++ b/controllers/issue.js @@ -380,12 +380,12 @@ exports.open = function (aReq, aRes, aNext) { discussionLib.postTopic(authedUser, category.slug, topic, content, true, function (aDiscussion) { if (!aDiscussion) { - aRes.redirect('/' + encodeURI(category) + '/open'); + aRes.redirect('/' + category.slugUrl + '/open'); // NOTE: Watchpoint return; } - aRes.redirect(encodeURI(aDiscussion.path + - (aDiscussion.duplicateId ? '_' + aDiscussion.duplicateId : ''))); + aRes.redirect(aDiscussion.path + + (aDiscussion.duplicateId ? '_' + aDiscussion.duplicateId : '')); // NOTE: Watchpoint } ); } else { @@ -439,8 +439,8 @@ exports.comment = function (aReq, aRes, aNext) { discussionLib.postComment(authedUser, aIssue, content, false, function (aErr, aDiscussion) { - aRes.redirect(encodeURI(aDiscussion.path - + (aDiscussion.duplicateId ? '_' + aDiscussion.duplicateId : ''))); + aRes.redirect(aDiscussion.path + + (aDiscussion.duplicateId ? '_' + aDiscussion.duplicateId : '')); // NOTE: Watchpoint }); }); }); @@ -487,8 +487,8 @@ exports.changeStatus = function (aReq, aRes, aNext) { if (changed) { aIssue.save(function (aErr, aDiscussion) { - aRes.redirect(encodeURI(aDiscussion.path - + (aDiscussion.duplicateId ? '_' + aDiscussion.duplicateId : ''))); + aRes.redirect(aDiscussion.path + + (aDiscussion.duplicateId ? '_' + aDiscussion.duplicateId : '')); // NOTE: Watchpoint }); } else { aNext(); diff --git a/controllers/remove.js b/controllers/remove.js index 7f6ba6d69..8c2b39e9c 100644 --- a/controllers/remove.js +++ b/controllers/remove.js @@ -20,6 +20,8 @@ var scriptStorage = require('./scriptStorage'); //--- Library inclusions var removeLib = require('../libs/remove'); +var encode = require('../libs/helpers').encode; + var destroySessions = require('../libs/modifySessions').destroy; var statusCodePage = require('../libs/templateHelpers').statusCodePage; @@ -92,7 +94,7 @@ exports.rm = function (aReq, aRes, aNext) { aNext(); return; } - aRes.redirect('/users/' + encodeURIComponent(username) + '/scripts'); + aRes.redirect('/users/' + encode(username) + '/scripts'); // NOTE: Watchpoint }); }); break; diff --git a/controllers/scriptStorage.js b/controllers/scriptStorage.js index 9d7372dad..0b0e15aa1 100644 --- a/controllers/scriptStorage.js +++ b/controllers/scriptStorage.js @@ -26,6 +26,7 @@ var RepoManager = require('../libs/repoManager'); var cleanFilename = require('../libs/helpers').cleanFilename; var findDeadorAlive = require('../libs/remove').findDeadorAlive; +var encode = require('../libs/helpers').encode; //--- Configuration inclusions var userRoles = require('../models/userRoles.json'); @@ -84,15 +85,14 @@ function getInstallNameBase(aReq, aOptions) { } switch (aOptions.encoding) { - case 'uri': - base = encodeURIComponent(username) + '/' + encodeURIComponent(scriptname); + case 'url': + base = encode(username) + '/' + encode(scriptname); break; default: base = username + '/' + scriptname; } - return base; } exports.getInstallNameBase = getInstallNameBase; diff --git a/controllers/user.js b/controllers/user.js index d40b75eba..84c3078c1 100644 --- a/controllers/user.js +++ b/controllers/user.js @@ -1169,6 +1169,7 @@ exports.userGitHubImportScriptPage = function (aReq, aRes, aNext) { } else if (options.javascriptBlob.isJSLibrary) { if (!hasMissingExcludeAll(aBlobUtf8)) { scriptName = options.javascriptBlob.path.name; + jsLibraryMeta = scriptName; scriptStorage.storeScript(authedUser, jsLibraryMeta, aBlobUtf8, onScriptStored); } else { @@ -1194,7 +1195,7 @@ exports.userGitHubImportScriptPage = function (aReq, aRes, aNext) { script = modelParser.parseScript(options.script); - aRes.redirect(script.scriptPageUrl); + aRes.redirect(script.scriptPageUrl); // NOTE: Watchpoint }); }; @@ -1379,8 +1380,6 @@ exports.uploadScript = function (aReq, aRes, aNext) { User.findOne({ _id: authedUser._id }, function (aErr, aUser) { var scriptName = aFields.script_name; var bufferConcat = Buffer.concat(bufs); - var rJS = /\.js$/; - var rUserJS = /\.user\.js$/; if (isLib) { if (!hasMissingExcludeAll(bufferConcat)) { @@ -1391,8 +1390,12 @@ exports.uploadScript = function (aReq, aRes, aNext) { return; } - aRes.redirect('/libs/' + encodeURI(aScript.installName - .replace(rJS, ''))); // TODO: Split handling + aRes.redirect( + '/libs/' + + helpers.encode(helpers.cleanFilename(aScript.author)) + + '/' + + helpers.encode(helpers.cleanFilename(aScript.name)) + ); }); } else { aRes.redirect(failUrl); @@ -1408,8 +1411,12 @@ exports.uploadScript = function (aReq, aRes, aNext) { return; } - aRes.redirect('/scripts/' + encodeURI(aScript.installName - .replace(rUserJS, ''))); // TODO: Split handling + aRes.redirect( + '/scripts/' + + helpers.encode(helpers.cleanFilename(aScript.author)) + + '/' + + helpers.encode(helpers.cleanFilename(aScript.name)) + ); }); }); } @@ -1444,14 +1451,14 @@ exports.submitSource = function (aReq, aRes, aNext) { var url = null; function storeScript(aMeta, aSource) { - var rUserJS = /\.user\.js$/; - var rJS = /\.js$/; - User.findOne({ _id: authedUser._id }, function (aErr, aUser) { 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)); // TODO: Split handling + var redirectUrl = aScript + ? ((aScript.isLib ? '/libs/' : '/scripts/') + + helpers.encode(helpers.cleanFilename(aScript.author)) + + '/' + + helpers.encode(helpers.cleanFilename(aScript.name))) + : aReq.body.url; if (!aScript || !aReq.body.original) { aRes.redirect(redirectUrl); @@ -1462,6 +1469,10 @@ exports.submitSource = function (aReq, aRes, aNext) { function (aErr, aOrigScript) { var fork = null; + var origInstallNameSlugUrl = helpers.encode(helpers.cleanFilename(aOrigScript.author)) + + '/' + + helpers.encode(helpers.cleanFilename(aOrigScript.name)); + if (aErr || !aOrigScript) { aRes.redirect(redirectUrl); return; @@ -1469,8 +1480,9 @@ exports.submitSource = function (aReq, aRes, aNext) { fork = aOrigScript.fork || []; fork.unshift({ - author: aOrigScript.author, url: aOrigScript - .installName.replace(aOrigScript.isLib ? rJS : rUserJS, '') // TODO: Split handling + author: aOrigScript.author, + url: origInstallNameSlugUrl, + utf: aOrigScript.author + '/' + aOrigScript.name }); aScript.fork = fork; @@ -1646,7 +1658,7 @@ exports.editScript = function (aReq, aRes, aNext) { script.scriptPermalinkInstallPageXUrl = 'https://' + aReq.get('host') + script.scriptInstallPageXUrl; script.scriptRawPageUrl = '/src/' + (isLib ? 'libs' : 'scripts') + '/' - + scriptStorage.getInstallNameBase(aReq, { encoding: 'uri' }) + + + scriptStorage.getInstallNameBase(aReq, { encoding: 'url' }) + (isLib ? '.js#' : '.user.js#'); // Page metadata diff --git a/libs/helpers.js b/libs/helpers.js index a867adbc4..19e75e1b1 100644 --- a/libs/helpers.js +++ b/libs/helpers.js @@ -96,6 +96,13 @@ exports.cleanFilename = function (aFilename, aDefaultName) { return cleanName || aDefaultName; }; +// Default encoding like GitHubs +// Future code point for the smart encoder +// pending *express* redirect issue resolution +exports.encode = function (aStr) { + return encodeURIComponent(aStr); +} + exports.limitRange = function (aMin, aX, aMax, aDefault) { var x = Math.max(Math.min(aX, aMax), aMin); diff --git a/libs/modelParser.js b/libs/modelParser.js index 252c5b219..332639ec7 100644 --- a/libs/modelParser.js +++ b/libs/modelParser.js @@ -23,6 +23,7 @@ var findMeta = require('../controllers/scriptStorage').findMeta; var renderMd = require('../libs/markdown').renderMd; var getRating = require('../libs/collectiveRating').getRating; var cleanFilename = require('../libs/helpers').cleanFilename; +var encode = require('../libs/helpers').encode; //--- Configuration inclusions var htmlWhitelistLink = require('./htmlWhitelistLink.json'); @@ -98,7 +99,8 @@ var parseDateProperty = function (aObj, aKey) { } }; -// Parse persisted model data and return a new object with additional generated fields used in view templates. +// Parse persisted model data and return a new object +// with additional generated fields used in view templates. /** * Script @@ -107,9 +109,11 @@ var parseDateProperty = function (aObj, aKey) { // Urls var getScriptPageUrl = function (aScriptData) { var isLib = aScriptData.isLib || false; - var scriptPath = aScriptData.installName - .replace(isLib ? /\.js$/ : /\.user\.js$/, ''); - return (isLib ? '/libs/' : '/scripts/') + encodeURI(scriptPath); // TODO: #819 Split handling + + return (isLib ? '/libs/' : '/scripts/') + + aScriptData.authorSlugUrl + + '/' + + aScriptData.nameSlugUrl }; var getScriptViewSourcePageUrl = function (aScriptData) { @@ -126,7 +130,12 @@ var getScriptEditSourcePageUrl = function (aScriptData) { var getScriptInstallPageUrl = function (aScriptData) { var isLib = aScriptData.isLib || false; - return (isLib ? '/src/libs/' : '/install/') + encodeURI(aScriptData.installName); // TODO: #819 Split handling + + return (isLib ? '/src/libs/' : '/install/') + + aScriptData.authorSlugUrl + + '/' + + aScriptData.nameSlugUrl + + (isLib ? '.js' : '.user.js') }; // @@ -163,7 +172,9 @@ var parseScript = function (aScript) { // Author // Extend with rewrite the User model `name` key to be an Object instead of String if (_.isString(script.author)) { - script.author = parseUser({ name: script.author }); + script.author = parseUser({ + name: script.author + }); } // Description default @@ -236,11 +247,15 @@ var parseScript = function (aScript) { script.votesPercent = votesPercent; script.flagsPercent = flagsPercent; - // Urls: Slugs + // DB: Slugs script.authorSlug = script.author.name; script.nameSlug = cleanFilename(script.name); - script.installNameSlug = encodeURIComponent(script.author.slug) + '/' + - encodeURIComponent(script.nameSlug); + script.installNameSlug = script.author.slug + '/' + script.nameSlug; // NOTE: Redundant as `installName` is already "slugged" + + // Urls: Slugs + script.authorSlugUrl = encode(script.authorSlug); + script.nameSlugUrl = encode(script.nameSlug); + script.installNameSlugUrl = encode(script.authorSlug) + '/' + encode(script.nameSlug); // Urls: Public script.scriptPageUrl = getScriptPageUrl(script); @@ -250,8 +265,8 @@ var parseScript = function (aScript) { // Urls: Issues slug = (script.isLib ? 'libs' : 'scripts'); - slug += '/' + encodeURIComponent(script.author.slug); - slug += '/' + encodeURIComponent(script.nameSlug); + slug += '/' + script.authorSlugUrl; + slug += '/' + script.nameSlugUrl; script.issuesCategorySlug = slug + '/issues'; script.scriptIssuesPageUrl = '/' + script.issuesCategorySlug; script.scriptOpenIssuePageUrl = '/' + slug + '/issue/new'; @@ -262,9 +277,9 @@ var parseScript = function (aScript) { // Urls: Moderation script.scriptRemovePageUrl = '/remove' + (script.isLib ? '/libs/' : '/scripts/') + - script.installNameSlug; + script.installNameSlugUrl; script.scriptFlagPageUrl = '/flag' + (script.isLib ? '/libs/' : '/scripts/') + - script.installNameSlug; + script.installNameSlugUrl; // Dates parseDateProperty(script, 'updated'); @@ -308,8 +323,10 @@ var parseUser = function (aUser) { // user.slug = user.name; + user.slugUrl = encode(user.slug); + // Urls: Public - user.userPageUrl = '/users/' + user.name; + user.userPageUrl = '/users/' + user.slugUrl; user.userCommentListPageUrl = user.userPageUrl + '/comments'; user.userScriptListPageUrl = user.userPageUrl + '/scripts'; user.userGitHubRepoListPageUrl = user.userPageUrl + '/github/repos'; @@ -317,8 +334,8 @@ var parseUser = function (aUser) { user.userGitHubImportPageUrl = user.userPageUrl + '/github/import'; user.userEditProfilePageUrl = user.userPageUrl + '/profile/edit'; user.userUpdatePageUrl = user.userPageUrl + '/update'; - user.userRemovePageUrl = '/remove/users/' + user.name; - user.userFlagPageUrl = '/flag/users/' + user.name; + user.userRemovePageUrl = '/remove/users/' + user.slugUrl; + user.userFlagPageUrl = '/flag/users/' + user.slugUrl; // Funcs user.githubUserId = function () { @@ -540,8 +557,13 @@ var parseCategory = function (aCategory) { category = aCategory.toObject ? aCategory.toObject() : aCategory; // Urls - category.categoryPageUrl = '/' + category.slug; - category.categoryPostDiscussionPageUrl = '/post/' + category.slug; + + category.slugUrl = category.slug.split('/').map(function (aStr) { + return encode(aStr); + }).join('/'); + + category.categoryPageUrl = '/' + category.slugUrl; + category.categoryPostDiscussionPageUrl = '/post/' + category.slugUrl; // Functions category.canUserPostTopic = function (aUser) { diff --git a/package.json b/package.json index 44a41687f..41acfc9bb 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "OpenUserJS.org", "description": "An open source user scripts repo built using Node.js", - "version": "0.2.2", + "version": "0.2.3", "main": "app", "dependencies": { "ace-builds": "git://github.com/ajaxorg/ace-builds#e94cb3c", diff --git a/views/pages/scriptPage.html b/views/pages/scriptPage.html index f6d98d787..057181099 100644 --- a/views/pages/scriptPage.html +++ b/views/pages/scriptPage.html @@ -58,7 +58,7 @@