diff --git a/duniter4j-core-shared/src/main/java/org/duniter/core/util/mail/MailContentBuilder.java b/duniter4j-core-shared/src/main/java/org/duniter/core/util/mail/MailContentBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..7e45cbc6b982400f95d2c4662378952f72524d54 --- /dev/null +++ b/duniter4j-core-shared/src/main/java/org/duniter/core/util/mail/MailContentBuilder.java @@ -0,0 +1,191 @@ +package org.duniter.core.util.mail; + +import com.google.common.collect.Lists; +import org.duniter.core.util.CollectionUtils; +import org.duniter.core.util.Preconditions; +import org.duniter.core.util.StringUtils; + +import javax.activation.DataHandler; +import javax.activation.DataSource; +import javax.activation.URLDataSource; +import javax.mail.BodyPart; +import javax.mail.MessagingException; +import javax.mail.Multipart; +import javax.mail.internet.MimeBodyPart; +import javax.mail.internet.MimeMultipart; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.HashMap; +import java.util.List; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Created by StrongMan on 25/05/14. + * See https://stackoverflow.com/questions/3902455/mail-multipart-alternative-vs-multipart-mixed + */ +public class MailContentBuilder { + + private static final Pattern COMPILED_PATTERN_SRC_URL_SINGLE = Pattern.compile("src='([^']*)'", Pattern.CASE_INSENSITIVE); + private static final Pattern COMPILED_PATTERN_SRC_URL_DOUBLE = Pattern.compile("src=\"([^\"]*)\"", Pattern.CASE_INSENSITIVE); + + + public static List<URL> findUrls(String messageHtml) { + Preconditions.checkNotNull(messageHtml); + List<URL> urls = Lists.newArrayList(); + urls.addAll(findUrls(messageHtml, COMPILED_PATTERN_SRC_URL_SINGLE)); + urls.addAll(findUrls(messageHtml, COMPILED_PATTERN_SRC_URL_DOUBLE)); + return urls; + } + + /** + * Build an email message. + * + * The HTML may reference the embedded image (messageHtmlInline) using the filename. Any path portion is ignored to make my life easier + * e.g. If you pass in the image C:\Temp\dog.jpg you can use <img src="dog.jpg"/> or <img src="C:\Temp\dog.jpg"/> and both will work + * + * @param messageText + * @param messageHtml + * @param embeddedImages + * @param attachments + * @return + * @throws MessagingException + */ + public Multipart build(String messageText, String messageHtml, List<URL> embeddedImages, List<URL> attachments) throws MessagingException { + final Multipart mpMixed = new MimeMultipart("mixed"); + { + // alternative + final Multipart mpMixedAlternative = newChild(mpMixed, "alternative"); + { + // Note: MUST RENDER HTML LAST otherwise iPad mail client only renders the last image and no email + addTextVersion(mpMixedAlternative,messageText); + addHtmlVersion(mpMixedAlternative,messageHtml, embeddedImages); + } + // attachments + addAttachments(mpMixed,attachments); + } + + //msg.setText(message, "utf-8"); + //msg.setContent(message,"text/html; charset=utf-8"); + return mpMixed; + } + + private Multipart newChild(Multipart parent, String alternative) throws MessagingException { + MimeMultipart child = new MimeMultipart(alternative); + final MimeBodyPart mbp = new MimeBodyPart(); + parent.addBodyPart(mbp); + mbp.setContent(child); + return child; + } + + private void addTextVersion(Multipart mpRelatedAlternative, String messageText) throws MessagingException { + final MimeBodyPart textPart = new MimeBodyPart(); + textPart.setContent(messageText, "text/plain"); + mpRelatedAlternative.addBodyPart(textPart); + } + + private void addHtmlVersion(Multipart parent, String messageHtml, List<URL> embedded) throws MessagingException { + // HTML version + final Multipart mpRelated = newChild(parent,"related"); + + // Get embedded URL from message + if (embedded == null) { + embedded = findUrls(messageHtml); + } + + // Html + final MimeBodyPart htmlPart = new MimeBodyPart(); + HashMap<String,String> cids = new HashMap<String, String>(); + htmlPart.setContent(replaceUrlWithCids(messageHtml,cids), "text/html"); + mpRelated.addBodyPart(htmlPart); + + // Inline images + addImagesInline(mpRelated, embedded, cids); + } + + private void addImagesInline(Multipart parent, List<URL> embedded, HashMap<String,String> cids) throws MessagingException { + if (embedded != null) { + for (URL img : embedded) { + final MimeBodyPart htmlPartImg = new MimeBodyPart(); + DataSource htmlPartImgDs = new URLDataSource(img); + htmlPartImg.setDataHandler(new DataHandler(htmlPartImgDs)); + String fileName = img.getFile(); + fileName = getFileName(fileName); + String newFileName = cids.get(fileName); + boolean imageNotReferencedInHtml = newFileName == null; + if (imageNotReferencedInHtml) continue; + // Gmail requires the cid have <> around it + htmlPartImg.setHeader("Content-ID", "<"+newFileName+">"); + htmlPartImg.setDisposition(BodyPart.INLINE); + parent.addBodyPart(htmlPartImg); + } + } + } + + private void addAttachments(Multipart parent, List<URL> attachments) throws MessagingException { + if (attachments != null) + { + for (URL attachment : attachments) + { + final MimeBodyPart mbpAttachment = new MimeBodyPart(); + DataSource htmlPartImgDs = new URLDataSource(attachment); + mbpAttachment.setDataHandler(new DataHandler(htmlPartImgDs)); + String fileName = attachment.getFile(); + fileName = getFileName(fileName); + mbpAttachment.setDisposition(BodyPart.ATTACHMENT); + mbpAttachment.setFileName(fileName); + parent.addBodyPart(mbpAttachment); + } + } + } + + public String replaceUrlWithCids(String html, HashMap<String,String> cids) + { + html = replaceUrlWithCids(html, COMPILED_PATTERN_SRC_URL_SINGLE, "src='cid:@cid'", cids); + html = replaceUrlWithCids(html, COMPILED_PATTERN_SRC_URL_DOUBLE, "src=\"cid:@cid\"", cids); + return html; + } + + private String replaceUrlWithCids(String html, Pattern pattern, String replacement, HashMap<String,String> cids) { + Matcher matcher = pattern.matcher(html); + StringBuffer sb = new StringBuffer(); + while (matcher.find()) { + String fileName = matcher.group(1); + // Disregarding file path, so don't clash your filenames! + fileName = getFileName(fileName); + // A cid must start with @ and be globally unique + String cid = "@" + UUID.randomUUID() + "_" + fileName; + if (cids.containsKey(fileName)) + cid = cids.get(fileName); + else + cids.put(fileName,cid); + matcher.appendReplacement(sb,replacement.replace("@cid",cid)); + } + matcher.appendTail(sb); + html = sb.toString(); + return html; + } + + private String getFileName(String fileName) { + if (fileName.contains("/")) + fileName = fileName.substring(fileName.lastIndexOf("/")+1); + return fileName; + } + + + private static List<URL> findUrls(String messageHtml, Pattern pattern) { + List<URL> urls = Lists.newArrayList(); + Matcher matcher = pattern.matcher(messageHtml); + while (matcher.find()) { + String src = matcher.group(1); + try { + URL url = new URL(src); + urls.add(url); + } catch (MalformedURLException e) { + // Skip + } + } + return urls; + } +} \ No newline at end of file