From 43e8ea4d4d1967f82cfdb3b2a890fd5c40e84f6e Mon Sep 17 00:00:00 2001 From: Lin Hua Cheng Date: Tue, 5 Feb 2013 14:45:47 -0800 Subject: [PATCH] Fix on async messaging to don't escape message string marked as safe. Fixes bug 1103243. Change-Id: Ia9541d5cc338914f5f9164e7ee1386270211f4d0 --- horizon/messages.py | 8 +++++++- horizon/middleware.py | 4 ++-- horizon/static/horizon/js/horizon.messages.js | 12 +++++++++--- horizon/static/horizon/tests/messages.js | 10 ++++++++++ .../horizon/client_side/_alert_message.html | 10 +++++++++- horizon/templatetags/horizon.py | 4 +++- horizon/test/tests/messages.py | 17 ++++++++++++++++- 7 files changed, 56 insertions(+), 9 deletions(-) diff --git a/horizon/messages.py b/horizon/messages.py index feac64dbe..05b2ee109 100644 --- a/horizon/messages.py +++ b/horizon/messages.py @@ -22,6 +22,7 @@ messaging needs (e.g. AJAX communication, etc.). from django.contrib import messages as _messages from django.contrib.messages import constants from django.utils.encoding import force_unicode +from django.utils.safestring import SafeData def add_message(request, level, message, extra_tags='', fail_silently=False): @@ -30,8 +31,13 @@ def add_message(request, level, message, extra_tags='', fail_silently=False): """ if request.is_ajax(): tag = constants.DEFAULT_TAGS[level] + # if message is marked as safe, pass "safe" tag as extra_tags so that + # client can skip HTML escape for the message when rendering + if isinstance(message, SafeData): + extra_tags = extra_tags + ' safe' request.horizon['async_messages'].append([tag, - force_unicode(message)]) + force_unicode(message), + extra_tags]) else: return _messages.add_message(request, level, message, extra_tags, fail_silently) diff --git a/horizon/middleware.py b/horizon/middleware.py index 2775bcab0..abd0077e3 100644 --- a/horizon/middleware.py +++ b/horizon/middleware.py @@ -98,8 +98,8 @@ class HorizonMiddleware(object): # Drop our messages back into the session as per usual so they # don't disappear during the redirect. Not that we explicitly # use django's messages methods here. - for tag, message in queued_msgs: - getattr(django_messages, tag)(request, message) + for tag, message, extra_tags in queued_msgs: + getattr(django_messages, tag)(request, message, extra_tags) redirect_response = http.HttpResponse() redirect_response['X-Horizon-Location'] = response['location'] return redirect_response diff --git a/horizon/static/horizon/js/horizon.messages.js b/horizon/static/horizon/js/horizon.messages.js index dfeedd548..e3ce88e67 100644 --- a/horizon/static/horizon/js/horizon.messages.js +++ b/horizon/static/horizon/js/horizon.messages.js @@ -1,9 +1,15 @@ -horizon.alert = function (type, message) { +horizon.alert = function (type, message, extra_tags) { + safe = false + // Check if the message is tagged as safe. + if (typeof(extra_tags) !== "undefined" && _.contains(extra_tags.split(' '), 'safe')) { + safe = true + } var template = horizon.templates.compiled_templates["#alert_message_template"], params = { "type": type, "type_capitalized": horizon.utils.capitalize(type), - "message": message + "message": message, + "safe": safe }; return $(template.render(params)).hide().prependTo("#main_content .messages").fadeIn(100); }; @@ -26,7 +32,7 @@ horizon.addInitFunction(function () { $("body").ajaxComplete(function(event, request, settings){ var message_array = $.parseJSON(horizon.ajax.get_messages(request)); $(message_array).each(function (index, item) { - horizon.alert(item[0], item[1]); + horizon.alert(item[0], item[1], item[2]); }); }); diff --git a/horizon/static/horizon/tests/messages.js b/horizon/static/horizon/tests/messages.js index ba108b1f6..f75224243 100644 --- a/horizon/static/horizon/tests/messages.js +++ b/horizon/static/horizon/tests/messages.js @@ -25,4 +25,14 @@ horizon.addInitFunction(function () { equal($('#main_content .messages .alert-success').length, 0, "Verify our success message was removed."); equal($('#main_content .messages .alert').length, 0, "Verify no messages remain."); }); + + test("Alert With HTML Tag", function () { + safe_string = "A safe message here!" + message = horizon.alert("success", safe_string, "safe"); + ok(message, "Create a message with extra tag."); + ok((message.html().indexOf(safe_string ) != -1), 'Verify the message with HTML tag was not escaped.'); + equal($('#main_content .messages .alert').length, 1, "Verify our message was added to the DOM."); + horizon.clearAllMessages(); + equal($('#main_content .messages .alert').length, 0, "Verify our message was removed."); + }); }); diff --git a/horizon/templates/horizon/client_side/_alert_message.html b/horizon/templates/horizon/client_side/_alert_message.html index 24b7098a3..6a5fea204 100644 --- a/horizon/templates/horizon/client_side/_alert_message.html +++ b/horizon/templates/horizon/client_side/_alert_message.html @@ -7,7 +7,15 @@ {% jstemplate %}
× -

