diff --git a/DiscordChatExporter.Core/Exporting/ExportContext.cs b/DiscordChatExporter.Core/Exporting/ExportContext.cs index 9aff9218..960048d8 100644 --- a/DiscordChatExporter.Core/Exporting/ExportContext.cs +++ b/DiscordChatExporter.Core/Exporting/ExportContext.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using DiscordChatExporter.Core.Discord; using DiscordChatExporter.Core.Discord.Data; +using DiscordChatExporter.Core.Utils; using DiscordChatExporter.Core.Utils.Extensions; namespace DiscordChatExporter.Core.Exporting; @@ -118,7 +119,7 @@ internal class ExportContext(DiscordClient discord, ExportRequest request) var relativeFilePath = Path.GetRelativePath(Request.OutputDirPath, filePath); // Prefer the relative path so that the export package can be copied around without breaking references. - // However, if the assets directory lies outside of the export directory, use the absolute path instead. + // However, if the assets directory lies outside the export directory, use the absolute path instead. var shouldUseAbsoluteFilePath = relativeFilePath.StartsWith( ".." + Path.DirectorySeparatorChar, @@ -133,14 +134,7 @@ internal class ExportContext(DiscordClient discord, ExportRequest request) // For HTML, the path needs to be properly formatted if (Request.Format is ExportFormat.HtmlDark or ExportFormat.HtmlLight) - { - // Format the path into a valid file URI - var href = new Uri(new Uri("file:///"), optimalFilePath).ToString(); - - // File schema does not support relative paths, so strip it if that's the case - // https://github.com/Tyrrrz/DiscordChatExporter/issues/1155 - return shouldUseAbsoluteFilePath ? href : href[8..]; - } + return Url.EncodeFilePath(optimalFilePath); return optimalFilePath; } diff --git a/DiscordChatExporter.Core/Utils/Url.cs b/DiscordChatExporter.Core/Utils/Url.cs new file mode 100644 index 00000000..9ccbd30d --- /dev/null +++ b/DiscordChatExporter.Core/Utils/Url.cs @@ -0,0 +1,43 @@ +using System; +using System.Text; + +namespace DiscordChatExporter.Core.Utils; + +public static class Url +{ + public static string EncodeFilePath(string filePath) + { + var buffer = new StringBuilder(); + var position = 0; + + while (true) + { + if (position >= filePath.Length) + break; + + var separatorIndex = filePath.IndexOfAny([':', '/', '\\'], position); + if (separatorIndex < 0) + { + buffer.Append(Uri.EscapeDataString(filePath[position..])); + break; + } + + // Append the segment + buffer.Append(Uri.EscapeDataString(filePath[position..separatorIndex])); + + // Append the separator + buffer.Append( + filePath[separatorIndex] switch + { + // Normalize slashes + '\\' => '/', + var c => c, + } + ); + + position = separatorIndex + 1; + } + + return buffer.ToString(); + } +}