From f3c4906f88411012df26d23ec45130a45729e237 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20Kaan=20K=C3=B6se?= Date: Thu, 5 Sep 2024 17:23:15 +0200 Subject: [PATCH] Fixing Outlook attachment issues. --- .../Extensions/OutlookIntegratorExtensions.cs | 40 ------------- Wino.Core/Misc/OutlookFileAttachment.cs | 25 ++++++++ Wino.Core/Synchronizers/BaseSynchronizer.cs | 2 - .../Synchronizers/OutlookSynchronizer.cs | 60 ++++++++++++++++++- 4 files changed, 82 insertions(+), 45 deletions(-) create mode 100644 Wino.Core/Misc/OutlookFileAttachment.cs diff --git a/Wino.Core/Extensions/OutlookIntegratorExtensions.cs b/Wino.Core/Extensions/OutlookIntegratorExtensions.cs index 6d02f20f..7cb73203 100644 --- a/Wino.Core/Extensions/OutlookIntegratorExtensions.cs +++ b/Wino.Core/Extensions/OutlookIntegratorExtensions.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using Microsoft.Graph.Models; using MimeKit; @@ -98,45 +97,6 @@ namespace Wino.Core.Extensions message.InternetMessageHeaders = GetHeaderList(mime); } - foreach (var part in mime.BodyParts) - { - if (part.IsAttachment) - { - // File attachment. - - using var memory = new MemoryStream(); - ((MimePart)part).Content.DecodeTo(memory); - - var bytes = memory.ToArray(); - - var fileAttachment = new FileAttachment() - { - ContentId = part.ContentId, - Name = part.ContentDisposition?.FileName ?? part.ContentType.Name, - ContentBytes = bytes, - }; - - message.Attachments.Add(fileAttachment); - } - else if (part.ContentDisposition != null && part.ContentDisposition.Disposition == "inline") - { - // Inline attachment. - - using var memory = new MemoryStream(); - ((MimePart)part).Content.DecodeTo(memory); - - var bytes = memory.ToArray(); - var inlineAttachment = new FileAttachment() - { - IsInline = true, - ContentId = part.ContentId, - Name = part.ContentDisposition?.FileName ?? part.ContentType.Name, - ContentBytes = bytes - }; - - message.Attachments.Add(inlineAttachment); - } - } return message; } diff --git a/Wino.Core/Misc/OutlookFileAttachment.cs b/Wino.Core/Misc/OutlookFileAttachment.cs new file mode 100644 index 00000000..7c707920 --- /dev/null +++ b/Wino.Core/Misc/OutlookFileAttachment.cs @@ -0,0 +1,25 @@ +using System.Text.Json.Serialization; + +namespace Wino.Core.Misc +{ + public class OutlookFileAttachment + { + [JsonPropertyName("@odata.type")] + public string OdataType { get; } = "#microsoft.graph.fileAttachment"; + + [JsonPropertyName("name")] + public string FileName { get; set; } + + [JsonPropertyName("contentBytes")] + public string Base64EncodedContentBytes { get; set; } + + [JsonPropertyName("contentType")] + public string ContentType { get; set; } + + [JsonPropertyName("contentId")] + public string ContentId { get; set; } + + [JsonPropertyName("isInline")] + public bool IsInline { get; set; } + } +} diff --git a/Wino.Core/Synchronizers/BaseSynchronizer.cs b/Wino.Core/Synchronizers/BaseSynchronizer.cs index b0f39765..ec7bf203 100644 --- a/Wino.Core/Synchronizers/BaseSynchronizer.cs +++ b/Wino.Core/Synchronizers/BaseSynchronizer.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Net.Http; using System.Threading; @@ -236,7 +235,6 @@ namespace Wino.Core.Synchronizers catch (Exception ex) { Logger.Error(ex, "Synchronization failed for {Name}", Account.Name); - Debugger.Break(); throw; } diff --git a/Wino.Core/Synchronizers/OutlookSynchronizer.cs b/Wino.Core/Synchronizers/OutlookSynchronizer.cs index 8e9c83d8..fb40c4fa 100644 --- a/Wino.Core/Synchronizers/OutlookSynchronizer.cs +++ b/Wino.Core/Synchronizers/OutlookSynchronizer.cs @@ -31,6 +31,7 @@ using Wino.Core.Domain.Models.Synchronization; using Wino.Core.Extensions; using Wino.Core.Http; using Wino.Core.Integration.Processors; +using Wino.Core.Misc; using Wino.Core.Requests; namespace Wino.Core.Synchronizers @@ -689,6 +690,11 @@ namespace Wino.Core.Synchronizers var outlookMessage = mimeMessage.AsOutlookMessage(false); + // Create attachment requests. + // TODO: We need to support large file attachments with sessioned upload at some point. + + var attachmentRequestList = CreateAttachmentUploadBundles(mimeMessage, mailCopyId, request).ToList(); + // Update draft. var patchDraftRequest = _graphClient.Me.Messages[mailCopyId].ToPatchRequestInformation(outlookMessage); @@ -696,12 +702,60 @@ namespace Wino.Core.Synchronizers // Send draft. - var sendDraftRequest = PreparePostRequestInformation(_graphClient.Me.Messages[mailCopyId].Send.ToPostRequestInformation()); - var sendDraftRequestBundle = new HttpRequestBundle(sendDraftRequest, request); - return [patchDraftRequestBundle, sendDraftRequestBundle]; + return [.. attachmentRequestList, patchDraftRequestBundle, sendDraftRequestBundle]; + } + + private IEnumerable> CreateAttachmentUploadBundles(MimeMessage mime, string mailCopyId, IRequestBase sourceRequest) + { + var allAttachments = new List(); + + foreach (var part in mime.BodyParts) + { + var isAttachmentOrInline = part.IsAttachment ? true : part.ContentDisposition?.Disposition == "inline"; + + if (!isAttachmentOrInline) continue; + + using var memory = new MemoryStream(); + ((MimePart)part).Content.DecodeTo(memory); + + var base64String = Convert.ToBase64String(memory.ToArray()); + + var attachment = new OutlookFileAttachment() + { + Base64EncodedContentBytes = base64String, + FileName = part.ContentDisposition?.FileName ?? part.ContentType.Name, + ContentId = part.ContentId, + ContentType = part.ContentType.MimeType, + IsInline = part.ContentDisposition?.Disposition == "inline" + }; + + allAttachments.Add(attachment); + } + + RequestInformation PrepareUploadAttachmentRequest(RequestInformation requestInformation, OutlookFileAttachment outlookFileAttachment) + { + requestInformation.Headers.Clear(); + + string contentJson = JsonSerializer.Serialize(outlookFileAttachment); + + requestInformation.Content = new MemoryStream(Encoding.UTF8.GetBytes(contentJson)); + requestInformation.HttpMethod = Method.POST; + requestInformation.Headers.Add("Content-Type", "application/json"); + + return requestInformation; + } + + // Prepare attachment upload requests. + return allAttachments.Select(outlookAttachment => + { + var emptyPostRequest = _graphClient.Me.Messages[mailCopyId].Attachments.ToPostRequestInformation(new Attachment()); + var modifiedAttachmentUploadRequest = PrepareUploadAttachmentRequest(emptyPostRequest, outlookAttachment); + + return new HttpRequestBundle(modifiedAttachmentUploadRequest, sourceRequest); + }); } public override IEnumerable> Archive(BatchArchiveRequest request)