diff --git a/DiscordChatExporter.Core/Exporting/ExportContext.cs b/DiscordChatExporter.Core/Exporting/ExportContext.cs index 3c7f5785..ae640a8a 100644 --- a/DiscordChatExporter.Core/Exporting/ExportContext.cs +++ b/DiscordChatExporter.Core/Exporting/ExportContext.cs @@ -120,6 +120,12 @@ internal class ExportContext(DiscordClient discord, ExportRequest request) public Color? TryGetUserColor(Snowflake id) => GetUserRoles(id).Where(r => r.Color is not null).Select(r => r.Color).FirstOrDefault(); + public static string? EnsureSafeUrl(string? url) => + Uri.TryCreate(url, UriKind.Absolute, out var uri) + && (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps) + ? url + : null; + public async ValueTask ResolveAssetUrlAsync( string url, CancellationToken cancellationToken = default diff --git a/DiscordChatExporter.Core/Exporting/HtmlMarkdownVisitor.cs b/DiscordChatExporter.Core/Exporting/HtmlMarkdownVisitor.cs index 0104a6d8..e40dae7d 100644 --- a/DiscordChatExporter.Core/Exporting/HtmlMarkdownVisitor.cs +++ b/DiscordChatExporter.Core/Exporting/HtmlMarkdownVisitor.cs @@ -188,12 +188,19 @@ internal partial class HtmlMarkdownVisitor( .Groups[1] .Value; + var safeUrl = ExportContext.EnsureSafeUrl(link.Url); + if (string.IsNullOrWhiteSpace(safeUrl)) + { + await VisitAsync(link.Children, cancellationToken); + return; + } + buffer.Append( !string.IsNullOrWhiteSpace(linkedMessageId) // lang=html - ? $"""""" + ? $"""""" // lang=html - : $"""""" + : $"""""" ); await VisitAsync(link.Children, cancellationToken); diff --git a/DiscordChatExporter.Core/Exporting/MessageGroupTemplate.cshtml b/DiscordChatExporter.Core/Exporting/MessageGroupTemplate.cshtml index b3c06837..9f3f3ea0 100644 --- a/DiscordChatExporter.Core/Exporting/MessageGroupTemplate.cshtml +++ b/DiscordChatExporter.Core/Exporting/MessageGroupTemplate.cshtml @@ -24,6 +24,9 @@ string FormatDate(DateTimeOffset instant, string format = "g") => Context.FormatDate(instant, format); + string? EnsureSafeUrl(string? url) => + ExportContext.EnsureSafeUrl(url); + async ValueTask FormatMarkdownAsync(string markdown) => Context.Request.ShouldFormatMarkdown ? Html.Raw(await HtmlMarkdownVisitor.FormatAsync(Context, markdown, true, CancellationToken)) @@ -484,9 +487,10 @@ @if (!string.IsNullOrWhiteSpace(embed.Author.Name)) { - if (!string.IsNullOrWhiteSpace(embed.Author.Url)) + var authorUrl = EnsureSafeUrl(embed.Author.Url); + if (!string.IsNullOrWhiteSpace(authorUrl)) { - +
@embed.Author.Name
} @@ -502,9 +506,12 @@ @if (!string.IsNullOrWhiteSpace(embed.Title)) {
- @if (!string.IsNullOrWhiteSpace(embed.Url)) + @{ + var titleUrl = EnsureSafeUrl(embed.Url); + } + @if (!string.IsNullOrWhiteSpace(titleUrl)) { - +
@(await FormatEmbedMarkdownAsync(embed.Title))
} @@ -604,9 +611,10 @@ @if (!string.IsNullOrWhiteSpace(embed.Author.Name)) { - if (!string.IsNullOrWhiteSpace(embed.Author.Url)) + var authorUrl = EnsureSafeUrl(embed.Author.Url); + if (!string.IsNullOrWhiteSpace(authorUrl)) { - +
@embed.Author.Name
} @@ -622,9 +630,12 @@ @if (!string.IsNullOrWhiteSpace(embed.Title)) {
- @if (!string.IsNullOrWhiteSpace(embed.Url)) + @{ + var titleUrl = EnsureSafeUrl(embed.Url); + } + @if (!string.IsNullOrWhiteSpace(titleUrl)) { - +
@(await FormatEmbedMarkdownAsync(embed.Title))
}