[[type_capitalized]]: [[message]]

+

+ [[type_capitalized]]: + [[#safe]] + [[[message]]] + [[/safe]] + [[^safe]] + [[message]] + [[/safe]] +

{% endjstemplate %} {% endblock %} diff --git a/horizon/templatetags/horizon.py b/horizon/templatetags/horizon.py index ad1c317dd..a428a2bbc 100644 --- a/horizon/templatetags/horizon.py +++ b/horizon/templatetags/horizon.py @@ -120,6 +120,7 @@ class JSTemplateNode(template.Node): def render(self, context, ): output = self.nodelist.render(context) + output = output.replace('[[[', '{{{').replace(']]]', '}}}') output = output.replace('[[', '{{').replace(']]', '}}') output = output.replace('[%', '{%').replace('%]', '%}') return output @@ -128,7 +129,8 @@ class JSTemplateNode(template.Node): @register.tag def jstemplate(parser, token): """ - Replaces ``[[`` and ``]]`` with ``{{`` and ``}}`` and + Replaces ``[[[`` and ``]]]`` with ``{{{`` and ``}}}``, + ``[[`` and ``]]`` with ``{{`` and ``}}`` and ``[%`` and ``%]`` with ``{%`` and ``%}`` to avoid conflicts with Django's template engine when using any of the Mustache-based templating libraries. diff --git a/horizon/test/tests/messages.py b/horizon/test/tests/messages.py index 5983bc7e5..8548ff4df 100644 --- a/horizon/test/tests/messages.py +++ b/horizon/test/tests/messages.py @@ -18,6 +18,7 @@ import json from django import http from django.utils.encoding import force_unicode +from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ from horizon import messages @@ -29,7 +30,21 @@ class MessageTests(test.TestCase): def test_middleware_header(self): req = self.request string = _("Giant ants are attacking San Francisco!") - expected = ["error", force_unicode(string)] + expected = ["error", force_unicode(string), ""] + self.assertTrue("async_messages" in req.horizon) + self.assertItemsEqual(req.horizon['async_messages'], []) + req.META['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' + messages.error(req, string) + self.assertItemsEqual(req.horizon['async_messages'], [expected]) + res = http.HttpResponse() + res = middleware.HorizonMiddleware().process_response(req, res) + self.assertEqual(res['X-Horizon-Messages'], + json.dumps([expected])) + + def test_safe_message(self): + req = self.request + string = mark_safe(_("We are now safe from ants! Go here!")) + expected = ["error", force_unicode(string), " safe"] self.assertTrue("async_messages" in req.horizon) self.assertItemsEqual(req.horizon['async_messages'], []) req.META['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'