Add FormattedString type (#6339)

This is basically a lightweight marker type saying "this string contains markup". Intended to avoid injection accidents if people don't realize they should escape stuff.
This commit is contained in:
Pieter-Jan Briers
2025-12-15 19:36:16 +01:00
committed by GitHub
parent 53e1222b6b
commit d161c3b3b8
6 changed files with 330 additions and 1 deletions
@@ -0,0 +1,54 @@
using NUnit.Framework;
using Robust.Shared.RichText;
namespace Robust.UnitTesting.Shared.RichText;
[Parallelizable(ParallelScope.All)]
[TestOf(typeof(FormattedString))]
[TestFixture]
internal sealed class FormattedStringTest
{
/// <summary>
/// Test that permissive parsing properly normalizes & passes through markup, as appropriate.
/// </summary>
[Test]
[TestCase("", ExpectedResult = "")]
[TestCase("foobar", ExpectedResult = "foobar")]
[TestCase("[whaaaaaa", ExpectedResult = "\\[whaaaaaa")]
[TestCase("\\[whaaaaaa", ExpectedResult = "\\[whaaaaaa")]
[TestCase("[whaaaaaa]wow[/whaaaaaa]", ExpectedResult = "[whaaaaaa]wow[/whaaaaaa]")]
[TestCase("[whaaaaaa]\\[womp[/whaaaaaa]", ExpectedResult = "[whaaaaaa]\\[womp[/whaaaaaa]")]
public static string TestPermissiveNormalize(string input)
{
return FormattedString.FromMarkupPermissive(input).Markup;
}
[Test]
[TestCase("")]
[TestCase("real")]
[TestCase("[whaaaaaa]wow[/whaaaaaa]")]
public static void TestStrictParse(string input)
{
var str = FormattedString.FromMarkup(input);
Assert.That(str.Markup, Is.EqualTo(input));
}
[Test]
[TestCase("", ExpectedResult = "")]
[TestCase("real", ExpectedResult = "real")]
[TestCase("[real", ExpectedResult = "\\[real")]
[TestCase("\\", ExpectedResult = @"\\")]
public static string TestFromPlainText(string input)
{
return FormattedString.FromPlainText(input).Markup;
}
[Test]
[TestCase("[whaaaaaawow")]
[TestCase("[whaaaaaawow val=\"]")]
public static void TestStrictThrows(string input)
{
Assert.That(() => FormattedString.FromMarkup(input), Throws.ArgumentException);
}
}
@@ -0,0 +1,76 @@
using System.IO;
using NetSerializer;
using NUnit.Framework;
using Robust.Shared.RichText;
using Robust.Shared.Serialization;
namespace Robust.UnitTesting.Shared.Serialization;
[Parallelizable(ParallelScope.All)]
[TestFixture, TestOf(typeof(NetFormattedStringSerializer))]
internal sealed class NetSerializerFormattedStringTest
{
[Test]
[TestCase("")]
[TestCase("real")]
[TestCase("[i]heck[/i]")]
public void TestBasic(string markup)
{
var serializer = MakeSerializer();
var str = FormattedString.FromMarkup(markup);
var stream = new MemoryStream();
serializer.Serialize(stream, str);
stream.Position = 0;
var deserialized = (FormattedString) serializer.Deserialize(stream);
Assert.That(deserialized, NUnit.Framework.Is.EqualTo(str));
}
/// <summary>
/// Test that the on-wire representation of a <see cref="FormattedString"/> is the same as a regular string.
/// This is to ensure <see cref="TestInvalid"/> is a valid test.
/// </summary>
[Test]
[TestCase("")]
[TestCase("real")]
[TestCase("[i]heck[/i]")]
public void TestEqualToString(string markup)
{
var serializer = MakeSerializer();
var stream = new MemoryStream();
serializer.SerializeDirect(stream, markup);
stream.Position = 0;
serializer.DeserializeDirect(stream, out FormattedString str);
Assert.That(str.Markup, Is.EqualTo(markup));
}
/// <summary>
/// Test that deserialization fails if a malicious client sends broken markup.
/// </summary>
[Test]
public void TestInvalid()
{
var serializer = MakeSerializer();
var stream = new MemoryStream();
serializer.SerializeDirect(stream, "[wahoooo");
stream.Position = 0;
Assert.That(() => serializer.DeserializeDirect(stream, out FormattedString _), Throws.Exception);
}
private static Serializer MakeSerializer()
{
return new Serializer([typeof(FormattedString)],
new Settings
{
CustomTypeSerializers = [new NetFormattedStringSerializer()]
});
}
}