diff --git a/src/DotNetApiDiff/Reporting/HtmlFormatterScriban.cs b/src/DotNetApiDiff/Reporting/HtmlFormatterScriban.cs index e53e9d4..c9c932d 100644 --- a/src/DotNetApiDiff/Reporting/HtmlFormatterScriban.cs +++ b/src/DotNetApiDiff/Reporting/HtmlFormatterScriban.cs @@ -64,6 +64,21 @@ public string Format(ComparisonResult result) return _mainTemplate.Render(context); } + private static string HtmlEscape(string? input) + { + if (string.IsNullOrEmpty(input)) + { + return input ?? string.Empty; + } + + return input + .Replace("&", "&") + .Replace("<", "<") + .Replace(">", ">") + .Replace("\"", """) + .Replace("'", "'"); + } + private object PrepareConfigData(ComparisonConfiguration config) { var namespaceMappings = config?.Mappings?.NamespaceMappings ?? new Dictionary>(); @@ -231,8 +246,8 @@ private object[] GroupChanges(List changes) description = c.Description, is_breaking_change = c.IsBreakingChange, has_signatures = !string.IsNullOrEmpty(c.OldSignature) || !string.IsNullOrEmpty(c.NewSignature), - old_signature = c.OldSignature, - new_signature = c.NewSignature, + old_signature = HtmlEscape(c.OldSignature), + new_signature = HtmlEscape(c.NewSignature), details_id = $"details-{Guid.NewGuid():N}" }).ToArray() }).ToArray(); diff --git a/tests/DotNetApiDiff.Tests/Reporting/HtmlFormatterScribanTests.cs b/tests/DotNetApiDiff.Tests/Reporting/HtmlFormatterScribanTests.cs index 1ec4dfb..d9907b7 100644 --- a/tests/DotNetApiDiff.Tests/Reporting/HtmlFormatterScribanTests.cs +++ b/tests/DotNetApiDiff.Tests/Reporting/HtmlFormatterScribanTests.cs @@ -181,6 +181,43 @@ public void Format_WithSignatures_IncludesSignatureDetails() Assert.Contains("public void ChangedMethod(string param)", report); } + [Fact] + public void Format_WithGenericTypeSignatures_ProperlyEscapesHtml() + { + // Arrange + var result = new ComparisonResult + { + OldAssemblyPath = "source.dll", + NewAssemblyPath = "target.dll", + ComparisonTimestamp = new DateTime(2023, 1, 1, 12, 0, 0, DateTimeKind.Utc), + Configuration = CreateDefaultConfiguration(), + Differences = new List + { + new ApiDifference + { + ChangeType = ChangeType.Added, + ElementType = ApiElementType.Method, + ElementName = "Execute", + Description = "Added method with generic parameters", + NewSignature = "public ValkeyResult Execute(string command, ICollection args)", + Severity = SeverityLevel.Info + } + } + }; + + // Act + var report = _formatter.Format(result); + + // Assert + Assert.NotNull(report); + Assert.Contains("Added Items", report); + Assert.Contains("Execute", report); + // Verify that generic type parameters are properly HTML-escaped + Assert.Contains("ICollection<object>", report); + // Verify that unescaped angle brackets are not present + Assert.DoesNotContain("ICollection", report); + } + private static ComparisonConfiguration CreateDefaultConfiguration() { return new ComparisonConfiguration