Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import jakarta.activation.DataSource;
import jakarta.mail.Address;
import jakarta.mail.BodyPart;
import jakarta.mail.Header;
import jakarta.mail.MessagingException;
import jakarta.mail.Multipart;
import jakarta.mail.Session;
Expand Down Expand Up @@ -46,6 +47,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand All @@ -61,7 +63,8 @@
@WritesAttribute(attribute = "filename ", description = "The filename of the attachment"),
@WritesAttribute(attribute = "email.attachment.parent.filename ", description = "The filename of the parent FlowFile"),
@WritesAttribute(attribute = "email.attachment.parent.uuid", description = "The UUID of the original FlowFile."),
@WritesAttribute(attribute = "mime.type", description = "The mime type of the attachment.")})
@WritesAttribute(attribute = "mime.type", description = "The mime type of the attachment."),
@WritesAttribute(attribute = ExtractEmailAttachments.ATTACHMENT_HEADER_ATTRIBUTE_PREFIX + "<attachment header name>", description = "Attachment header.")})

public class ExtractEmailAttachments extends AbstractProcessor {
public static final String ATTACHMENT_ORIGINAL_FILENAME = "email.attachment.parent.filename";
Expand All @@ -80,6 +83,7 @@ public class ExtractEmailAttachments extends AbstractProcessor {
.description("FlowFiles that could not be parsed")
.build();

static final String ATTACHMENT_HEADER_ATTRIBUTE_PREFIX = "email.attachment.header.";
private static final String ATTACHMENT_DISPOSITION = "attachment";

private static final Set<Relationship> RELATIONSHIPS = Set.of(
Expand Down Expand Up @@ -117,12 +121,13 @@ public void onTrigger(final ProcessContext context, final ProcessSession session

final String originalFlowFileName = originalFlowFile.getAttribute(CoreAttributes.FILENAME.key());
try {
final List<DataSource> attachments = new ArrayList<>();
final List<Attachment> attachments = new ArrayList<>();
parseAttachments(attachments, originalMessage, 0);

for (final DataSource data : attachments) {
for (final Attachment attachment : attachments) {
FlowFile split = session.create(originalFlowFile);
final Map<String, String> attributes = new HashMap<>();
final DataSource data = attachment.dataSource();
final String name = data.getName();
if (name != null && !name.isBlank()) {
attributes.put(CoreAttributes.FILENAME.key(), name);
Expand All @@ -131,6 +136,13 @@ public void onTrigger(final ProcessContext context, final ProcessSession session
if (contentType != null && !contentType.isBlank()) {
attributes.put(CoreAttributes.MIME_TYPE.key(), contentType);
}

for (Map.Entry<String, String> entry : attachment.headers().entrySet()) {
final String headerAttributeName = ATTACHMENT_HEADER_ATTRIBUTE_PREFIX + entry.getKey();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the header name be lowercased when building the attribute name, as ExtractEmailHeaders does, so downstream flows can rely on stable attribute names?

final String headerAttributeValue = entry.getValue();
attributes.put(headerAttributeName, headerAttributeValue);
}

String parentUuid = originalFlowFile.getAttribute(CoreAttributes.UUID.key());
attributes.put(ATTACHMENT_ORIGINAL_UUID, parentUuid);
attributes.put(ATTACHMENT_ORIGINAL_FILENAME, originalFlowFileName);
Expand Down Expand Up @@ -176,7 +188,7 @@ public Set<Relationship> getRelationships() {
return RELATIONSHIPS;
}

private void parseAttachments(final List<DataSource> attachments, final MimePart parentPart, final int depth) throws MessagingException, IOException {
private void parseAttachments(final List<Attachment> attachments, final MimePart parentPart, final int depth) throws MessagingException, IOException {
final String disposition = parentPart.getDisposition();

final Object parentContent = parentPart.getContent();
Expand All @@ -191,7 +203,24 @@ private void parseAttachments(final List<DataSource> attachments, final MimePart
}
} else if (ATTACHMENT_DISPOSITION.equalsIgnoreCase(disposition) || depth > 0) {
final DataSource dataSource = parentPart.getDataHandler().getDataSource();
attachments.add(dataSource);
final Map<String, String> extractedHeaders = new HashMap<>();

if (parentPart instanceof final MimeBodyPart mimeBodyPart) {
final Enumeration<Header> headers = mimeBodyPart.getAllHeaders();
while (headers.hasMoreElements()) {
final Header header = headers.nextElement();
final String name = header.getName();
if (name != null && !name.isBlank()) {
final String value = header.getValue();
extractedHeaders.put(name, value);
}
}
}

final Attachment attachment = new Attachment(dataSource, extractedHeaders);
attachments.add(attachment);
}
}
}

record Attachment(DataSource dataSource, Map<String, String> headers) { }
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ public void testValidEmailWithAttachments() {
runner.assertTransferCount(ExtractEmailAttachments.REL_ATTACHMENTS, 1);
// Have a look at the attachments...
final List<MockFlowFile> splits = runner.getFlowFilesForRelationship(ExtractEmailAttachments.REL_ATTACHMENTS);
splits.get(0).assertAttributeEquals("filename", "pom.xml-0");
final MockFlowFile split = splits.getFirst();
split.assertAttributeEquals("filename", "pom.xml-0");
assertAttachmentHeaderAttributes(split);
}

@Test
Expand All @@ -76,6 +78,10 @@ public void testValidEmailWithMultipleAttachments() {
}

assertTrue(filenames.containsAll(Arrays.asList("pom.xml-0", "pom.xml-1", "pom.xml-2")));

for (MockFlowFile split : splits) {
assertAttachmentHeaderAttributes(split);
}
}

@Test
Expand All @@ -102,4 +108,12 @@ public void testInvalidEmail() {
runner.assertTransferCount(ExtractEmailAttachments.REL_FAILURE, 1);
runner.assertTransferCount(ExtractEmailAttachments.REL_ATTACHMENTS, 0);
}

private void assertAttachmentHeaderAttributes(MockFlowFile split) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the tests assert the exact header attribute names and values, and cover body, inline image, and attachment parts to verify the behavior described in NIFI-16195?

final String regex = "^" + ExtractEmailAttachments.ATTACHMENT_HEADER_ATTRIBUTE_PREFIX + ".+";
final boolean match = split.getAttributes().keySet().stream()
.anyMatch(attribute -> attribute.matches(regex));

assertTrue(match, "FlowFile did not have any attributes which began with " + ExtractEmailAttachments.ATTACHMENT_HEADER_ATTRIBUTE_PREFIX);
}
}
Loading