diff --git a/Gemfile b/Gemfile index f7eca24642..10f209d3d6 100644 --- a/Gemfile +++ b/Gemfile @@ -30,7 +30,6 @@ gem 'onebox' gem 'ember-rails' gem 'ember-source', '1.12.1' -gem 'handlebars-source', '2.0.0' gem 'barber' gem 'babel-transpiler' diff --git a/Gemfile.lock b/Gemfile.lock index 94f520b288..019f893ffb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -159,7 +159,6 @@ GEM globalid (0.3.6) activesupport (>= 4.1.0) guess_html_encoding (0.0.11) - handlebars-source (2.0.0) hashie (3.4.2) highline (1.7.7) hike (1.2.3) @@ -465,7 +464,6 @@ DEPENDENCIES flamegraph foreman gctools - handlebars-source (= 2.0.0) highline hiredis htmlentities diff --git a/app/assets/javascripts/admin/components/ace-editor.js.es6 b/app/assets/javascripts/admin/components/ace-editor.js.es6 index b660d48859..755dc574b2 100644 --- a/app/assets/javascripts/admin/components/ace-editor.js.es6 +++ b/app/assets/javascripts/admin/components/ace-editor.js.es6 @@ -16,7 +16,7 @@ export default Ember.Component.extend({ render(buffer) { buffer.push("
";
_.each(raw, function(linehash) {
- returned += Handlebars.Utils.escapeExpression(linehash["QUERY PLAN"]);
+ returned += Discourse.Utilities.escapeExpression(linehash["QUERY PLAN"]);
returned += "
";
});
@@ -32,7 +32,7 @@ export default Ember.Controller.extend({
processed_sample: Ember.computed.map('model.sample', function(grant) {
var i18nKey = 'admin.badges.preview.grant.with',
- i18nParams = { username: Handlebars.Utils.escapeExpression(grant.username) };
+ i18nParams = { username: Discourse.Utilities.escapeExpression(grant.username) };
if (grant.post_id) {
i18nKey += "_post";
@@ -41,7 +41,7 @@ export default Ember.Controller.extend({
if (grant.granted_at) {
i18nKey += "_time";
- i18nParams.time = Handlebars.Utils.escapeExpression(moment(grant.granted_at).format(I18n.t('dates.long_with_year')));
+ i18nParams.time = Discourse.Utilities.escapeExpression(moment(grant.granted_at).format(I18n.t('dates.long_with_year')));
}
return I18n.t(i18nKey, i18nParams);
diff --git a/app/assets/javascripts/admin/models/staff_action_log.js b/app/assets/javascripts/admin/models/staff_action_log.js
index 0dde773661..14f250479d 100644
--- a/app/assets/javascripts/admin/models/staff_action_log.js
+++ b/app/assets/javascripts/admin/models/staff_action_log.js
@@ -17,14 +17,14 @@ Discourse.StaffActionLog = Discourse.Model.extend({
formatted += this.format('admin.logs.staff_actions.previous_value', 'previous_value');
}
if (!this.get('useModalForDetails')) {
- if (this.get('details')) formatted += Handlebars.Utils.escapeExpression(this.get('details')) + '
';
+ if (this.get('details')) formatted += Discourse.Utilities.escapeExpression(this.get('details')) + '
';
}
return formatted;
}.property('ip_address', 'email', 'topic_id', 'post_id', 'category_id'),
format: function(label, propertyName) {
if (this.get(propertyName)) {
- return ('' + I18n.t(label) + ': ' + Handlebars.Utils.escapeExpression(this.get(propertyName)) + '
');
+ return ('' + I18n.t(label) + ': ' + Discourse.Utilities.escapeExpression(this.get(propertyName)) + '
');
} else {
return '';
}
diff --git a/app/assets/javascripts/admin/views/admin-backups-logs.js.es6 b/app/assets/javascripts/admin/views/admin-backups-logs.js.es6
index 9d164142aa..c5ba44e27e 100644
--- a/app/assets/javascripts/admin/views/admin-backups-logs.js.es6
+++ b/app/assets/javascripts/admin/views/admin-backups-logs.js.es6
@@ -19,7 +19,7 @@ export default Ember.View.extend({
let formattedLogs = this.get("formattedLogs");
for (let i = this.get("index"), length = logs.length; i < length; i++) {
const date = logs[i].get("timestamp"),
- message = Handlebars.Utils.escapeExpression(logs[i].get("message"));
+ message = Discourse.Utilities.escapeExpression(logs[i].get("message"));
formattedLogs += "[" + date + "] " + message + "\n";
}
// update the formatted logs & cache index
diff --git a/app/assets/javascripts/discourse/components/notification-item.js.es6 b/app/assets/javascripts/discourse/components/notification-item.js.es6
index 8b4730074d..66520e002f 100644
--- a/app/assets/javascripts/discourse/components/notification-item.js.es6
+++ b/app/assets/javascripts/discourse/components/notification-item.js.es6
@@ -44,10 +44,10 @@ export default Ember.Component.extend({
description: function() {
const badgeName = this.get("notification.data.badge_name");
- if (badgeName) { return Handlebars.Utils.escapeExpression(badgeName); }
+ if (badgeName) { return Discourse.Utilities.escapeExpression(badgeName); }
const title = this.get('notification.data.topic_title');
- return Ember.isEmpty(title) ? "" : Handlebars.Utils.escapeExpression(title);
+ return Ember.isEmpty(title) ? "" : Discourse.Utilities.escapeExpression(title);
}.property("notification.data.{badge_name,topic_title}"),
_markRead: function(){
diff --git a/app/assets/javascripts/discourse/components/post-gutter.js.es6 b/app/assets/javascripts/discourse/components/post-gutter.js.es6
index c545f1c0b7..9889184d1f 100644
--- a/app/assets/javascripts/discourse/components/post-gutter.js.es6
+++ b/app/assets/javascripts/discourse/components/post-gutter.js.es6
@@ -48,7 +48,7 @@ export default Component.extend(StringBuffer, {
let title = get(l, 'title');
if (!isEmpty(title)) {
- title = Handlebars.Utils.escapeExpression(title);
+ title = Discourse.Utilities.escapeExpression(title);
buffer.push(Discourse.Emoji.unescape(title));
}
if (clicks) {
diff --git a/app/assets/javascripts/discourse/components/poster-name.js.es6 b/app/assets/javascripts/discourse/components/poster-name.js.es6
index 5a2a7bd8ea..7f213f7c00 100644
--- a/app/assets/javascripts/discourse/components/poster-name.js.es6
+++ b/app/assets/javascripts/discourse/components/poster-name.js.es6
@@ -40,7 +40,7 @@ const PosterNameComponent = Em.Component.extend({
// Are we showing full names?
if (name && this.get('displayNameOnPosts') && (this.sanitizeName(name) !== this.sanitizeName(username))) {
- name = Handlebars.Utils.escapeExpression(name);
+ name = Discourse.Utilities.escapeExpression(name);
buffer.push("" + name + "");
}
@@ -48,7 +48,7 @@ const PosterNameComponent = Em.Component.extend({
let title = post.get('user_title');
if (!Em.isEmpty(title)) {
- title = Handlebars.Utils.escapeExpression(title);
+ title = Discourse.Utilities.escapeExpression(title);
buffer.push('');
if (Em.isEmpty(primaryGroupName)) {
buffer.push(title);
diff --git a/app/assets/javascripts/discourse/components/topic-status.js.es6 b/app/assets/javascripts/discourse/components/topic-status.js.es6
index 9b8c644561..9576164b1b 100644
--- a/app/assets/javascripts/discourse/components/topic-status.js.es6
+++ b/app/assets/javascripts/discourse/components/topic-status.js.es6
@@ -29,7 +29,7 @@ export default Ember.Component.extend(StringBuffer, {
const self = this;
const renderIcon = function(name, key, actionable) {
- const title = Handlebars.Utils.escapeExpression(I18n.t(`topic_statuses.${key}.help`)),
+ const title = Discourse.Utilities.escapeExpression(I18n.t(`topic_statuses.${key}.help`)),
startTag = actionable ? "a href" : "span",
endTag = actionable ? "a" : "span",
iconArgs = key === 'unpinned' ? { 'class': 'unpinned' } : null,
diff --git a/app/assets/javascripts/discourse/controllers/composer.js.es6 b/app/assets/javascripts/discourse/controllers/composer.js.es6
index 92b51cc959..3aa0c7dd74 100644
--- a/app/assets/javascripts/discourse/controllers/composer.js.es6
+++ b/app/assets/javascripts/discourse/controllers/composer.js.es6
@@ -230,7 +230,7 @@ export default Ember.Controller.extend({
if (currentTopic) {
buttons.push({
- "label": I18n.t("composer.reply_here") + "
" + Handlebars.Utils.escapeExpression(currentTopic.get('title')) + "",
+ "label": I18n.t("composer.reply_here") + "
" + Discourse.Utilities.escapeExpression(currentTopic.get('title')) + "",
"class": "btn btn-reply-here",
"callback": function() {
composer.set('topic', currentTopic);
@@ -241,7 +241,7 @@ export default Ember.Controller.extend({
}
buttons.push({
- "label": I18n.t("composer.reply_original") + "
" + Handlebars.Utils.escapeExpression(this.get('model.topic.title')) + "",
+ "label": I18n.t("composer.reply_original") + "
" + Discourse.Utilities.escapeExpression(this.get('model.topic.title')) + "",
"class": "btn-primary btn-reply-on-original",
"callback": function() {
self.save(true);
diff --git a/app/assets/javascripts/discourse/controllers/forgot-password.js.es6 b/app/assets/javascripts/discourse/controllers/forgot-password.js.es6
index 64da750cfe..afecb74ef9 100644
--- a/app/assets/javascripts/discourse/controllers/forgot-password.js.es6
+++ b/app/assets/javascripts/discourse/controllers/forgot-password.js.es6
@@ -17,7 +17,7 @@ export default Ember.Controller.extend(ModalFunctionality, {
var success = function(data) {
// don't tell people what happened, this keeps it more secure (ensure same on server)
- var escaped = Handlebars.Utils.escapeExpression(self.get('accountEmailOrUsername'));
+ var escaped = Discourse.Utilities.escapeExpression(self.get('accountEmailOrUsername'));
var isEmail = self.get('accountEmailOrUsername').match(/@/);
var key = 'forgot_password.complete_' + (isEmail ? 'email' : 'username');
diff --git a/app/assets/javascripts/discourse/controllers/full-page-search.js.es6 b/app/assets/javascripts/discourse/controllers/full-page-search.js.es6
index a4208806a7..a5129e1959 100644
--- a/app/assets/javascripts/discourse/controllers/full-page-search.js.es6
+++ b/app/assets/javascripts/discourse/controllers/full-page-search.js.es6
@@ -75,7 +75,7 @@ export default Ember.Controller.extend({
}
});
}
- return Handlebars.Utils.escapeExpression(q);
+ return Discourse.Utilities.escapeExpression(q);
},
_searchOnSortChange: true,
diff --git a/app/assets/javascripts/discourse/dialects/code_dialect.js b/app/assets/javascripts/discourse/dialects/code_dialect.js
index 2594cd8316..8e2b5bddf2 100644
--- a/app/assets/javascripts/discourse/dialects/code_dialect.js
+++ b/app/assets/javascripts/discourse/dialects/code_dialect.js
@@ -76,7 +76,7 @@ Discourse.Dialect.on('parseNode', function (event) {
} else {
regexp = /^ +| +$/g;
}
- node[node.length-1] = Handlebars.Utils.escapeExpression(contents.replace(regexp,''));
+ node[node.length-1] = Discourse.Utilities.escapeExpression(contents.replace(regexp,''));
}
});
diff --git a/app/assets/javascripts/discourse/dialects/dialect.js b/app/assets/javascripts/discourse/dialects/dialect.js
index cfb7b67f00..760bddd046 100644
--- a/app/assets/javascripts/discourse/dialects/dialect.js
+++ b/app/assets/javascripts/discourse/dialects/dialect.js
@@ -13,7 +13,7 @@ var parser = window.BetterMarkdown,
emitters = [],
hoisted,
preProcessors = [],
- escape = Handlebars.Utils.escapeExpression;
+ escape = Discourse.Utilities.escapeExpression;
/**
Initialize our dialects for processing.
diff --git a/app/assets/javascripts/discourse/helpers/user-status.js.es6 b/app/assets/javascripts/discourse/helpers/user-status.js.es6
index 7a5ddbd78f..e40197b7b5 100644
--- a/app/assets/javascripts/discourse/helpers/user-status.js.es6
+++ b/app/assets/javascripts/discourse/helpers/user-status.js.es6
@@ -5,7 +5,7 @@ const Safe = Handlebars.SafeString;
export default Ember.Handlebars.makeBoundHelper(function(user, args) {
if (!user) { return; }
- const name = Handlebars.Utils.escapeExpression(user.get('name'));
+ const name = Discourse.Utilities.escapeExpression(user.get('name'));
const currentUser = args.hash.currentUser;
if (currentUser && user.get('admin') && currentUser.get('staff')) {
diff --git a/app/assets/javascripts/discourse/lib/utilities.js b/app/assets/javascripts/discourse/lib/utilities.js
index f01cec5816..edf1436088 100644
--- a/app/assets/javascripts/discourse/lib/utilities.js
+++ b/app/assets/javascripts/discourse/lib/utilities.js
@@ -1,3 +1,18 @@
+
+var discourseEscape = {
+ "&": "&",
+ "<": "<",
+ ">": ">",
+ '"': """,
+ "'": "'",
+ '`': '`'
+};
+var discourseBadChars = /[&<>"'`]/g;
+var discoursePossible = /[&<>"'`]/;
+
+function discourseEscapeChar(chr) {
+ return discourseEscape[chr];
+}
Discourse.Utilities = {
translateSize: function(size) {
@@ -24,6 +39,28 @@ Discourse.Utilities = {
}
},
+ // Handlebars no longer allows spaces in its `escapeExpression` code which makes it
+ // unsuitable for many of Discourse's uses. Use `Handlebars.Utils.escapeExpression`
+ // when escaping an attribute in HTML, otherwise this one will do.
+ escapeExpression: function(string) {
+ // don't escape SafeStrings, since they're already safe
+ if (string instanceof Handlebars.SafeString) {
+ return string.toString();
+ } else if (string == null) {
+ return "";
+ } else if (!string) {
+ return string + '';
+ }
+
+ // Force a string conversion as this will be done by the append regardless and
+ // the regex test will do this transparently behind the scenes, causing issues if
+ // an object's to string has escaped characters in it.
+ string = "" + string;
+
+ if(!discoursePossible.test(string)) { return string; }
+ return string.replace(discourseBadChars, discourseEscapeChar);
+ },
+
avatarUrl: function(template, size) {
if (!template) { return ""; }
var rawSize = Discourse.Utilities.getRawSize(Discourse.Utilities.translateSize(size));
diff --git a/app/assets/javascripts/discourse/models/composer.js.es6 b/app/assets/javascripts/discourse/models/composer.js.es6
index 352c6f1989..aedac56c9a 100644
--- a/app/assets/javascripts/discourse/models/composer.js.es6
+++ b/app/assets/javascripts/discourse/models/composer.js.es6
@@ -138,7 +138,7 @@ const Composer = RestModel.extend({
const postNumber = this.get('post.post_number');
postLink = "" +
I18n.t("post.post_number", { number: postNumber }) + "";
- topicLink = " " + (Handlebars.Utils.escapeExpression(topic.get('title'))) + "";
+ topicLink = " " + Discourse.Utilities.escapeExpression(topic.get('title')) + "";
usernameLink = "" + this.get('post.username') + "";
}
diff --git a/app/assets/javascripts/main_include.js b/app/assets/javascripts/main_include.js
index 853d0d5e24..65660c12e8 100644
--- a/app/assets/javascripts/main_include.js
+++ b/app/assets/javascripts/main_include.js
@@ -70,6 +70,7 @@
//= require ./discourse/components/topic-notifications-button
//= require ./discourse/lib/link-mentions
//= require ./discourse/views/header
+//= require ./discourse/lib/utilities
//= require ./discourse/dialects/dialect
//= require ./discourse/lib/emoji/emoji
//= require ./discourse/lib/emoji/emoji-groups
diff --git a/lib/freedom_patches/ember_compat_handlebars.rb b/lib/freedom_patches/ember_compat_handlebars.rb
index b3cdd4285e..bfffaeddea 100644
--- a/lib/freedom_patches/ember_compat_handlebars.rb
+++ b/lib/freedom_patches/ember_compat_handlebars.rb
@@ -5,7 +5,7 @@ module Barber
class EmberCompatPrecompiler < Barber::Precompiler
def sources
- [handlebars, precompiler]
+ [File.open("#{Rails.root}/vendor/assets/javascripts/handlebars.js"), precompiler]
end
def precompiler
diff --git a/lib/pretty_text.rb b/lib/pretty_text.rb
index f3acfbc099..3f09835437 100644
--- a/lib/pretty_text.rb
+++ b/lib/pretty_text.rb
@@ -90,9 +90,9 @@ module PrettyText
ctx_load(ctx,
"vendor/assets/javascripts/better_markdown.js",
"app/assets/javascripts/defer/html-sanitizer-bundle.js",
+ "app/assets/javascripts/discourse/lib/utilities.js",
"app/assets/javascripts/discourse/dialects/dialect.js",
"app/assets/javascripts/discourse/lib/censored-words.js",
- "app/assets/javascripts/discourse/lib/utilities.js",
"app/assets/javascripts/discourse/lib/markdown.js",
)
diff --git a/vendor/assets/javascripts/handlebars.js b/vendor/assets/javascripts/handlebars.js
index f826bbfd38..04bb23566f 100644
--- a/vendor/assets/javascripts/handlebars.js
+++ b/vendor/assets/javascripts/handlebars.js
@@ -64,11 +64,16 @@ var __module3__ = (function(__dependency1__) {
">": ">",
'"': """,
"'": "'",
- "`": "`"
+ '`': '`',
+ '\n' : '\\n', // NewLine
+ '\r' : '\\n', // Return
+ '\b' : '\\b', // Backspace
+ '\f' : '\\f', // Form fee
+ '\t' : '\\t', // Tab
+ '\v' : '\\v' // Vertical Tab
};
-
- var badChars = /[&<>"'`]/g;
- var possible = /[&<>"'`]/;
+ var badChars = /[&<>"'`\b\f\n\r\t\v]/g;
+ var possible = /[&<>"'`\b\f\n\r\t\v]/;
function escapeChar(chr) {
return escape[chr];