By default, html-encode translation parameters

Also ensure that any existing parameter is not double-encoded by
removing .Text when used as a translation parameter.
This commit is contained in:
Sebastien Ros
2015-06-16 16:50:45 -07:00
parent cd49d2ccf2
commit 20c5b494cd
13 changed files with 106 additions and 18 deletions

View File

@@ -0,0 +1,46 @@
using Autofac;
using Moq;
using NUnit.Framework;
using Orchard.Localization;
using Orchard.Localization.Services;
using Orchard.Mvc;
using Orchard.Tests.Stubs;
using System.Web;
namespace Orchard.Tests.Localization {
[TestFixture]
public class TextTests {
private IContainer _container;
private IText _text;
[SetUp]
public void Init() {
var mockLocalizedManager = new Mock<ILocalizedStringManager>();
mockLocalizedManager
.Setup(x => x.GetLocalizedString(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))
.Returns("foo {0}");
var builder = new ContainerBuilder();
builder.RegisterInstance(new StubCultureSelector("fr-CA")).As<ICultureSelector>();
builder.RegisterInstance(new StubWorkContext()).As<WorkContext>();
builder.RegisterType<StubWorkContextAccessor>().As<IWorkContextAccessor>();
builder.RegisterInstance(mockLocalizedManager.Object);
builder.RegisterType<Orchard.Localization.Text>().As<IText>().WithParameter(new NamedParameter("scope", "scope"));
_container = builder.Build();
_text = _container.Resolve<IText>();
}
[Test]
public void TextHtmlEncodeAllArguments() {
Assert.That(_text.Get("foo {0}", "bar").Text, Is.EqualTo("foo bar"));
Assert.That(_text.Get("foo {0}", "<bar>").Text, Is.EqualTo("foo &lt;bar&gt;"));
}
[Test]
public void TextDoesEncodeHtmlEncodedArguments()
{
Assert.That(_text.Get("foo {0}", new HtmlString("bar")).Text, Is.EqualTo("foo bar"));
Assert.That(_text.Get("foo {0}", new HtmlString("<bar>")).Text, Is.EqualTo("foo <bar>"));
}
}
}

View File

@@ -259,6 +259,7 @@
<Compile Include="FileSystems\Dependencies\AssemblyProbingFolderTests.cs" />
<Compile Include="FileSystems\Dependencies\DependenciesFolderTests.cs" />
<Compile Include="FileSystems\VirtualPath\DefaultVirtualPathProviderTests.cs" />
<Compile Include="Localization\TextTests.cs" />
<Compile Include="Localization\CurrentCultureWorkContextTests.cs" />
<Compile Include="Localization\CultureManagerTests.cs" />
<Compile Include="Localization\DateTimePartsTests.cs" />

View File

@@ -1 +1,6 @@
<div class="message message-@Model.Type">@Html.Raw(Html.Encode(Model.Message).Replace("\n", "<br />"))</div>
@*
Model.Message can either be:
- an IHtmlString and won't be re-encoded
- a string and will be encoded
*@
<div class="message message-@Model.Type">@Html.Raw(Html.Encode(Model.Message).Replace("\n", "<br />"))</div>

View File

@@ -3,7 +3,7 @@
<div class="user-display">
@if (WorkContext.CurrentUser != null) {
<span class="user-actions welcome">
@T("Welcome, <strong>{0}</strong>!", "<a href=\"" + @Url.Action("ChangePassword", new { Controller = "Account", Area = "Orchard.Users" }) + "\">" + @Html.ItemDisplayText(WorkContext.CurrentUser) + "</a>")
@T("Welcome, <strong>{0}</strong>!", Html.Raw("<a href=\"" + @Url.Action("ChangePassword", new { Controller = "Account", Area = "Orchard.Users" }) + "\">" + Html.ItemDisplayText(WorkContext.CurrentUser) + "</a>"))
</span>
<span class="user-actions">
@Html.ActionLink(T("Sign Out").ToString(), "LogOff", new { Controller = "Account", Area = "Orchard.Users", ReturnUrl = Context.Request.RawUrl }, new { rel = "nofollow" })

View File

@@ -32,5 +32,5 @@
}
<div class="publication-status">
<em>@T("Status:")</em>
<span>@T("{0}", mediaPart.ContentItem.VersionRecord.Published ? "Published" : "Draft")</span>
<span>@T(mediaPart.ContentItem.VersionRecord.Published ? "Published" : "Draft")</span>
</div>

View File

@@ -89,7 +89,7 @@
featureClassName += " missingDependencies";
}
<li class="@featureClassName" id="@featureId" title="@T("{0} is {1}", Html.AttributeEncode(featureName), featureState)">
<li class="@featureClassName" id="@featureId" title="@T(feature.IsEnabled ? "{0} is enabled" : "{0} is disabled", Html.AttributeEncode(featureName))">
<div class="summary">
<div class="properties">
<h3>

View File

