All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.hubspot.imap.utils.parsers.fetch.EnvelopeParser Maven / Gradle / Ivy
package com.hubspot.imap.utils.parsers.fetch;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.hubspot.imap.protocol.message.Envelope;
import com.hubspot.imap.protocol.message.ImapAddress;
import com.hubspot.imap.protocol.message.ImapAddress.Builder;
import com.hubspot.imap.utils.NilMarker;
import com.hubspot.imap.utils.enums.EnvelopeField;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.TemporalAccessor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import org.apache.james.mime4j.dom.Header;
import org.apache.james.mime4j.stream.Field;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class EnvelopeParser {
private static final Logger LOGGER = LoggerFactory.getLogger(EnvelopeParser.class);
private static final Splitter COMMA_SPLITTER = Splitter
.on(",")
.omitEmptyStrings()
.trimResults();
private static final Splitter PARAMETER_COMMA_SPLITTER = Splitter
.onPattern("(\\>,)")
.omitEmptyStrings()
.trimResults();
private static final Splitter ADDRESS_SPLITTER = Splitter
.onPattern("[\\<\\>]")
.omitEmptyStrings()
.trimResults();
static final DateTimeFormatter RFC2822_FORMATTER = DateTimeFormatter
.ofPattern("[EEE, ]d MMM yyyy H:m:s[ zzz][ Z][ (z)]")
.withLocale(Locale.US);
/**
* This parses an envelope response according to RFC3501:
*
* The fields of the envelope structure are in the following
* order: date, subject, from, sender, reply-to, to, cc, bcc,
* in-reply-to, and message-id. The date, subject, in-reply-to,
* and message-id fields are strings. The from, sender, reply-to,
* to, cc, and bcc fields are parenthesized lists of address
* structures.
*
* @param in ByteBuf containing the full envelope response.
* @return Parsed Envelope object.
*/
public static Envelope parse(List in) {
LOGGER.debug("Parsing envelope response: {}", in);
String dateString = castToString(in.get(0));
String subject = castToString(in.get(1));
List from = emailAddressesFromNestedList(castToList(in.get(2)));
List sender = emailAddressesFromNestedList(castToList(in.get(3)));
List replyTo = emailAddressesFromNestedList(castToList(in.get(4)));
List to = emailAddressesFromNestedList(castToList(in.get(5)));
List cc = emailAddressesFromNestedList(castToList(in.get(6)));
List bcc = emailAddressesFromNestedList(castToList(in.get(7)));
String inReplyTo = castToString(in.get(8));
String messageId = castToString(in.get(9));
Envelope.Builder envelope = new Envelope.Builder()
.setDateString(dateString)
.setSubject(subject)
.setFrom(from)
.setSender(sender)
.setReplyTo(replyTo)
.setTo(to)
.setCc(cc)
.setBcc(bcc)
.setInReplyTo(inReplyTo)
.setMessageId(messageId);
try {
if (!Strings.isNullOrEmpty(dateString) && !dateString.equalsIgnoreCase("nil")) {
envelope.setDate(parseDate(dateString));
}
} catch (DateTimeParseException e) {
LOGGER.debug("Failed to parse date {}", dateString, e);
}
return envelope.build();
}
public static Envelope parseHeader(Header header) {
Map envelopeFields = header
.getFields()
.stream()
.filter(f -> EnvelopeField.NAME_INDEX.containsKey(f.getName().toLowerCase()))
.collect(
Collectors.groupingBy(
field -> field.getName().toLowerCase(),
Collectors.mapping(Field::getBody, Collectors.joining(","))
)
);
Envelope.Builder envelope = new Envelope.Builder();
String dateString = envelopeFields.get(EnvelopeField.DATE.getFieldName());
List fromAddress = emailAddressesFromStringList(
envelopeFields.get(EnvelopeField.FROM.getFieldName())
);
envelope
.setDateString(dateString)
.setSubject(envelopeFields.get(EnvelopeField.SUBJECT.getFieldName()))
.setFrom(fromAddress)
.setSender(
emailAddressesFromStringList(
envelopeFields.get(EnvelopeField.SENDER.getFieldName()),
fromAddress
)
)
.setReplyTo(
emailAddressesFromStringList(
envelopeFields.get(EnvelopeField.REPLY_TO.getFieldName()),
fromAddress
)
)
.setTo(
emailAddressesFromStringList(envelopeFields.get(EnvelopeField.TO.getFieldName()))
)
.setCc(
emailAddressesFromStringList(envelopeFields.get(EnvelopeField.CC.getFieldName()))
)
.setBcc(
emailAddressesFromStringList(envelopeFields.get(EnvelopeField.BCC.getFieldName()))
)
.setInReplyTo(envelopeFields.get(EnvelopeField.IN_REPLY_TO.getFieldName()))
.setMessageId(envelopeFields.get(EnvelopeField.MESSAGE_ID.getFieldName()));
try {
if (!Strings.isNullOrEmpty(dateString)) {
envelope.setDate(parseDate(dateString));
}
} catch (DateTimeParseException e) {
LOGGER.debug("Failed to parse date {}", header.getField("date").getBody(), e);
}
return envelope.build();
}
private static List emailAddressesFromStringList(String addresses) {
return emailAddressesFromStringList(addresses, Collections.emptyList());
}
@VisibleForTesting
public static List emailAddressesFromStringList(
String addresses,
List defaults
) {
if (Strings.isNullOrEmpty(addresses)) {
return defaults;
}
return getSplitter(addresses)
.splitToList(addresses)
.stream()
.map(ADDRESS_SPLITTER::splitToList)
.map(EnvelopeParser::imapAddressFromParts)
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
}
private static Splitter getSplitter(String addresses) {
if (addresses.contains(">")) {
return PARAMETER_COMMA_SPLITTER;
}
return COMMA_SPLITTER;
}
private static Optional imapAddressFromParts(List addressParts) {
if (addressParts.isEmpty()) {
return Optional.empty();
}
Optional emailAddressMaybe = addressParts
.stream()
.filter(part -> part.contains("@"))
.findFirst();
if (!emailAddressMaybe.isPresent()) {
return Optional.empty();
}
String emailAddress = emailAddressMaybe.get();
ImapAddress.Builder addressBuilder = new ImapAddress.Builder();
addressBuilder.setAddress(emailAddress);
int emailIndex = addressParts.indexOf(emailAddress);
if (addressParts.size() > 2) {
LOGGER.warn(
"Expected two address parts but found {} - {}",
addressParts.size(),
addressParts
);
}
if (emailIndex > 0) {
addressBuilder.setPersonal(addressParts.get(emailIndex - 1));
}
return Optional.of(addressBuilder.build());
}
@SuppressWarnings("unchecked")
private static List castToList(Object object) {
if (object instanceof String) {
String string = ((String) object);
if (string.startsWith("NIL")) {
return new ArrayList<>();
} else {
LOGGER.debug("Failed to cast object to list: {} ", string);
throw new IllegalStateException(
"A list cannot have string value other than \"NIL\""
);
}
} else if (object instanceof NilMarker) {
return Collections.emptyList();
} else {
return ((List) object);
}
}
@SuppressWarnings("unchecked")
private static String castToString(Object object) {
if (object instanceof String) {
return ((String) object);
} else if (object instanceof NilMarker) {
return null;
} else {
throw new IllegalStateException(
String.format(
"Cannot use instance of type %s as string",
object.getClass().getName()
)
);
}
}
@SuppressWarnings("unchecked")
private static List emailAddressesFromNestedList(List in) {
if (in.size() == 0) {
return new ArrayList<>();
}
return in
.stream()
.filter(o -> !(o instanceof NilMarker))
.map(o ->
((List) o).stream()
.map(e -> {
if (e instanceof NilMarker) {
return null;
} else {
return ((String) e);
}
})
.collect(Collectors.toList())
)
.map(o -> new Builder().parseFrom(o).build())
.collect(Collectors.toList());
}
@VisibleForTesting
public static ZonedDateTime parseDate(String in) {
in = in.replaceAll("\\s+", " ");
TemporalAccessor temporalAccessor = RFC2822_FORMATTER.parseBest(
in,
ZonedDateTime::from,
LocalDateTime::from,
LocalDate::from
);
if (temporalAccessor instanceof LocalDateTime) {
return ((LocalDateTime) temporalAccessor).atZone(ZoneId.of("UTC"));
} else if (temporalAccessor instanceof LocalDate) {
return ((LocalDate) temporalAccessor).atStartOfDay(ZoneId.of("UTC"));
}
return ((ZonedDateTime) temporalAccessor);
}
}