package tech.blueglacier.email; import com.google.common.net.MediaType; import tech.blueglacier.configuration.AppConfig; import tech.blueglacier.util.Common; import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.james.mime4j.codec.DecodeMonitor; import org.apache.james.mime4j.dom.Header; import org.apache.james.mime4j.message.HeaderImpl; import org.apache.james.mime4j.message.MaximalBodyDescriptor; import org.apache.james.mime4j.stream.BodyDescriptor; import org.apache.james.mime4j.stream.Field; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.*; import java.util.ArrayList; import java.util.List; import java.util.Stack; /** * Contains core logic to recreate an tech.blueglacier.email as seen and perceived by a general user. */ public class Email { private Header header; private ArrayList<Attachment> attachments; private Attachment plainTextEmailBody; private Attachment htmlEmailBody; private Attachment calendarBody; private boolean attachmentReplacedInHtmlBody; private Stack<MultipartType> multipartStack; //Added to distinguish between tech.blueglacier.email attached within another tech.blueglacier.email case private Stack<EmailMessageType> emailMessageStack; private int decodedEmailSize; private int emailSize; public int getEmailSize() { return emailSize; } public int getDecodedEmailSize() { return decodedEmailSize; } Logger LOGGER = LoggerFactory.getLogger(Email.class); public Email(){ this.header = new HeaderImpl(); this.attachments = new ArrayList<Attachment>(); this.attachmentReplacedInHtmlBody = false; this.multipartStack = new Stack<MultipartType>(); this.emailMessageStack = new Stack<EmailMessageType>(); this.decodedEmailSize = 0; this.emailSize = 0; } public Header getHeader() { return header; } public Attachment getPlainTextEmailBody(){ return plainTextEmailBody; } public void fillEmailContents(BodyDescriptor bd, InputStream is){ LOGGER.info("mime part received"); if(addPlainTextEmailBody(bd,is)){} else if(addHTMLEmailBody(bd,is)){} else if(addCalendar(bd,is)){} else{ addAttachments(bd,is); } } private boolean addCalendar(BodyDescriptor bd, InputStream is) { boolean isBodySet = false; BodyDescriptor calendarBodyDescriptor = bd; if(calendarBody == null){ if(isCalendarBody(calendarBodyDescriptor)){ calendarBody = new CalendarBody(bd,is); isBodySet = true; LOGGER.info("Email calendar body identified"); } } return isBodySet; } private boolean shouldIgnore(BodyDescriptor bd, InputStream is) { String attachmentName = Common.getAttachmentName(bd); boolean shouldIgnore = (attachmentName == null); if(shouldIgnore){ LOGGER.info("ignored mime part for attachment consideration"); } return shouldIgnore; } public Stack<MultipartType> getMultipartStack() { return multipartStack; } public Stack<EmailMessageType> getMessageStack() { return emailMessageStack; } public String getEmailSubject(){ Field subjectField = header.getField("Subject"); if (subjectField != null) { Field decodedSubjectField = new CustomUnstructuredFieldImpl(subjectField,DecodeMonitor.SILENT); return ((CustomUnstructuredFieldImpl)decodedSubjectField).getValue(); } return null; } public String getToEmailHeaderValue() { Field to = header.getField("To"); if (to != null) { return to.getBody(); } return null; } public String getCCEmailHeaderValue(){ Field cc = header.getField("Cc"); if (cc != null) { return cc.getBody(); } return null; } public String getFromEmailHeaderValue(){ Field from = header.getField("From"); if (from != null) { return from.getBody(); } return null; } private void addAttachments(BodyDescriptor bd, InputStream is) { attachments.add(new EmailAttachment(bd,is)); LOGGER.info("Email attachment identified"); } private void addAttachments(Attachment attachment) { attachments.add(attachment); LOGGER.info("Email attachment identified"); } private boolean addHTMLEmailBody(BodyDescriptor bd, InputStream is) { boolean isBodySet = false; BodyDescriptor emailHTMLBodyDescriptor = bd; if(htmlEmailBody == null){ if(isHTMLBody(emailHTMLBodyDescriptor)){ htmlEmailBody = new HtmlEmailBody(bd,is); isBodySet = true; LOGGER.info("Email html body identified"); } }else{ if(isHTMLBody(emailHTMLBodyDescriptor)){ if(multipartStack.peek().getBodyDescriptor().getMimeType().equalsIgnoreCase("multipart/mixed")){ InputStream mainInputStream; try { mainInputStream = concatInputStream(is, htmlEmailBody.getIs()); } catch (IOException e) { throw new RuntimeException(e); } htmlEmailBody.setIs(mainInputStream); }else{ addAttachments(new HtmlEmailBody(bd,is)); } isBodySet = true; } } return isBodySet; } private boolean isHTMLBody(BodyDescriptor emailHTMLBodyDescriptor) { String bodyName = Common.getAttachmentName(emailHTMLBodyDescriptor); return (emailHTMLBodyDescriptor.getMimeType().equalsIgnoreCase("text/html") && bodyName == null); } private boolean isCalendarBody(BodyDescriptor emailCalendarBodyDescriptor) { String bodyName = Common.getAttachmentName(emailCalendarBodyDescriptor); return (emailCalendarBodyDescriptor.getMimeType().equalsIgnoreCase("text/calendar") && bodyName == null); } private boolean addPlainTextEmailBody(BodyDescriptor bd, InputStream is) { boolean isBodySet = false; BodyDescriptor emailPlainBodyDescriptor = bd; if(plainTextEmailBody == null){ if(isPlainTextBody(emailPlainBodyDescriptor)){ plainTextEmailBody = new PlainTextEmailBody(bd,is); isBodySet = true; LOGGER.info("Email plain text body identified"); } }else{ if(isPlainTextBody(emailPlainBodyDescriptor)){ if(multipartStack.peek().getBodyDescriptor().getMimeType().equalsIgnoreCase("multipart/mixed")){ InputStream mainInputStream; try { mainInputStream = concatInputStream(is,plainTextEmailBody.getIs()); } catch (IOException e) { throw new RuntimeException(e); } plainTextEmailBody.setIs(mainInputStream); }else{ addAttachments(new PlainTextEmailBody(bd,is)); } isBodySet = true; } } return isBodySet; } private boolean isPlainTextBody(BodyDescriptor emailPlainBodyDescriptor) { String bodyName = Common.getAttachmentName(emailPlainBodyDescriptor); return (emailPlainBodyDescriptor.getMimeType().equalsIgnoreCase("text/plain") && bodyName == null); } public List<Attachment> getAttachments() { return attachments; } public Attachment getHTMLEmailBody() { return htmlEmailBody; } public Attachment getCalendarBody() { return calendarBody; } public void reArrangeEmail() { decodedEmailSize = setEmailSize(); replaceInlineImageAttachmentsInHtmlBody(); removeUnidentifiedMimePartsForAttachment(); emailSize = setEmailSize(); } private int setEmailSize() { int emailSize = 0; if(getHTMLEmailBody() != null){ emailSize += getHTMLEmailBody().getAttachmentSize(); } if (getPlainTextEmailBody() != null) { emailSize += getPlainTextEmailBody().getAttachmentSize(); } if (getCalendarBody() != null) { emailSize += getCalendarBody().getAttachmentSize(); } for (Attachment attachment : getAttachments()) { emailSize += attachment.getAttachmentSize(); } return emailSize; } private void removeUnidentifiedMimePartsForAttachment() { List<Attachment> removeList = new ArrayList<Attachment>(); for (Attachment attachment : attachments) { if(shouldIgnore(attachment.bd, attachment.getIs())){ removeList.add(attachment); } } removeAttachments(removeList); } private void replaceInlineImageAttachmentsInHtmlBody() { if (htmlEmailBody != null) { String strHTMLBody = getHtmlBodyString(); List<Attachment> removalList = new ArrayList<Attachment>(); for (int i = 0; i < attachments.size(); i++) { Attachment attachment = attachments.get(i); if (isImage(attachment)) { String imageMimeType = getImageMimeType(attachment); String contentId = getAttachmentContentID(attachment); strHTMLBody = replaceAttachmentInHtmlBody(strHTMLBody, removalList, attachment, contentId, imageMimeType); } } removeAttachments(removalList); resetRecreatedHtmlBody(strHTMLBody); LOGGER.info("Finished embedding images in html"); } } private String replaceAttachmentInHtmlBody(String strHTMLBody, List<Attachment> removalList, Attachment attachment, String contentId, String imageMimeType) { if (strHTMLBody.contains("cid:" + contentId)) { String base64EncodedAttachment = null; try { base64EncodedAttachment = Base64.encodeBase64String(IOUtils.toByteArray(attachment.getIs())); } catch (IOException e) { throw new RuntimeException(e); } strHTMLBody = strHTMLBody.replace("cid:" + contentId, "data:" + imageMimeType + ";base64," + base64EncodedAttachment); removalList.add(attachment); attachmentReplacedInHtmlBody = true; } return strHTMLBody; } private boolean isImage(Attachment attachment) { if((((MaximalBodyDescriptor)attachment.getBd()).getMediaType().equalsIgnoreCase("image")) || AppConfig.getInstance().isImageFormat(attachment.getAttachmentName())){ return true; } return false; } private String getImageMimeType(Attachment attachment) { String imageMimeType = ((MaximalBodyDescriptor) attachment.getBd()).getMimeType(); if (!isValidImageMimeType(imageMimeType)) { imageMimeType = StringUtils.EMPTY; } return imageMimeType; } private boolean isValidImageMimeType(String imageMimeType) { // Here 'MediaType' of Google Guava library is 'MimeType' of Apache James mime4j MediaType mediaType = null; try { mediaType = MediaType.parse(imageMimeType); } catch (IllegalArgumentException e) { LOGGER.error(e.getMessage()); } return (mediaType != null); } public boolean isAttachmentReplacedInHtmlBody() { return attachmentReplacedInHtmlBody; } private void resetRecreatedHtmlBody(String strHTMLBody) { try { htmlEmailBody.setIs(new ByteArrayInputStream(strHTMLBody.getBytes(getCharSet()))); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } private void removeAttachments(List<Attachment> removalList) { attachments.removeAll(removalList); } private String getAttachmentContentID(Attachment attachment) { String contentId = ((MaximalBodyDescriptor) attachment.getBd()).getContentId(); contentId = stripContentID(contentId); return contentId; } private String stripContentID(String contentId) { contentId = StringUtils.stripStart(contentId, "<"); contentId = StringUtils.stripEnd(contentId, ">"); return contentId; } private String getHtmlBodyString() { String strHTMLBody = ""; try { String charSet = getCharSet(); strHTMLBody = convertStreamToString(htmlEmailBody.getIs(),charSet); } catch (IOException e) { throw new RuntimeException(e); } return strHTMLBody; } private String getCharSet() { String charSet = Common.getFallbackCharset(htmlEmailBody.getBd().getCharset()); return charSet; } private String convertStreamToString(InputStream is, String charSet) throws IOException { if (is != null) { return IOUtils.toString(is, charSet); } else { return ""; } } private InputStream concatInputStream(InputStream source, InputStream destination) throws IOException{ return new SequenceInputStream(destination, source); } }