diff --git a/internal/htmlutil/htmlutil.go b/internal/htmlutil/htmlutil.go index a012c501..e192dbb4 100644 --- a/internal/htmlutil/htmlutil.go +++ b/internal/htmlutil/htmlutil.go @@ -73,7 +73,7 @@ func ExtractAttachments(s string) []Attachment { return nil } var attachments []Attachment - findAttachments(doc, &attachments) + findAttachments(doc, &attachments, 0) return attachments } @@ -336,7 +336,7 @@ func parseEmbeddedContent(content string, depth int) *html.Node { return doc } -func findAttachments(n *html.Node, attachments *[]Attachment) { +func findAttachments(n *html.Node, attachments *[]Attachment, depth int) { if n.Type == html.ElementNode { switch n.Data { case "action-text-attachment": @@ -352,7 +352,10 @@ func findAttachments(n *html.Node, attachments *[]Attachment) { *attachments = append(*attachments, attachment) } case "figure": - if trix := parseTrixAttachment(n); trix != nil && trix.URL != "" && trix.Filename != "" { + trix := parseTrixAttachment(n) + switch { + case trix == nil: + case trix.URL != "" && trix.Filename != "": *attachments = append(*attachments, Attachment{ URL: trix.URL, Filename: trix.Filename, @@ -360,11 +363,18 @@ func findAttachments(n *html.Node, attachments *[]Attachment) { ByteSize: nonnegativeAttachmentByteSize(trix.Filesize), SGID: trix.SGID, }) + case trix.Content != "": + // An inbound email's files are inside the embedded markup, not + // on the figure that wraps it. The wrapper itself is not listed: + // an embedded body is not a downloadable file. + if doc := parseEmbeddedContent(trix.Content, depth); doc != nil { + findAttachments(doc, attachments, depth+1) + } } } } for child := n.FirstChild; child != nil; child = child.NextSibling { - findAttachments(child, attachments) + findAttachments(child, attachments, depth) } } diff --git a/internal/htmlutil/htmlutil_test.go b/internal/htmlutil/htmlutil_test.go index f5f703b9..c49bd118 100644 --- a/internal/htmlutil/htmlutil_test.go +++ b/internal/htmlutil/htmlutil_test.go @@ -194,6 +194,33 @@ func TestExtractAttachmentsSkipsEmbeddedHTMLAttachment(t *testing.T) { } } +func TestExtractAttachmentsInsideEmbeddedHTMLAttachment(t *testing.T) { + // An HTML email from outside HEY arrives as one text/html trix attachment + // whose content string holds the original markup, files included. + content := `
` + + attachments := ExtractAttachments(content) + if len(attachments) != 1 { + t.Fatalf("ExtractAttachments = %+v, want the file inside the embedded body", attachments) + } + got := attachments[0] + if got.Filename != "payslip.pdf" || got.URL != "/rails/blobs/payslip.pdf" || got.ContentType != "application/pdf" || got.SGID != "sgid-1" || got.ByteSize == nil || *got.ByteSize != 44218 { + t.Errorf("embedded attachment = %+v", got) + } +} + +func TestExtractAttachmentsEmbeddedContentStopsRecursing(t *testing.T) { + nested := `
` + for range embeddedContentDepthLimit + 2 { + nested = `
` + } + + if attachments := ExtractAttachments(nested); len(attachments) != 0 { + t.Errorf("ExtractAttachments = %+v, should stop before the innermost level", attachments) + } +} + func TestExtractAttachments(t *testing.T) { h := `
`