diff --git a/src/internal/converters/eml/eml.go b/src/internal/converters/eml/eml.go index 35c3519e5..29f7bf455 100644 --- a/src/internal/converters/eml/eml.go +++ b/src/internal/converters/eml/eml.go @@ -208,6 +208,15 @@ func getItemAttachment(ctx context.Context, attachment models.Attachmentable) (* With("attachment_id", ptr.Val(attachment.GetId())) } + name := ptr.Val(attachment.GetName()) + if len(name) == 0 { + // Graph as of now does not let us create any attachments + // without a name, but we have run into instances where we have + // see attachments without a name, possibly from old + // data. This is for those cases. + name = "Unnamed" + } + switch it := it.(type) { case *models.Message: cb, err := FromMessageable(ctx, it) @@ -217,7 +226,7 @@ func getItemAttachment(ctx context.Context, attachment models.Attachmentable) (* } return &mail.File{ - Name: ptr.Val(attachment.GetName()), + Name: name, MimeType: "message/rfc822", Data: []byte(cb), }, nil diff --git a/src/internal/converters/eml/eml_test.go b/src/internal/converters/eml/eml_test.go index a1c0fd5db..862842907 100644 --- a/src/internal/converters/eml/eml_test.go +++ b/src/internal/converters/eml/eml_test.go @@ -137,6 +137,11 @@ func (suite *EMLUnitSuite) TestConvert_messageble_to_eml() { } func (suite *EMLUnitSuite) TestConvert_edge_cases() { + bodies := []string{ + testdata.EmailWithAttachments, + testdata.EmailWithinEmail, + } + tests := []struct { name string transform func(models.Messageable) @@ -202,33 +207,35 @@ func (suite *EMLUnitSuite) TestConvert_edge_cases() { }, } - for _, test := range tests { - suite.Run(test.name, func() { - t := suite.T() + for _, b := range bodies { + for _, test := range tests { + suite.Run(test.name, func() { + t := suite.T() - ctx, flush := tester.NewContext(t) - defer flush() + ctx, flush := tester.NewContext(t) + defer flush() - body := []byte(testdata.EmailWithAttachments) + body := []byte(b) - msg, err := api.BytesToMessageable(body) - require.NoError(t, err, "creating message") + msg, err := api.BytesToMessageable(body) + require.NoError(t, err, "creating message") - test.transform(msg) + test.transform(msg) - writer := kjson.NewJsonSerializationWriter() + writer := kjson.NewJsonSerializationWriter() - defer writer.Close() + defer writer.Close() - err = writer.WriteObjectValue("", msg) - require.NoError(t, err, "serializing message") + err = writer.WriteObjectValue("", msg) + require.NoError(t, err, "serializing message") - nbody, err := writer.GetSerializedContent() - require.NoError(t, err, "getting serialized content") + nbody, err := writer.GetSerializedContent() + require.NoError(t, err, "getting serialized content") - _, err = FromJSON(ctx, nbody) - assert.NoError(t, err, "converting to eml") - }) + _, err = FromJSON(ctx, nbody) + assert.NoError(t, err, "converting to eml") + }) + } } } @@ -461,7 +468,7 @@ func (suite *EMLUnitSuite) TestConvert_message_in_messageble_to_eml() { assert.Equal(t, formatAddress(msg.GetFrom().GetEmailAddress()), eml.GetHeader("From")) attachments := eml.Attachments - assert.Equal(t, 1, len(attachments), "attachment count in parent email") + assert.Equal(t, 3, len(attachments), "attachment count in parent email") ieml, err := enmime.ReadEnvelope(strings.NewReader(string(attachments[0].Content))) require.NoError(t, err, "reading created eml") diff --git a/src/internal/converters/eml/testdata/email-within-email.json b/src/internal/converters/eml/testdata/email-within-email.json index 58263ae8d..aed67dc2b 100644 --- a/src/internal/converters/eml/testdata/email-within-email.json +++ b/src/internal/converters/eml/testdata/email-within-email.json @@ -77,6 +77,146 @@ ], "webLink": "https://outlook.office365.com/owa/?AttachmentItemID=AAMkAGJiZmE2NGU4LTQ4YjktNDI1Mi1iMWQzLTQ1MmMxODJkZmQyNABGAAAAAABFdiK7oifWRb4ADuqgSRcnBwBBFDg0JJk7TY1fmsJrh7tNAAAAAAEJAABBFDg0JJk7TY1fmsJrh7tNAAFnbV%2FqAAABEgAQAEUyH0VS3HJBgHDlZdWZl0k%3D&exvsurl=1&viewmodel=ItemAttachment" } + }, + { + "id": "AAMkAGJiZmE2NGU4LTQ4YjktNDI1Mi1iMWQzLTQ1MmMxODJkZmQyNABGAAAAAABFdiK7oifWRb4ADuqgSRcnBwBBFDg0JJk7TY1fmsJrh7tNAAAAAAEJAABBFDg0JJk7TY1fmsJrh7tNAAFnbV-qAAABEgAQAEUyH0VS3HJBgHDlZdWZl02=", + "@odata.type": "#microsoft.graph.itemAttachment", + "item@odata.navigationLink": "https://graph.microsoft.com/v1.0/users('7ceb8e03-bdc5-4509-a136-457526165ec0')/messages('')", + "item@odata.associationLink": "https://graph.microsoft.com/v1.0/users('7ceb8e03-bdc5-4509-a136-457526165ec0')/messages('')/$ref", + "isInline": false, + "lastModifiedDateTime": "2024-02-05T09:33:46Z", + "name": "Purpose of life part 2", + "size": 11840, + "item": { + "id": "", + "@odata.type": "#microsoft.graph.message", + "createdDateTime": "2024-02-05T09:33:24Z", + "lastModifiedDateTime": "2024-02-05T09:33:46Z", + "attachments": [ + { + "id": "AAMkAGJiZmE2NGU4LTQ4YjktNDI1Mi1iMWQzLTQ1MmMxODJkZmQyNABGAAAAAABFdiK7oifWRb4ADuqgSRcnBwBBFDg0JJk7TY1fmsJrh7tNAAAAAAEJAABBFDg0JJk7TY1fmsJrh7tNAAFnbV-qAAACEgAQAEUyH0VS3HJBgHDlZdWZl0kSABAAjBhd4-oQaUS969pTkS-gzA==", + "@odata.type": "#microsoft.graph.fileAttachment", + "@odata.mediaContentType": "text/calendar", + "contentType": "text/calendar", + "isInline": false, + "lastModifiedDateTime": "2024-02-05T09:33:46Z", + "name": "Abidjan.ics", + "size": 573, + "contentBytes": "QkVHSU46VkNBTEVOREFSDQpQUk9ESUQ6LS8vdHp1cmwub3JnLy9OT05TR01MIE9sc29uIDIwMjNkLy9FTg0KVkVSU0lPTjoyLjANCkJFR0lOOlZUSU1FWk9ORQ0KVFpJRDpBZnJpY2EvQWJpZGphbg0KTEFTVC1NT0RJRklFRDoyMDIzMTIyMlQyMzMzNThaDQpUWlVSTDpodHRwczovL3d3dy50enVybC5vcmcvem9uZWluZm8vQWZyaWNhL0FiaWRqYW4NClgtTElDLUxPQ0FUSU9OOkFmcmljYS9BYmlkamFuDQpYLVBST0xFUFRJQy1UWk5BTUU6TE1UDQpCRUdJTjpTVEFOREFSRA0KVFpOQU1FOkdNVA0KVFpPRkZTRVRGUk9NOi0wMDE2MDgNClRaT0ZGU0VUVE86KzAwMDANCkRUU1RBUlQ6MTkxMjAxMDFUMDAwMDAwDQpFTkQ6U1RBTkRBUkQNCkVORDpWVElNRVpPTkUNCkVORDpWQ0FMRU5EQVINCg==" + } + ], + "body": { + "content": "
\r\n