From 63b44b5a92bf0ee0f4840aecc978797950c6ab04 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Sun, 11 Oct 2026 01:38:25 +0100 Subject: [PATCH] Fix PostIt attachment links to match uploaded filenames Apply the shared filename filter before URL encoding, preserve escaped URLs and original link labels, and add regression coverage for accented and spaced filenames. Update the changelog. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 4 ++++ .../PostIt.Tests/BlogAttachmentLinkTests.cs | 21 ++++++++++++------- .../PostIt/ViewModels/Blogs/BlogsViewModel.cs | 6 ++++-- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87d34909d..9c62779c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,10 @@ ### Fixed +* [PostIt] Les liens Markdown des pièces jointes utilisent désormais + le même filtre de nom que l'upload serveur, avant l'encodage URL. + Le libellé conserve le nom original, notamment ses accents. + * [Yavsc.Abstract][Yavsc.Blogs] Encodage des caractères non supportés dans les noms de fichiers téléversés avec `_` au lieu de `#`, pour éviter les fragments d'URL et permettre le téléchargement via les diff --git a/src/PostIt/PostIt.Tests/BlogAttachmentLinkTests.cs b/src/PostIt/PostIt.Tests/BlogAttachmentLinkTests.cs index 946d3d28e..6c88e9412 100644 --- a/src/PostIt/PostIt.Tests/BlogAttachmentLinkTests.cs +++ b/src/PostIt/PostIt.Tests/BlogAttachmentLinkTests.cs @@ -20,8 +20,13 @@ namespace PostIt.Tests; /// public class BlogAttachmentLinkTests { - [Fact] - public async Task Save_with_attachment_appends_absolute_link_derived_from_the_oidc_authority() + [Theory] + [InlineData("note.txt", "note.txt")] + [InlineData("INSC_E03686381_SCHNEIDER_000113378AA__Troisi\u00e8me__.PDF", + "INSC_E03686381_SCHNEIDER_000113378AA__Troisi_232me__.PDF")] + [InlineData("rapport final.pdf", "rapport%20final.pdf")] + public async Task Save_with_attachment_appends_absolute_link_derived_from_the_oidc_authority( + string originalName, string urlName) { const string authority = "https://idp.example.test"; @@ -53,7 +58,7 @@ public class BlogAttachmentLinkTests vm.DraftTitle = "Après"; vm.DraftArticle = "Contenu modifié."; vm.DraftAttachments.Add(new BlogUploadFile( - "note.txt", + originalName, System.Text.Encoding.UTF8.GetBytes("payload test"), "text/plain")); @@ -68,12 +73,14 @@ public class BlogAttachmentLinkTests var sent = Assert.IsType>(linkPut.body); var multipartContent = Assert.IsType(sent()); - var expectedUrl = $"{authority}/files/tester/blogs/42/note.txt"; - var sentStringContent = multipartContent.First() as StringContent ; + var expectedUrl = $"{authority}/files/tester/blogs/42/{urlName}"; + var sentStringContent = Assert.IsType(multipartContent.First()); var sentString = await sentStringContent.ReadAsStringAsync(TestContext.Current.CancellationToken); - Assert.Contains($"- [note.txt]({expectedUrl})", sentString); + using var document = System.Text.Json.JsonDocument.Parse(sentString); + var article = document.RootElement.GetProperty("article").GetString(); + Assert.Contains($"- [{originalName}]({expectedUrl})", article); // The regression shape: a relative, authority-less link. - Assert.DoesNotContain("](/files/", sentString); + Assert.DoesNotContain("](/files/", article); } } diff --git a/src/PostIt/PostIt/ViewModels/Blogs/BlogsViewModel.cs b/src/PostIt/PostIt/ViewModels/Blogs/BlogsViewModel.cs index 882b5d19e..cecd5cb2c 100644 --- a/src/PostIt/PostIt/ViewModels/Blogs/BlogsViewModel.cs +++ b/src/PostIt/PostIt/ViewModels/Blogs/BlogsViewModel.cs @@ -10,6 +10,7 @@ using Microsoft.Extensions.DependencyInjection; using Yavsc.Blogspot; using Yavsc.Api.Client; using Yavsc.Abstract.Files; +using Yavsc.Server.Helpers; using AvaloniaEdit.Document; namespace PostIt.ViewModels; @@ -611,7 +612,8 @@ public partial class BlogsViewModel : ViewModelBase, IActionStatusViewModel foreach (var attachment in attachments) { - var relativePath = $"{EscapePathSegment(ownerSegment)}/blogs/{post.Id}/{EscapePathSegment(attachment.FileName)}"; + var storedName = AbstractFileSystemHelpers.FilterFileName(attachment.FileName); + var relativePath = $"{EscapePathSegment(ownerSegment)}/blogs/{post.Id}/{EscapePathSegment(storedName)}"; var fileUrl = ResolveUserFileUrl(relativePath); var markdownLine = $"- [{attachment.FileName}]({fileUrl})"; @@ -636,7 +638,7 @@ public partial class BlogsViewModel : ViewModelBase, IActionStatusViewModel if (!string.IsNullOrWhiteSpace(authority) && Uri.TryCreate(authority, UriKind.Absolute, out var baseUri)) { - return FileServerUrlHelpers.GetUserFilesUri(baseUri, relativePath).ToString(); + return FileServerUrlHelpers.GetUserFilesUri(baseUri, relativePath).AbsoluteUri; } return $"{Yavsc.Constants.UserFilesPath}/{relativePath}";