feat: parse inline attachments for incoming mail (#7136)
- Some email clients send inline attachments using the `multipart/related` Mime-Type and enmime collects these in the `Envelope.OtherParts` list; until now only Envelope.Attachments and Envelope.Inline were considered while parsing incoming mail. - As some email clients send attachments without filename, especially in the multipart/related case, this PR implements `constructFilename`, which guesses the filename based on the ContentType. How the issue was disovered: I implemented an otherwise unrelated application written in go that parses emails with enmime just like Forgejo does. I noticed that in a few occasions that system would fail to detect all attachments. Investigating this issue led me to realize the above described issue. After implementing a fix for that application, I looked through the Forgejo email parsing code and discovered the same problem. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7136 Reviewed-by: Gusted <gusted@noreply.codeberg.org> Co-authored-by: christopher-besch <mail@chris-besch.com> Co-committed-by: christopher-besch <mail@chris-besch.com>
This commit is contained in:
		
					parent
					
						
							
								b2d01fdde6
							
						
					
				
			
			
				commit
				
					
						3de904c963
					
				
			
		
					 2 changed files with 224 additions and 3 deletions
				
			
		| 
						 | 
				
			
			@ -7,8 +7,10 @@ import (
 | 
			
		|||
	"context"
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"mime"
 | 
			
		||||
	net_mail "net/mail"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"slices"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -374,25 +376,56 @@ type Attachment struct {
 | 
			
		|||
// getContentFromMailReader grabs the plain content and the attachments from the mail.
 | 
			
		||||
// A potential reply/signature gets stripped from the content.
 | 
			
		||||
func getContentFromMailReader(env *enmime.Envelope) *MailContent {
 | 
			
		||||
	// get attachments
 | 
			
		||||
	attachments := make([]*Attachment, 0, len(env.Attachments))
 | 
			
		||||
	for _, attachment := range env.Attachments {
 | 
			
		||||
		attachments = append(attachments, &Attachment{
 | 
			
		||||
			Name:    attachment.FileName,
 | 
			
		||||
			Name:    constructFilename(attachment),
 | 
			
		||||
			Content: attachment.Content,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	// get inlines
 | 
			
		||||
	inlineAttachments := make([]*Attachment, 0, len(env.Inlines))
 | 
			
		||||
	for _, inline := range env.Inlines {
 | 
			
		||||
		if inline.FileName != "" && inline.ContentType != "text/plain" {
 | 
			
		||||
			inlineAttachments = append(inlineAttachments, &Attachment{
 | 
			
		||||
				Name:    inline.FileName,
 | 
			
		||||
				Name:    constructFilename(inline),
 | 
			
		||||
				Content: inline.Content,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	// get other parts (mostly multipart/related files, these are for example embedded images in an html mail)
 | 
			
		||||
	otherParts := make([]*Attachment, 0, len(env.Inlines))
 | 
			
		||||
	for _, otherPart := range env.OtherParts {
 | 
			
		||||
		otherParts = append(otherParts, &Attachment{
 | 
			
		||||
			Name:    constructFilename(otherPart),
 | 
			
		||||
			Content: otherPart.Content,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &MailContent{
 | 
			
		||||
		Content:     reply.FromText(env.Text),
 | 
			
		||||
		Attachments: append(attachments, inlineAttachments...),
 | 
			
		||||
		Attachments: slices.Concat(attachments, inlineAttachments, otherParts),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// constructFilename interprets the mime part as an (inline) attachment and returns its filename
 | 
			
		||||
// If no filename is given it guesses a sensible filename for it based on the filetype.
 | 
			
		||||
func constructFilename(part *enmime.Part) string {
 | 
			
		||||
	if strings.TrimSpace(part.FileName) != "" {
 | 
			
		||||
		return part.FileName
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	filenameWOExtension := "unnamed_file"
 | 
			
		||||
	if strings.TrimSpace(part.ContentID) != "" {
 | 
			
		||||
		filenameWOExtension = part.ContentID
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fileExtension := ".unknown"
 | 
			
		||||
	mimeExtensions, err := mime.ExtensionsByType(part.ContentType)
 | 
			
		||||
	if err == nil && len(mimeExtensions) != 0 {
 | 
			
		||||
		// just use the first one we find
 | 
			
		||||
		fileExtension = mimeExtensions[0]
 | 
			
		||||
	}
 | 
			
		||||
	return filenameWOExtension + fileExtension
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,6 +4,7 @@
 | 
			
		|||
package incoming
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/base64"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -194,4 +195,191 @@ func TestGetContentFromMailReader(t *testing.T) {
 | 
			
		|||
	require.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, "mail content without signature", content.Content)
 | 
			
		||||
	assert.Empty(t, content.Attachments)
 | 
			
		||||
 | 
			
		||||
	// Some versions of Outlook send inline attachments like this, inside a multipart/related part.
 | 
			
		||||
	// the attached image is from: https://openmoji.org/library/emoji-1F684
 | 
			
		||||
	mailString = "Content-Type: multipart/related; boundary=\"=_related boundary=\"\r\n" +
 | 
			
		||||
		"\r\n" +
 | 
			
		||||
		"This text is for clients unable to decode multipart/related with multipart/alternative.\r\n" +
 | 
			
		||||
		"\r\n" +
 | 
			
		||||
		"--=_related boundary=\r\n" +
 | 
			
		||||
		"Content-Type: multipart/alternative; boundary=\"=_alternative boundary=\"\r\n" +
 | 
			
		||||
		"\r\n" +
 | 
			
		||||
		"\r\n" +
 | 
			
		||||
		"\r\n" +
 | 
			
		||||
		"--=_alternative boundary=\r\n" +
 | 
			
		||||
		"Content-Type: text/plain\r\n" +
 | 
			
		||||
		"\r\n" +
 | 
			
		||||
		"This is the plaintext.\r\n" +
 | 
			
		||||
		"\r\n" +
 | 
			
		||||
		"--=_alternative boundary=\r\n" +
 | 
			
		||||
		"Content-Type: text/html\r\n" +
 | 
			
		||||
		"\r\n" +
 | 
			
		||||
		"<p>This is a mail with multipart/related. Here is an image sent with a filename.</p>\r\n" +
 | 
			
		||||
		"<img src=cid:_1_2845>\r\n" +
 | 
			
		||||
		"\r\n" +
 | 
			
		||||
		"--=_alternative boundary=--\r\n" +
 | 
			
		||||
		"\r\n" +
 | 
			
		||||
		"--=_related boundary=\r\n" +
 | 
			
		||||
		"Content-Transfer-Encoding: base64\r\n" +
 | 
			
		||||
		"Content-Type: image/png;\r\n" +
 | 
			
		||||
		"	name=\"image001.png\"\r\n" +
 | 
			
		||||
		"Content-ID: <_1_2845>\r\n" +
 | 
			
		||||
		"\r\n" +
 | 
			
		||||
		"iVBORw0KGgoAAAANSUhEUgAAAEAAAAAiCAYAAADvVd+PAAAFLUlEQVRo3t2ZX0iTXxjHP3u35qvT\r\n" +
 | 
			
		||||
		"6ZzhzKFuzPQq9WKQZS6FvLQf3Wh30ViBQXnViC5+LVKEiC6DjMQgCCy6NChoIKwghhcR1bJ5s5Ei\r\n" +
 | 
			
		||||
		"LmtNs/05XYT7Vercaps/94Xn4uU95znvOc/3+XdehRBCsM1YXl7G6/Xi8Xh49uwZMzMzhEIhFhcX\r\n" +
 | 
			
		||||
		"+fbtW87WbW1tRbVdmxZC8PTpU8bGxrh//z5fv37dcJxGo2HXrl1ZWVOhUPzybDAYUOSbAYlEgjt3\r\n" +
 | 
			
		||||
		"7nD58mVmZ2cBkCSJ1tZWDhw4wP79+2lpaUGv16PX61Gr1Tm3RN7w/Plz0d7eLgABCKPRKJxOp3j/\r\n" +
 | 
			
		||||
		"/r3YLuTlAD5+/ChOnDiR3HhdXZ24e/euiMfjYruRcxe4evUqV65c4fPnz6hUKrq7uzl06NA6v157\r\n" +
 | 
			
		||||
		"19bWlrbueDzOq1evmJ6eJhQKZRww9+3blzsXWFpaEqdOnUpaPV2ZmJjYUveLFy+Ew+EQFRUVGev/\r\n" +
 | 
			
		||||
		"WTQaTW4Y8OjRIxwOB4FAAEmS0Gq1lJWVpZwTjUaZm5vDZrPhdrs3HOP3+3E6nTx48IC1zy4uLqas\r\n" +
 | 
			
		||||
		"rAy1Wr0uym8FnU6X3TT46dMnzp8/z82bNwHQarU0NTVRUlKScl44HMbn8wFQU1Oz7n0sFuP69etc\r\n" +
 | 
			
		||||
		"unSJ5eVllEole/bswWAwbKk7FSRJyl4a/NnqSqWS+vp6jEZjSqskEglmZ2cJBoMIIbBYLExNTWEw\r\n" +
 | 
			
		||||
		"GJJjvF4vDoeD6elpAKqrqzGbzVlJj5Ik/T0D/tTqS0tL+Hw+VlZWUKlUDAwMMDQ0RGlpKQArKyu4\r\n" +
 | 
			
		||||
		"XC6uXbtGLBZDlmUaGxuprKzMajGmyrfVY7EYfr+fDx8+ANDS0sLo6ChWqzU5xu12c/r0aXw+HwqF\r\n" +
 | 
			
		||||
		"gtraWkwmE0qlMutZSpVPq8/NzeH3+4lGo5SUlOByuRgcHESl+u8zLly4wMjICAClpaU0NTUlWZEL\r\n" +
 | 
			
		||||
		"ZBwDfo/wDQ0NKa0ej8dZWFggEAgQiUQA6Onp4caNG5jN5l/GTk1N0dnZmTab8sqA+fl5jh07hsfj\r\n" +
 | 
			
		||||
		"AUCWZXbv3g1AIBBYR/NoNEokEuHLly8kEgkATCYTQ0NDHD9+fFOGrKW2jfRmGxqNJr1CaHJyUuj1\r\n" +
 | 
			
		||||
		"+j8qNiRJEp2dneL27dtidXU15TrhcFhYLJa/Km4ykeLi4tSF0O++Xl9fz5EjR9Dr9SlPtry8nKqq\r\n" +
 | 
			
		||||
		"KsxmM1arFa1Wm7ZVQqEQDx8+5N27dznvTG022+YMmJycFEajUQBClmUxMjIiYrGYKDSwVQ3f3t4u\r\n" +
 | 
			
		||||
		"3rx5k1LJy5cvhd1uF83NzaKoqChvFP5b6e3t/fUAMrX64uKiOHnypFAoFDtm0z9Ll14nVACRSIQz\r\n" +
 | 
			
		||||
		"Z84wPj4OwMGDBxkbG6OxsXFT/7l37x6Dg4PMz89TJEn0VVXwT2U5dUVq1DlOXdmCsrwcVTQapaen\r\n" +
 | 
			
		||||
		"B4/HgyzLDA8Pc+7cOSRJ2nTixYsXGR4eBsBaWsK/xmrqitTsSIyOjgpAmEwm8fbt27QCx969e3ck\r\n" +
 | 
			
		||||
		"5TdyAfr6+gQgxsfH046ct27dEjqdbscfQG9vr1CtNRiZVMR2ux273U4hQOru7gbA5XLh9Xr5H/wn\r\n" +
 | 
			
		||||
		"yS9WV1dFR0dHQfh0ptLR0fGjDgiHw8LpdIqGhoYdm9P/RCwWS3qXok+ePOHs2bO8fv06eZ3c39+X\r\n" +
 | 
			
		||||
		"7AZ3MlK2wzMzMwwMDPD48WMAamtr6e/vo7m5uWBCwKYMWFhYoK2tjWAwiEaj4ejRXmw2W8oCqaAO\r\n" +
 | 
			
		||||
		"wO1209XVhSzLHD5s+3F5UGAwGo2bt8OhUEjU1NQUdBDc8s9QMBhkYmIieVVVaLDZbHwHmmIQk3rD\r\n" +
 | 
			
		||||
		"exgAAAAASUVORK5CYII=\r\n" +
 | 
			
		||||
		"\r\n" +
 | 
			
		||||
		"--=_related boundary=--\r\n" +
 | 
			
		||||
		"\r\n"
 | 
			
		||||
 | 
			
		||||
	env, err = enmime.ReadEnvelope(strings.NewReader(mailString))
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	content = getContentFromMailReader(env)
 | 
			
		||||
	assert.Equal(t, "This is the plaintext.", content.Content)
 | 
			
		||||
	assert.Len(t, content.Attachments, 1)
 | 
			
		||||
	assert.Equal(t, "image001.png", content.Attachments[0].Name)
 | 
			
		||||
	expectedAttachment, err := base64.StdEncoding.DecodeString(
 | 
			
		||||
		"iVBORw0KGgoAAAANSUhEUgAAAEAAAAAiCAYAAADvVd+PAAAFLUlEQVRo3t2ZX0iTXxjHP3u35qvT\r\n" +
 | 
			
		||||
			"6ZzhzKFuzPQq9WKQZS6FvLQf3Wh30ViBQXnViC5+LVKEiC6DjMQgCCy6NChoIKwghhcR1bJ5s5Ei\r\n" +
 | 
			
		||||
			"LmtNs/05XYT7Vercaps/94Xn4uU95znvOc/3+XdehRBCsM1YXl7G6/Xi8Xh49uwZMzMzhEIhFhcX\r\n" +
 | 
			
		||||
			"+fbtW87WbW1tRbVdmxZC8PTpU8bGxrh//z5fv37dcJxGo2HXrl1ZWVOhUPzybDAYUOSbAYlEgjt3\r\n" +
 | 
			
		||||
			"7nD58mVmZ2cBkCSJ1tZWDhw4wP79+2lpaUGv16PX61Gr1Tm3RN7w/Plz0d7eLgABCKPRKJxOp3j/\r\n" +
 | 
			
		||||
			"/r3YLuTlAD5+/ChOnDiR3HhdXZ24e/euiMfjYruRcxe4evUqV65c4fPnz6hUKrq7uzl06NA6v157\r\n" +
 | 
			
		||||
			"19bWlrbueDzOq1evmJ6eJhQKZRww9+3blzsXWFpaEqdOnUpaPV2ZmJjYUveLFy+Ew+EQFRUVGev/\r\n" +
 | 
			
		||||
			"WTQaTW4Y8OjRIxwOB4FAAEmS0Gq1lJWVpZwTjUaZm5vDZrPhdrs3HOP3+3E6nTx48IC1zy4uLqas\r\n" +
 | 
			
		||||
			"rAy1Wr0uym8FnU6X3TT46dMnzp8/z82bNwHQarU0NTVRUlKScl44HMbn8wFQU1Oz7n0sFuP69etc\r\n" +
 | 
			
		||||
			"unSJ5eVllEole/bswWAwbKk7FSRJyl4a/NnqSqWS+vp6jEZjSqskEglmZ2cJBoMIIbBYLExNTWEw\r\n" +
 | 
			
		||||
			"GJJjvF4vDoeD6elpAKqrqzGbzVlJj5Ik/T0D/tTqS0tL+Hw+VlZWUKlUDAwMMDQ0RGlpKQArKyu4\r\n" +
 | 
			
		||||
			"XC6uXbtGLBZDlmUaGxuprKzMajGmyrfVY7EYfr+fDx8+ANDS0sLo6ChWqzU5xu12c/r0aXw+HwqF\r\n" +
 | 
			
		||||
			"gtraWkwmE0qlMutZSpVPq8/NzeH3+4lGo5SUlOByuRgcHESl+u8zLly4wMjICAClpaU0NTUlWZEL\r\n" +
 | 
			
		||||
			"ZBwDfo/wDQ0NKa0ej8dZWFggEAgQiUQA6Onp4caNG5jN5l/GTk1N0dnZmTab8sqA+fl5jh07hsfj\r\n" +
 | 
			
		||||
			"AUCWZXbv3g1AIBBYR/NoNEokEuHLly8kEgkATCYTQ0NDHD9+fFOGrKW2jfRmGxqNJr1CaHJyUuj1\r\n" +
 | 
			
		||||
			"+j8qNiRJEp2dneL27dtidXU15TrhcFhYLJa/Km4ykeLi4tSF0O++Xl9fz5EjR9Dr9SlPtry8nKqq\r\n" +
 | 
			
		||||
			"KsxmM1arFa1Wm7ZVQqEQDx8+5N27dznvTG022+YMmJycFEajUQBClmUxMjIiYrGYKDSwVQ3f3t4u\r\n" +
 | 
			
		||||
			"3rx5k1LJy5cvhd1uF83NzaKoqChvFP5b6e3t/fUAMrX64uKiOHnypFAoFDtm0z9Ll14nVACRSIQz\r\n" +
 | 
			
		||||
			"Z84wPj4OwMGDBxkbG6OxsXFT/7l37x6Dg4PMz89TJEn0VVXwT2U5dUVq1DlOXdmCsrwcVTQapaen\r\n" +
 | 
			
		||||
			"B4/HgyzLDA8Pc+7cOSRJ2nTixYsXGR4eBsBaWsK/xmrqitTsSIyOjgpAmEwm8fbt27QCx969e3ck\r\n" +
 | 
			
		||||
			"5TdyAfr6+gQgxsfH046ct27dEjqdbscfQG9vr1CtNRiZVMR2ux273U4hQOru7gbA5XLh9Xr5H/wn\r\n" +
 | 
			
		||||
			"yS9WV1dFR0dHQfh0ptLR0fGjDgiHw8LpdIqGhoYdm9P/RCwWS3qXok+ePOHs2bO8fv06eZ3c39+X\r\n" +
 | 
			
		||||
			"7AZ3MlK2wzMzMwwMDPD48WMAamtr6e/vo7m5uWBCwKYMWFhYoK2tjWAwiEaj4ejRXmw2W8oCqaAO\r\n" +
 | 
			
		||||
			"wO1209XVhSzLHD5s+3F5UGAwGo2bt8OhUEjU1NQUdBDc8s9QMBhkYmIieVVVaLDZbHwHmmIQk3rD\r\n" +
 | 
			
		||||
			"exgAAAAASUVORK5CYII=\r\n")
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, expectedAttachment, content.Attachments[0].Content)
 | 
			
		||||
 | 
			
		||||
	// HCL Notes inlines attachments like this: without a filename.
 | 
			
		||||
	// the attached image is from: https://openmoji.org/library/emoji-1F684
 | 
			
		||||
	mailString = "Content-Type: multipart/related; boundary=\"=_related boundary=\"\r\n" +
 | 
			
		||||
		"\r\n" +
 | 
			
		||||
		"This text is for clients unable to decode multipart/related with multipart/alternative.\r\n" +
 | 
			
		||||
		"\r\n" +
 | 
			
		||||
		"--=_related boundary=\r\n" +
 | 
			
		||||
		"Content-Type: multipart/alternative; boundary=\"=_alternative boundary=\"\r\n" +
 | 
			
		||||
		"\r\n" +
 | 
			
		||||
		"\r\n" +
 | 
			
		||||
		"\r\n" +
 | 
			
		||||
		"--=_alternative boundary=\r\n" +
 | 
			
		||||
		"Content-Type: text/plain\r\n" +
 | 
			
		||||
		"\r\n" +
 | 
			
		||||
		"This is the plaintext.\r\n" +
 | 
			
		||||
		"\r\n" +
 | 
			
		||||
		"--=_alternative boundary=\r\n" +
 | 
			
		||||
		"Content-Type: text/html\r\n" +
 | 
			
		||||
		"\r\n" +
 | 
			
		||||
		"<p>This is a mail with multipart/related. Here is an image sent without a filename.</p>\r\n" +
 | 
			
		||||
		"<img src=cid:_1_2845>\r\n" +
 | 
			
		||||
		"\r\n" +
 | 
			
		||||
		"--=_alternative boundary=--\r\n" +
 | 
			
		||||
		"\r\n" +
 | 
			
		||||
		"--=_related boundary=\r\n" +
 | 
			
		||||
		"Content-Transfer-Encoding: base64\r\n" +
 | 
			
		||||
		"Content-Type: image/png\r\n" +
 | 
			
		||||
		"Content-ID: <_1_2845>\r\n" +
 | 
			
		||||
		"\r\n" +
 | 
			
		||||
		"iVBORw0KGgoAAAANSUhEUgAAAEAAAAAiCAYAAADvVd+PAAAFLUlEQVRo3t2ZX0iTXxjHP3u35qvT\r\n" +
 | 
			
		||||
		"6ZzhzKFuzPQq9WKQZS6FvLQf3Wh30ViBQXnViC5+LVKEiC6DjMQgCCy6NChoIKwghhcR1bJ5s5Ei\r\n" +
 | 
			
		||||
		"LmtNs/05XYT7Vercaps/94Xn4uU95znvOc/3+XdehRBCsM1YXl7G6/Xi8Xh49uwZMzMzhEIhFhcX\r\n" +
 | 
			
		||||
		"+fbtW87WbW1tRbVdmxZC8PTpU8bGxrh//z5fv37dcJxGo2HXrl1ZWVOhUPzybDAYUOSbAYlEgjt3\r\n" +
 | 
			
		||||
		"7nD58mVmZ2cBkCSJ1tZWDhw4wP79+2lpaUGv16PX61Gr1Tm3RN7w/Plz0d7eLgABCKPRKJxOp3j/\r\n" +
 | 
			
		||||
		"/r3YLuTlAD5+/ChOnDiR3HhdXZ24e/euiMfjYruRcxe4evUqV65c4fPnz6hUKrq7uzl06NA6v157\r\n" +
 | 
			
		||||
		"19bWlrbueDzOq1evmJ6eJhQKZRww9+3blzsXWFpaEqdOnUpaPV2ZmJjYUveLFy+Ew+EQFRUVGev/\r\n" +
 | 
			
		||||
		"WTQaTW4Y8OjRIxwOB4FAAEmS0Gq1lJWVpZwTjUaZm5vDZrPhdrs3HOP3+3E6nTx48IC1zy4uLqas\r\n" +
 | 
			
		||||
		"rAy1Wr0uym8FnU6X3TT46dMnzp8/z82bNwHQarU0NTVRUlKScl44HMbn8wFQU1Oz7n0sFuP69etc\r\n" +
 | 
			
		||||
		"unSJ5eVllEole/bswWAwbKk7FSRJyl4a/NnqSqWS+vp6jEZjSqskEglmZ2cJBoMIIbBYLExNTWEw\r\n" +
 | 
			
		||||
		"GJJjvF4vDoeD6elpAKqrqzGbzVlJj5Ik/T0D/tTqS0tL+Hw+VlZWUKlUDAwMMDQ0RGlpKQArKyu4\r\n" +
 | 
			
		||||
		"XC6uXbtGLBZDlmUaGxuprKzMajGmyrfVY7EYfr+fDx8+ANDS0sLo6ChWqzU5xu12c/r0aXw+HwqF\r\n" +
 | 
			
		||||
		"gtraWkwmE0qlMutZSpVPq8/NzeH3+4lGo5SUlOByuRgcHESl+u8zLly4wMjICAClpaU0NTUlWZEL\r\n" +
 | 
			
		||||
		"ZBwDfo/wDQ0NKa0ej8dZWFggEAgQiUQA6Onp4caNG5jN5l/GTk1N0dnZmTab8sqA+fl5jh07hsfj\r\n" +
 | 
			
		||||
		"AUCWZXbv3g1AIBBYR/NoNEokEuHLly8kEgkATCYTQ0NDHD9+fFOGrKW2jfRmGxqNJr1CaHJyUuj1\r\n" +
 | 
			
		||||
		"+j8qNiRJEp2dneL27dtidXU15TrhcFhYLJa/Km4ykeLi4tSF0O++Xl9fz5EjR9Dr9SlPtry8nKqq\r\n" +
 | 
			
		||||
		"KsxmM1arFa1Wm7ZVQqEQDx8+5N27dznvTG022+YMmJycFEajUQBClmUxMjIiYrGYKDSwVQ3f3t4u\r\n" +
 | 
			
		||||
		"3rx5k1LJy5cvhd1uF83NzaKoqChvFP5b6e3t/fUAMrX64uKiOHnypFAoFDtm0z9Ll14nVACRSIQz\r\n" +
 | 
			
		||||
		"Z84wPj4OwMGDBxkbG6OxsXFT/7l37x6Dg4PMz89TJEn0VVXwT2U5dUVq1DlOXdmCsrwcVTQapaen\r\n" +
 | 
			
		||||
		"B4/HgyzLDA8Pc+7cOSRJ2nTixYsXGR4eBsBaWsK/xmrqitTsSIyOjgpAmEwm8fbt27QCx969e3ck\r\n" +
 | 
			
		||||
		"5TdyAfr6+gQgxsfH046ct27dEjqdbscfQG9vr1CtNRiZVMR2ux273U4hQOru7gbA5XLh9Xr5H/wn\r\n" +
 | 
			
		||||
		"yS9WV1dFR0dHQfh0ptLR0fGjDgiHw8LpdIqGhoYdm9P/RCwWS3qXok+ePOHs2bO8fv06eZ3c39+X\r\n" +
 | 
			
		||||
		"7AZ3MlK2wzMzMwwMDPD48WMAamtr6e/vo7m5uWBCwKYMWFhYoK2tjWAwiEaj4ejRXmw2W8oCqaAO\r\n" +
 | 
			
		||||
		"wO1209XVhSzLHD5s+3F5UGAwGo2bt8OhUEjU1NQUdBDc8s9QMBhkYmIieVVVaLDZbHwHmmIQk3rD\r\n" +
 | 
			
		||||
		"exgAAAAASUVORK5CYII=\r\n" +
 | 
			
		||||
		"\r\n" +
 | 
			
		||||
		"--=_related boundary=--\r\n" +
 | 
			
		||||
		"\r\n"
 | 
			
		||||
 | 
			
		||||
	env, err = enmime.ReadEnvelope(strings.NewReader(mailString))
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	content = getContentFromMailReader(env)
 | 
			
		||||
	assert.Equal(t, "This is the plaintext.", content.Content)
 | 
			
		||||
	assert.Len(t, content.Attachments, 1)
 | 
			
		||||
	assert.Equal(t, "_1_2845.png", content.Attachments[0].Name)
 | 
			
		||||
	expectedAttachment, err = base64.StdEncoding.DecodeString(
 | 
			
		||||
		"iVBORw0KGgoAAAANSUhEUgAAAEAAAAAiCAYAAADvVd+PAAAFLUlEQVRo3t2ZX0iTXxjHP3u35qvT\r\n" +
 | 
			
		||||
			"6ZzhzKFuzPQq9WKQZS6FvLQf3Wh30ViBQXnViC5+LVKEiC6DjMQgCCy6NChoIKwghhcR1bJ5s5Ei\r\n" +
 | 
			
		||||
			"LmtNs/05XYT7Vercaps/94Xn4uU95znvOc/3+XdehRBCsM1YXl7G6/Xi8Xh49uwZMzMzhEIhFhcX\r\n" +
 | 
			
		||||
			"+fbtW87WbW1tRbVdmxZC8PTpU8bGxrh//z5fv37dcJxGo2HXrl1ZWVOhUPzybDAYUOSbAYlEgjt3\r\n" +
 | 
			
		||||
			"7nD58mVmZ2cBkCSJ1tZWDhw4wP79+2lpaUGv16PX61Gr1Tm3RN7w/Plz0d7eLgABCKPRKJxOp3j/\r\n" +
 | 
			
		||||
			"/r3YLuTlAD5+/ChOnDiR3HhdXZ24e/euiMfjYruRcxe4evUqV65c4fPnz6hUKrq7uzl06NA6v157\r\n" +
 | 
			
		||||
			"19bWlrbueDzOq1evmJ6eJhQKZRww9+3blzsXWFpaEqdOnUpaPV2ZmJjYUveLFy+Ew+EQFRUVGev/\r\n" +
 | 
			
		||||
			"WTQaTW4Y8OjRIxwOB4FAAEmS0Gq1lJWVpZwTjUaZm5vDZrPhdrs3HOP3+3E6nTx48IC1zy4uLqas\r\n" +
 | 
			
		||||
			"rAy1Wr0uym8FnU6X3TT46dMnzp8/z82bNwHQarU0NTVRUlKScl44HMbn8wFQU1Oz7n0sFuP69etc\r\n" +
 | 
			
		||||
			"unSJ5eVllEole/bswWAwbKk7FSRJyl4a/NnqSqWS+vp6jEZjSqskEglmZ2cJBoMIIbBYLExNTWEw\r\n" +
 | 
			
		||||
			"GJJjvF4vDoeD6elpAKqrqzGbzVlJj5Ik/T0D/tTqS0tL+Hw+VlZWUKlUDAwMMDQ0RGlpKQArKyu4\r\n" +
 | 
			
		||||
			"XC6uXbtGLBZDlmUaGxuprKzMajGmyrfVY7EYfr+fDx8+ANDS0sLo6ChWqzU5xu12c/r0aXw+HwqF\r\n" +
 | 
			
		||||
			"gtraWkwmE0qlMutZSpVPq8/NzeH3+4lGo5SUlOByuRgcHESl+u8zLly4wMjICAClpaU0NTUlWZEL\r\n" +
 | 
			
		||||
			"ZBwDfo/wDQ0NKa0ej8dZWFggEAgQiUQA6Onp4caNG5jN5l/GTk1N0dnZmTab8sqA+fl5jh07hsfj\r\n" +
 | 
			
		||||
			"AUCWZXbv3g1AIBBYR/NoNEokEuHLly8kEgkATCYTQ0NDHD9+fFOGrKW2jfRmGxqNJr1CaHJyUuj1\r\n" +
 | 
			
		||||
			"+j8qNiRJEp2dneL27dtidXU15TrhcFhYLJa/Km4ykeLi4tSF0O++Xl9fz5EjR9Dr9SlPtry8nKqq\r\n" +
 | 
			
		||||
			"KsxmM1arFa1Wm7ZVQqEQDx8+5N27dznvTG022+YMmJycFEajUQBClmUxMjIiYrGYKDSwVQ3f3t4u\r\n" +
 | 
			
		||||
			"3rx5k1LJy5cvhd1uF83NzaKoqChvFP5b6e3t/fUAMrX64uKiOHnypFAoFDtm0z9Ll14nVACRSIQz\r\n" +
 | 
			
		||||
			"Z84wPj4OwMGDBxkbG6OxsXFT/7l37x6Dg4PMz89TJEn0VVXwT2U5dUVq1DlOXdmCsrwcVTQapaen\r\n" +
 | 
			
		||||
			"B4/HgyzLDA8Pc+7cOSRJ2nTixYsXGR4eBsBaWsK/xmrqitTsSIyOjgpAmEwm8fbt27QCx969e3ck\r\n" +
 | 
			
		||||
			"5TdyAfr6+gQgxsfH046ct27dEjqdbscfQG9vr1CtNRiZVMR2ux273U4hQOru7gbA5XLh9Xr5H/wn\r\n" +
 | 
			
		||||
			"yS9WV1dFR0dHQfh0ptLR0fGjDgiHw8LpdIqGhoYdm9P/RCwWS3qXok+ePOHs2bO8fv06eZ3c39+X\r\n" +
 | 
			
		||||
			"7AZ3MlK2wzMzMwwMDPD48WMAamtr6e/vo7m5uWBCwKYMWFhYoK2tjWAwiEaj4ejRXmw2W8oCqaAO\r\n" +
 | 
			
		||||
			"wO1209XVhSzLHD5s+3F5UGAwGo2bt8OhUEjU1NQUdBDc8s9QMBhkYmIieVVVaLDZbHwHmmIQk3rD\r\n" +
 | 
			
		||||
			"exgAAAAASUVORK5CYII=\r\n")
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, expectedAttachment, content.Attachments[0].Content)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue