Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 19 additions & 11 deletions src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,26 +178,31 @@ public ActionResult Frame(int id, string culture)
return RedirectPermanent($"../../{id}{query}");
}

private static bool ValidateProvidedCulture(string culture)
/// <summary>
/// Validates the provided culture code.
/// </summary>
/// <remarks>
/// Marked as internal to expose for unit tests.
/// </remarks>
internal static bool ValidateProvidedCulture(string culture)
{
if (string.IsNullOrEmpty(culture))
{
return true;
}

// We can be confident the backoffice will have provided a valid culture in linking to the
// preview, so we don't need to check that the culture matches an Umbraco language.
// We are only concerned here with protecting against XSS attacks from a fiddled preview
// URL, so we can just confirm we have a valid culture.
try
{
CultureInfo.GetCultureInfo(culture, true);
return true;
}
catch (CultureNotFoundException)
// Culture codes are expected to match this pattern.
if (CultureCodeRegex().IsMatch(culture) is false)
{
return false;
}

// We can be confident the backoffice will have provided a valid culture in linking to the
// preview, so we don't need to check that the culture matches an Umbraco language (or is even a
// valid culture code).
// We are only concerned here with protecting against XSS attacks from a fiddled preview
// URL, so we can proceed if the the regex is matched.
return true;
}

public ActionResult? EnterPreview(int id)
Expand Down Expand Up @@ -261,4 +266,7 @@ public ActionResult End(string? redir = null)

[GeneratedRegex("^\\/(?<id>\\d*)(\\?culture=(?<culture>[\\w-]*))?$")]
private static partial Regex DefaultPreviewRedirectRegex();

[GeneratedRegex(@"^[a-z]{2,3}[-0-9a-z]*$", RegexOptions.IgnoreCase)]
private static partial Regex CultureCodeRegex();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.

using System.Globalization;
using NUnit.Framework;
using Umbraco.Cms.Web.BackOffice.Controllers;

namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers;

[TestFixture]
public class PreviewControllerTests
{
[TestCase("en-US", true)] // A framework culture.
[TestCase("en-JP", true)] // A valid culture string, but not one that's in the framework.
[TestCase("a!", false)] // Not a valid culture string.
[TestCase("<script>alert(123)</script>", false)]
public void ValidateProvidedCulture_Validates_Culture(string culture, bool expectValid)
{
var result = PreviewController.ValidateProvidedCulture(culture);
Assert.AreEqual(expectValid, result);
}

[Test]
public void ValidateProvidedCulture_Validates_Culture_For_All_Framework_Cultures()
{
var cultures = CultureInfo.GetCultures(CultureTypes.AllCultures);
foreach (var culture in cultures)
{
Assert.IsTrue(PreviewController.ValidateProvidedCulture(culture.Name), $"{culture.Name} is not considered a valid culture.");
Assert.IsTrue(PreviewController.ValidateProvidedCulture(culture.Name.ToUpperInvariant()), $"{culture.Name.ToUpperInvariant()} is not considered a valid culture.");
Assert.IsTrue(PreviewController.ValidateProvidedCulture(culture.Name.ToLowerInvariant()), $"{culture.Name.ToLowerInvariant()} is not considered a valid culture.");
}
}
}
Loading