@@ -249,21 +249,21 @@ namespace Orchard.Projections.FilterEditors.Forms {
switch (op) {
case DateTimeOperator.LessThan:
return T("{0} is less than {1}{2}", fieldName, value, T(valueUnit).Text);
return T("{0} is less than {1}{2}", fieldName, value, T(valueUnit));
case DateTimeOperator.LessThanEquals:
return T("{0} is less or equal than {1}{2}", fieldName, value, T(valueUnit).Text);
return T("{0} is less or equal than {1}{2}", fieldName, value, T(valueUnit));
case DateTimeOperator.Equals:
return T("{0} equals {1}{2}", fieldName, value, T(valueUnit).Text);
return T("{0} equals {1}{2}", fieldName, value, T(valueUnit));
case DateTimeOperator.NotEquals:
return T("{0} is not equal to {1}{2}", fieldName, value, T(valueUnit).Text);
return T("{0} is not equal to {1}{2}", fieldName, value, T(valueUnit));
case DateTimeOperator.GreaterThan:
return T("{0} is greater than {1}{2}", fieldName, value, T(valueUnit).Text);
return T("{0} is greater than {1}{2}", fieldName, value, T(valueUnit));
case DateTimeOperator.GreaterThanEquals:
return T("{0} is greater or equal than {1}{2}", fieldName, value, T(valueUnit).Text);
return T("{0} is greater or equal than {1}{2}", fieldName, value, T(valueUnit));
case DateTimeOperator.Between:
return T("{0} is between {1}{2} and {3}{4}", fieldName, min, T(minUnit).Text, max, T(maxUnit).Text);
return T("{0} is between {1}{2} and {3}{4}", fieldName, min, T(minUnit), max, T(maxUnit));
case DateTimeOperator.NotBetween:
return T("{0} is not between {1}{2} and {3}{4}", fieldName, min, T(minUnit).Text, max, T(maxUnit).Text);
return T("{0} is not between {1}{2} and {3}{4}", fieldName, min, T(minUnit), max, T(maxUnit));
}
// should never be hit, but fail safe

View File

@@ -1,9 +1,8 @@
@model AdminEditViewModel
@using Orchard.Projections.Models;
@using Orchard.Projections.ViewModels;
@{
Layout.Title = T("Edit Query - {0}", Model.Name).Text;
Layout.Title = T("Edit Query - {0}", Model.Name);
Style.Include("admin-projections.css");
}

View File

@@ -2,11 +2,11 @@
@{
string name = Model.Name;
string title = null;
IHtmlString title = null;
if (Model.State != null && HasText(Model.State.Unity)) {
string amount = Model.State.Amount;
string unity = Model.State.Unity;
title = T("{0} {1} after", amount, T(unity).Text).Text;
title = T("{0} {1} after", amount, T(unity));
}
}

View File

@@ -1 +1 @@
<h1 id="page-title">@Model.Title.ToString()</h1>
<h1 id="page-title">@Model.Title</h1>

View File

@@ -2,6 +2,10 @@
using System.Web;
namespace Orchard.Localization {
/// <summary>
/// An HTML-encoded localized string
/// </summary>
public class LocalizedString : MarshalByRefObject, IHtmlString {
private readonly string _localized;
private readonly string _scope;
@@ -30,6 +34,9 @@ namespace Orchard.Localization {
get { return _scope; }
}
/// <summary>
/// The HTML-Encoded original text
/// </summary>
public string TextHint {
get { return _textHint; }
}
@@ -38,10 +45,16 @@ namespace Orchard.Localization {
get { return _args; }
}
/// <summary>
/// The HTML-encoded localized text
/// </summary>
public string Text {
get { return _localized; }
}
/// <summary>
/// The HTML-encoded localized text
/// </summary>
public override string ToString() {
return _localized;
}

View File

@@ -2,6 +2,13 @@ using System.Linq;
using Orchard.Localization;
namespace Orchard.Localization {
/// <summary>
/// Localizes some text based on the current Work Context culture
/// </summary>
/// <param name="text">The text format to localize</param>
/// <param name="args">The arguments used in the text format. The arguments are HTML-encoded if they don't implement <see cref="System.Web.IHtmlString"/>.</param>
/// <returns>An HTML-encoded localized string</returns>
public delegate LocalizedString Localizer(string text, params object[] args);
}

View File

@@ -2,6 +2,8 @@ using System;
using System.Globalization;
using Orchard.Localization.Services;
using Orchard.Logging;
using System.Web;
using System.Linq;
namespace Orchard.Localization {
public class Text : IText {
@@ -27,9 +29,15 @@ namespace Orchard.Localization {
var currentCulture = workContext.CurrentCulture;
var localizedFormat = _localizedStringManager.GetLocalizedString(_scope, textHint, currentCulture);
// localization arguments are HTML-encoded unless they implement IHtmlString
return args.Length == 0
? new LocalizedString(localizedFormat, _scope, textHint, args)
: new LocalizedString(string.Format(GetFormatProvider(currentCulture), localizedFormat, args), _scope, textHint, args);
: new LocalizedString(
String.Format(GetFormatProvider(currentCulture), localizedFormat, args.Select(Encode).ToArray()),
_scope,
textHint,
args);
}
return new LocalizedString(textHint, _scope, textHint, args);
@@ -43,5 +51,14 @@ namespace Orchard.Localization {
return null;
}
}
static object Encode(object arg)
{
if (arg is IFormattable || arg is IHtmlString) {
return arg;
}
return HttpUtility.HtmlEncode(arg);
}
}
}