/********************************************************************** * This file is part of iDempiere ERP Open Source * * http://www.idempiere.org * * * * Copyright (C) Contributors * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License * * as published by the Free Software Foundation; either version 2 * * of the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * * MA 02110-1301, USA. * **********************************************************************/ package org.compiere.util; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Date; import java.util.Enumeration; import java.util.List; import java.util.Properties; import java.util.logging.Level; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.mail.Address; import javax.mail.BodyPart; import javax.mail.Folder; import javax.mail.FolderClosedException; import javax.mail.Header; import javax.mail.Message; import javax.mail.MessagingException; import javax.mail.Multipart; import javax.mail.Part; import javax.mail.Session; import javax.mail.Store; import javax.mail.StoreClosedException; import javax.mail.internet.ContentType; import javax.mail.internet.MimeUtility; import org.adempiere.exceptions.AdempiereException; import org.compiere.model.MAuthorizationAccount; /** * Provide function for sent, receive email in imap protocol.
* Current only support receive email, for sent email, use {@link org.compiere.util.EMail} instead.
* In case internet line is slow, handling error during analysis of message by fetching message in part can have complication.
* Consider to add flag to fetch all message at one time (with retry when error) and after fetching, analysis fetched message offline. * http://www.oracle.com/technetwork/java/javamail/faq/index.html#imapserverbug * @author hieplq base in RequestEMailProcessor */ public class EmailSrv { protected transient static CLogger log = CLogger.getCLogger (EmailSrv.class); protected String imapHost; protected String imapUser; protected String imapPass; protected int imapPort = 143; protected boolean isSSL = false; protected Session mailSession; protected Store mailStore; /** * @param imapHost * @param imapUser * @param imapPass * @param imapPort * @param isSSL */ public EmailSrv (String imapHost, String imapUser, String imapPass, int imapPort, Boolean isSSL){ this.imapHost = imapHost; this.imapUser = imapUser; this.imapPass = imapPass; if(isSSL != null) { this.isSSL = isSSL; } else { this.isSSL = this.imapHost.toLowerCase().startsWith ("imap.gmail.com"); if(!this.isSSL && imapPort == 993) this.isSSL = true; // Port is 993 set to SSL IMAPS if (this.isSSL && imapPort != 993){ log.warning("because imap is gmail server, force port to 993"); imapPort = 993; } } this.imapPort = imapPort; } /** * @deprecated working only with gmail host. * @param imapHost * @param imapUser * @param imapPass */ @Deprecated public EmailSrv (String imapHost, String imapUser, String imapPass){ this (imapHost, imapUser, imapPass, (imapHost != null && imapHost.toLowerCase().startsWith ("imap.gmail.com"))? 993 : 143, (imapHost != null && imapHost.toLowerCase().startsWith ("imap.gmail.com"))? true : false); } /** * Log msg info with INFO log level. * @param msg * @param log * @throws MessagingException */ public static void logMailPartInfo (Part msg, CLogger log) throws MessagingException{ StringBuilder emailPartLogInfo = new StringBuilder(); if (msg instanceof Message){ emailPartLogInfo.append ("\r\n"); emailPartLogInfo.append ("=============Analysis email:"); emailPartLogInfo.append (((Message)msg).getSubject()); emailPartLogInfo.append ("============="); emailPartLogInfo.append ("\r\n"); }else{ emailPartLogInfo.append ("\r\n"); emailPartLogInfo.append (" ==mail part==\r\n"); } emailPartLogInfo.append (" Content type:"); emailPartLogInfo.append (msg.getContentType()); emailPartLogInfo.append ("\r\n"); emailPartLogInfo.append (" Content type raw:"); String [] lsContentTypeRaw = msg.getHeader("Content-Type"); if (lsContentTypeRaw != null){ for (String contentType : lsContentTypeRaw){ emailPartLogInfo.append (contentType); emailPartLogInfo.append ("; "); } } emailPartLogInfo.append ("\r\n"); emailPartLogInfo.append (" Disposition:"); emailPartLogInfo.append (msg.getDisposition()); emailPartLogInfo.append ("\r\n"); emailPartLogInfo.append (" ALL heads:"); emailPartLogInfo.append ("\r\n"); @SuppressWarnings("rawtypes") Enumeration allHead = msg.getAllHeaders(); if (allHead != null){ while (allHead.hasMoreElements()){ Header head = (Header)allHead.nextElement(); emailPartLogInfo.append (" "); emailPartLogInfo.append (head.getName()); emailPartLogInfo.append (":"); emailPartLogInfo.append (head.getValue()); emailPartLogInfo.append ("\r\n"); } } emailPartLogInfo.append ("\r\n"); if (EmailSrv.isBinaryPart (msg) && (msg.getDisposition() == null || msg.getDisposition().trim().equals(""))) { log.warning("can't detect attach type"); } if (EmailSrv.isBinaryPart (msg) && Part.INLINE.equalsIgnoreCase(msg.getDisposition()) && EmailSrv.getContentID (msg) == null){ log.warning("an inline content but has no content-id"); } log.info(emailPartLogInfo.toString()); } /** * Get mail session * @return mail session * @throws Exception */ protected Session getMailSession() throws Exception { if (mailSession != null) return mailSession; // Session Properties props = new Properties(); props.putAll(System.getProperties()); String protocol = "imap"; if (isSSL){ protocol = "imaps"; } props.put("mail.store.protocol", protocol); props.put("mail.host", imapHost); props.put("mail."+protocol+".port", imapPort); MAuthorizationAccount authAccount = MAuthorizationAccount.getEMailAccount(imapUser); boolean isOAuth2 = (authAccount != null); if (isOAuth2) { props.put("mail."+protocol+".ssl.enable", "true"); props.put("mail."+protocol+".auth.mechanisms", "XOAUTH2"); imapPass = authAccount.refreshAndGetAccessToken(); } mailSession = Session.getInstance(props); mailSession.setDebug(CLogMgt.isLevelFinest()); return mailSession; } // getSession /** * Get mail store * @return mail store * @throws Exception */ public Store getMailStore() throws Exception { if (mailStore != null) return mailStore; mailStore = getMailSession().getStore(); mailStore.connect(imapHost, imapUser, imapPass); return mailStore; } // getStore /** * Close mail store */ public void clearResource (){ if (mailStore != null && mailStore.isConnected()){ try { mailStore.close(); } catch (MessagingException e) { e.printStackTrace(); } } } /** * Open a mail store folder in read/write mode. * @param mailStore * @param folderName open nest folder by use format folder1/folder2/folder3 * @param isNestInbox in case true, open folder start from default inbox, other open from root folder * @param createWhenNonExists in case true, create folder by hierarchy if not exists, other not exists will make exception * @return folder opened in r/w model * @throws MessagingException */ public static Folder getFolder (Store mailStore, String folderName, Boolean isNestInbox, boolean createWhenNonExists) throws MessagingException{ if (folderName == null || "".equals(folderName.trim())){ throw new AdempiereException("Can't open a folder with empty name"); } char folderSeparate = '\\'; Folder openFolder = null; if (isNestInbox){ Folder inboxFolder = mailStore.getDefaultFolder(); if (!inboxFolder.exists()){ throw new AdempiereException("This mail account hasn't an inbox folder"); } folderSeparate = inboxFolder.getSeparator(); openFolder = inboxFolder.getFolder(folderName.replace('\\', folderSeparate)); }else{ String [] lsFolderName = folderName.split("\\\\"); if (lsFolderName.length > 0){ Folder testFolder = mailStore.getFolder(lsFolderName[0]); folderSeparate = testFolder.getSeparator(); folderName = folderName.replace('\\', folderSeparate); } openFolder = mailStore.getFolder(folderName); } openFolder = mailStore.getFolder(folderName); if (!openFolder.exists()){ if (createWhenNonExists){ if (!openFolder.create(Folder.HOLDS_MESSAGES)){ throw new AdempiereException("folder doesn't exist and can't create:" + folderName); } }else{ throw new AdempiereException("doesn't exists folder:" + folderName); } } openFolder.open(Folder.READ_WRITE); return openFolder; } /** * Read an email folder, with each email inject object processEmail for processing.
* In case error, close folder or close session (by disconnect) with retry of 3 times.
* When error with 5 continue message, stop process. * @param emailSrv * @param folderName folder name can hierarchy by use "\" * @param isNestInbox true in case start folder from inbox * @param processEmailHandle * @return true if success */ public static boolean readEmailFolder (EmailSrv emailSrv, String folderName, Boolean isNestInbox, ProcessEmailHandle processEmailHandle){ Message [] lsMsg = null; Folder readerFolder = null; Store mailStore = null; ClassLoader tcl = null; try{ tcl = Thread.currentThread().getContextClassLoader(); try { Thread.currentThread().setContextClassLoader(javax.mail.Session.class.getClassLoader()); mailStore = emailSrv.getMailStore(); readerFolder = EmailSrv.getFolder(mailStore, folderName, isNestInbox, false); lsMsg = readerFolder.getMessages(); } catch (MessagingException e) { e.printStackTrace(); emailSrv.clearResource(); throw new AdempiereException(e.getMessage()); }catch (AdempiereException appEx){ throw appEx; } catch (Exception e) { e.printStackTrace(); emailSrv.clearResource(); throw new AdempiereException(e.getMessage()); } int numOfTry = 0; int numeOfContinueErrorEmail = 0; for (int i = 0; i < lsMsg.length; ){ EmailContent processEmail = null; Message readerMsg = lsMsg [i]; try { // reopen store if (!mailStore.isConnected()) mailStore.connect(); // reopen reader folder if (!readerFolder.isOpen()){ readerFolder.open(Folder.READ_WRITE); } // reopen extra folder if (processEmailHandle != null && processEmailHandle.getListFolder() != null && processEmailHandle.getListFolder().size() > 0){ for (Folder exFolder : processEmailHandle.getListFolder()){ if (!exFolder.isOpen()){ exFolder.open(Folder.READ_WRITE); } } } processEmail = EmailSrv.processMessage(readerMsg, processEmailHandle, mailStore, readerFolder); i++; numOfTry = 0; numeOfContinueErrorEmail = 0; } catch (Exception e) { if ((e instanceof FolderClosedException || e instanceof StoreClosedException || e instanceof IOException) && numOfTry < 3){ log.warning("network disconnect, retry read email"); // by connect error, sleep for 30s before retry try { Thread.sleep(5000); numOfTry++; } catch (InterruptedException e1) { e1.printStackTrace(); } }else{ numeOfContinueErrorEmail++; i++; // call handle error after try 3 time try { processEmailHandle.processEmailError(processEmail, readerMsg, mailStore, readerFolder); } catch (MessagingException e1) { if (processEmail == null){ log.log(Level.SEVERE, String.format("can't complete handle error when process message with exception:%1$s", e1.getMessage())); }else{ log.log(Level.SEVERE, String.format("can't complete handle error when process message with exception:%1$s-%2$s-%3$s", processEmail.subject, processEmail.messageID, e1.getMessage())); } } if (e instanceof FolderClosedException || e instanceof StoreClosedException || e instanceof IOException){ throw new AdempiereException("can't reopen email store for process after three tries"); } // stop when has more 5 continue message error if (numeOfContinueErrorEmail > 5){ emailSrv.clearResource(); throw new AdempiereException("have 5 email errors when process"); } } } } emailSrv.clearResource(); }finally{ Thread.currentThread().setContextClassLoader(tcl); } return true; } /** * @see #processMessage(Message, ProcessEmailHandle, Store, Folder) * just manipulate message * @param msg * @return * @throws MessagingException */ public static EmailContent processMessage (Message msg) throws MessagingException, IOException{ return EmailSrv.processMessage (msg, null, null, null); } /** * Process message * @param msg * @param evaluateEmailHead * @return return EmailInfo contain info of email, in case evaluateEmailHead make cancel, return null * @throws MessagingException */ public static EmailContent processMessage (Message msg, ProcessEmailHandle evaluateEmailHead, Store mailStore, Folder mailFolder) throws MessagingException, IOException{ EmailContent emailInfo = new EmailContent(); // set from address Address[] from = msg.getFrom(); if (from != null){ for (Address fromAddress : from){ String address = null; if (fromAddress.toString().startsWith("<") && fromAddress.toString().endsWith(">")) { address = fromAddress.toString().substring(1, fromAddress.toString().length() - 1); } else { address = fromAddress.toString(); } emailInfo.fromAddress.add(address); } } // get message-id String [] lsMessageId = EmailSrv.getPartHeader(msg, "Message-ID"); if (lsMessageId != null){ emailInfo.messageID = lsMessageId[0]; } emailInfo.subject = msg.getSubject(); emailInfo.sentDate = msg.getSentDate(); if (evaluateEmailHead != null){ if (evaluateEmailHead.checkEmailHeader(emailInfo, msg)){ return null; } } EmailSrv.analysisEmailStructure(msg, emailInfo, true); if (evaluateEmailHead != null){ evaluateEmailHead.processEmailContent(emailInfo, msg, mailStore, mailFolder); } return emailInfo; } /** * @see #analysisEmailStructure(Part, EmailContent, boolean) * @param msg * @param emailContent * @throws MessagingException * @throws IOException */ public static void analysisEmailStructure (Part msg, EmailContent emailContent) throws MessagingException, IOException{ EmailSrv.analysisEmailStructure (msg, emailContent, false); } /** * Analysis {@link Part} object.
* Get content in plan or html text.
* Detect type of attached file and put it in to {@link EmailContent} for later processing. * @param msg mime part to analysis * @param emailContent object contain result analysis * @param isRoot true when part is {@link Message} * @throws MessagingException * @throws IOException */ public static void analysisEmailStructure (Part msg, EmailContent emailContent, boolean isRoot) throws MessagingException, IOException { logMailPartInfo (msg, log); boolean isUnknowPart = false; // [text/*] match with every mime of text, example: text/[plan, html, txt,..] if (msg.isMimeType("text/*")) { // is a text file attach if (Part.ATTACHMENT.equalsIgnoreCase(msg.getDisposition())){ if (msg instanceof BodyPart){ emailContent.lsAttachPart.add((BodyPart)msg); }else{ log.warning("can't detect where this file from"); } return; } // content is not cache, because in case use content many time, // consider save it to local variable, don't call getContent many times // http://www.oracle.com/technetwork/java/javamail/faq/index.html#cache String txtContent = msg.getContent().toString(); if (txtContent != null && msg.isMimeType("text/html")){ emailContent.htmlContentBuild.append(EmailSrv.getTextFromMailPart (msg)); }else if (txtContent != null){ emailContent.textContentBuil.append(EmailSrv.getTextFromMailPart (msg)); }else{ log.info("has non content in this part"); } } else if (msg.isMimeType("message/rfc822")) // Nested in multipart/digest { //TODO: html format will lost this content? must test log.warning("check html content of message/rfc822"); emailContent.textContentBuil.append(msg.getContent()); } else if (msg.isMimeType("multipart/*")) { // when message is multipart, process each part to get content (text, embed, attach,..) Multipart mp = (Multipart)msg.getContent(); int count = mp.getCount(); for (int i = 0; i < count; i++) { BodyPart part = mp.getBodyPart(i); EmailSrv.analysisEmailStructure(part, emailContent); } } else if (isBinaryPart (msg)) // attachment part { if (msg instanceof BodyPart){ BodyPart attachPart = (BodyPart)msg; if (attachPart.getDisposition() == null || attachPart.getDisposition().equalsIgnoreCase(Part.INLINE)){ emailContent.lsEmbedPart.add(attachPart); }else if (attachPart.getDisposition().equalsIgnoreCase(Part.ATTACHMENT)){ emailContent.lsAttachPart.add(attachPart); }else{ isUnknowPart = true; } }else{ log.warning ("TODO:content type is a binary, but isn't a instance of BodyPart"); } }else { isUnknowPart = true; } if (isUnknowPart){ emailContent.lsUnknowPart.add(msg); log.warning ("an unknown part, this content will miss"); } } // getMessage /** * http://www.oracle.com/technetwork/java/javamail/faq/index.html#unsupen * @param txtPart * @return * @throws MessagingException * @throws IOException */ public static String getTextFromMailPart (Part txtPart) throws MessagingException, IOException{ String text = null; try { Object content = txtPart.getContent(); if (content != null) text = content.toString(); } catch (UnsupportedEncodingException uex) { log.info("http://www.oracle.com/technetwork/java/javamail/faq/index.html#unsupen"); log.warning(uex.getMessage()); /* * Read the input stream into a byte array. * Choose a charset in some heuristic manner, use * that charset in the java.lang.String constructor * to convert the byte array into a String. */ // get charset of text in email ContentType cType = new ContentType(txtPart.getContentType()); String emailCharsetStr = cType.getParameter("charset"); String javaCharset = MimeUtility.javaCharset(emailCharsetStr); Charset emailCharset = Charset.forName("ISO_8859_1") ; if (Charset.isSupported(javaCharset)){ emailCharset = Charset.forName(javaCharset); } log.warning("try read with charset " + emailCharset.displayName() + " maybe make break text"); // read text from input stream with String str = null; StringBuilder sb = new StringBuilder(8192); InputStream is = null; try { is = txtPart.getInputStream(); BufferedReader bufferReader = new BufferedReader(new InputStreamReader(is, emailCharset)); while ((str = bufferReader.readLine()) != null) { sb.append(str); } } catch (IOException e) { throw e; } finally { try{ if (is != null) is.close(); }catch(IOException ex){} } text = sb.toString(); } return text; } /** * Read binary attachment from a multi-part * @param binaryPart * @return * @throws IOException * @throws MessagingException */ public static byte[] getBinaryData (Part binaryPart) throws IOException, MessagingException{ InputStream in = null; try{ in = binaryPart.getInputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream(); final int BUF_SIZE = 1 << 8; //1KiB buffer byte[] buffer = new byte[BUF_SIZE]; int bytesRead = -1; while((bytesRead = in.read(buffer)) > -1) { out.write(buffer, 0, bytesRead); } return out.toByteArray(); }catch (IOException ioe){ log.warning("exception when read attach in email"); throw ioe; }finally{ try{ if (in != null) in.close(); }catch (Exception ex){} } } /** * Download attached file and convert to base64 encoding * @param mailPart * @return base64 encoded content * @throws IOException * @throws MessagingException */ public static String getBinaryAsBASE64 (BodyPart mailPart) throws IOException, MessagingException{ // can improve by: when getEncode of mimeBodyPart is base64, read direct to ignore encorder, decorder return javax.xml.bind.DatatypeConverter.printBase64Binary(EmailSrv.getBinaryData(mailPart)); } /** * {@link #embedImgToEmail(String, ProvideBase64Data, String)} * use default pattern for embed image is "\\s+src\\s*=\\s*\"cid:(.*?)\""); * @param mailContent * @param provideBase64Data * @return * @throws Exception */ public static String embedImgToEmail (String mailContent, ProvideBase64Data provideBase64Data) throws Exception{ return EmailSrv.embedImgToEmail (mailContent, provideBase64Data, "\\s+src\\s*=\\s*\"cid:(.*?)\""); } /** * Find in mailContent pattern of embedded image.
* For each of them, replace cid by base64 data.
* Preview in cfEditor pattern is "\\s+src\\s*=\\s*\"cid:(.*?)\""
* With embedded image in gmail, pattern is "\\s+src\\s*=\\s*3D\\s*\"cid:(.*?)\""
* with embedded image in other server (nmicoud), pattern is "\\s+src\\s*=\\s*\"cid:(.*?)\""
* REMEMBER:cid:(.*?) must in group 1 * @param mailContent * @param provideBase64Data * @param embedPattern * @return * @throws Exception */ public static String embedImgToEmail (String mailContent, ProvideBase64Data provideBase64Data, String embedPattern) throws MessagingException, IOException{ String origonSign = mailContent; // pattern to get src value of attach image. Pattern imgPattern = Pattern.compile(embedPattern); // matcher object to anlysic image tab in sign Matcher imgMatcher = imgPattern.matcher(origonSign); // part not include "cid:imageName" List lsPart = new ArrayList (); // list image name in sign List lsImgSrc = new ArrayList (); // start index of text part not include "cid:imageName" int startIndex = 0; // start index of "cid:imageName" int startIndexMatch = 0; // end index of "cid:imageName" int endIndexMatch = 0; // split sign string to part // example: acb def ghi // lsPart will include "acb def ghi" // lsImgSrc wil include "image1", "image2" while (imgMatcher.find()){ startIndexMatch = imgMatcher.start(); endIndexMatch = imgMatcher.end(); // split text from end last matcher to start matcher String startString = origonSign.substring(startIndex, startIndexMatch); lsPart.add(startString); // get image name lsImgSrc.add(imgMatcher.group(1).trim()); startIndex = endIndexMatch; } // end string not include "cid:imageName" String startString = origonSign.substring(startIndex); lsPart.add(startString); // no image in sign return origon if (lsPart.size() == 0 || lsImgSrc.size() == 0){ return origonSign; } StringBuilder reconstructSign = new StringBuilder(); // reconstruct with image source convert to embed image by base64 encode for (int i = 0; i < lsImgSrc.size(); i++){ if (i == 0) reconstructSign.append(lsPart.get(0)); String imageBase64 = provideBase64Data.getBase64Data(lsImgSrc.get(i));; if (imageBase64 == null){ // no attach map with this src value // add server warning and return origon without src value, // maybe can improve to remove img tag //TODO: add server warning log log.warning("miss data of image has id is:" + lsImgSrc.get(i)); }else{ // convert image to base64 encode and embed to img tag reconstructSign.append(" alt=\"inline_image_").append(lsImgSrc.get(i)).append("\" src=\"data:image/jpeg;base64,").append(imageBase64).append("\""); } reconstructSign.append(lsPart.get(i + 1)); } return reconstructSign.toString(); } /** * Get embedded images * @param mailContent * @param provideBase64Data * @param embedPattern * @return list of embedded image part * @throws MessagingException * @throws IOException */ public static ArrayList getEmbededImages(String mailContent, ProvideBase64Data provideBase64Data, String embedPattern) throws MessagingException, IOException { ArrayList bodyPartImagesList = new ArrayList(); String origonSign = mailContent; // pattern to get src value of attach image. Pattern imgPattern = Pattern.compile(embedPattern); // matcher object to anlysic image tab in sign Matcher imgMatcher = imgPattern.matcher(origonSign); // list image name in sign List lsImgSrc = new ArrayList (); while (imgMatcher.find()){ // get image name lsImgSrc.add(imgMatcher.group(1).trim()); } // end string not include "cid:imageName" // no image in sign return origon if (lsImgSrc.size() == 0){ return bodyPartImagesList; } // reconstruct with image source convert to embed image by base64 encode for (int i = 0; i < lsImgSrc.size(); i++){ BodyPart image = provideBase64Data.getBodyPart(lsImgSrc.get(i)); if (image == null){ log.warning("miss data of image has id is:" + lsImgSrc.get(i)); }else{ // convert image to base64 encode and embed to img tag bodyPartImagesList.add(image); } } return bodyPartImagesList; } /** * Is binaryPart a binary Part * @param binaryPart * @return true if it is a binary part * @throws MessagingException */ public static boolean isBinaryPart (Part binaryPart) throws MessagingException{ return binaryPart.isMimeType("application/*") || binaryPart.isMimeType ("image/*"); } /** * Get contentID from header, with each inline attachment, will have a contentID value. * In case value at contentID difference from value at X-Attachment-Id, must manual recheck to add process. * @param attachPart * @return * @throws MessagingException */ public static String getContentID (Part attachPart) throws MessagingException{ String [] lsContentID = attachPart.getHeader("Content-Id"); String contentID = null; // get content value from header Content-Id if (lsContentID != null){ for (String contentValue : lsContentID){ if (contentValue != null && !"".equals(contentValue.trim())){ if (contentID != null && !contentID.equals(contentValue.trim())){ log.warning("has difference value of Content-Id"); } contentID = contentValue; } } } // content-id in format , because, remove < and > if (contentID != null){ if (contentID.startsWith("<") && contentID.endsWith(">")) contentID = contentID.substring(1, contentID.length() - 1); } // get content value from header X-Attachment-Id lsContentID = attachPart.getHeader("X-Attachment-Id"); if (lsContentID != null){ for (String contentValue : lsContentID){ if (contentValue != null && !"".equals(contentValue.trim())){ if (contentID != null && !contentID.equals(contentValue.trim())){ log.warning("value of X-Attachment-Id difference value of Content-Id"); } if (contentID == null){ contentID = contentValue; } } } } return contentID; } /** * Get part headers * @param msg * @param headerName * @return * @throws MessagingException */ public static String [] getPartHeader (Part msg, String headerName) throws MessagingException{ String [] headers = msg.getHeader(headerName); if (headers != null){ for (int i = 0; i < headers.length; i++){ String head = headers[i]; if (head.toString().startsWith("<") && head.endsWith(">")) { head = head.substring(1, head.length() - 1); headers [i] = head; } } } return headers; } //============helper class=========== /** * When process an email content, sometimes we wish to embed image as base64 string to mail.
* Source of image can come from many where. this interface for abstract source. * @author hieplq * */ public static interface ProvideBase64Data { public String getBase64Data (String dataId) throws MessagingException, IOException; public BodyPart getBodyPart (String dataId) throws MessagingException, IOException; } /** * This class inject to email reading process ({@link EmailSrv#processMessage(Message, ProcessEmailHandle, Store, Folder)}) * @author hieplq * */ public static interface ProcessEmailHandle { /** * after read header of email (from, subject, message_id,...), * will call this function to evaluate will continue process or cancel this email * at this time, in EmailInfo just has header info, content and attach is not manipulate * @param emailHeader * @param emailRaw * @return * @throws MessagingException */ public boolean checkEmailHeader (EmailContent emailHeader, Message emailRaw) throws MessagingException; /** * when read email for process, after some time try when has error, will call this function to ensure this email is can't process * @param emailHeader * @param emailRaw * @param mailStore * @param mailFolder * @throws MessagingException */ public void processEmailError (EmailContent emailHeader, Message emailRaw, Store mailStore, Folder mailFolder) throws MessagingException; /** * main where to process email. this time, every email info is manipulate to emailContent * @param emailContent * @param emailRaw * @param mailStore * @param mailFolder * @throws MessagingException */ public void processEmailContent (EmailContent emailContent, Message emailRaw, Store mailStore, Folder mailFolder) throws MessagingException, IOException; /** * List all folder use when process message * this function make handle close folder and close session can reopen it. * @return */ public List getListFolder (); } /** * {@docRoot} * this class implement source of image from attachment of email * @author hieplq * */ public static class EmailEmbedProvideBase64Data implements ProvideBase64Data{ private EmailContent emailContent; public EmailEmbedProvideBase64Data(EmailContent emailContent){ this.emailContent = emailContent; } /** * get image from image embed in email content by its content_id * download it and convert to string base64 * @param contentId * @return null when can't find attach has this contentId */ public String getBase64Data (String contentId) throws MessagingException, IOException{ if (contentId == null) return null; for (BodyPart imageEmbed : emailContent.lsEmbedPart){ if (contentId.equalsIgnoreCase(EmailSrv.getContentID(imageEmbed))){ return EmailSrv.getBinaryAsBASE64(imageEmbed); } } return null; } @Override public BodyPart getBodyPart(String contentId) throws MessagingException, IOException { if (contentId == null) return null; for (BodyPart imageEmbed : emailContent.lsEmbedPart){ if (contentId.equalsIgnoreCase(EmailSrv.getContentID(imageEmbed))){ return imageEmbed; } } return null; } } /** * Manipulate from {@link Message}
* Separate attached file to embed, attach, un-know list. * @author hieplq * */ public static class EmailContent { /** * contain list from address. * when @see javax.mail.Message#getFrom() return null, this list is empty * */ public List fromAddress = new ArrayList(); /** * unique value. has max length is 998 charater * http://tools.ietf.org/html/rfc4130#section-5.3.3 */ public String messageID; public String subject; public Date sentDate; /** * use to build content, to get content call {@link #getTextContent()} */ public StringBuilder textContentBuil = new StringBuilder(); /** * use to build content, to get content call {@link #getHtmlContent(boolean)} */ public StringBuilder htmlContentBuild = new StringBuilder(); /** * list attach file */ public List lsAttachPart = new ArrayList(); /** * list embed file */ public List lsEmbedPart = new ArrayList(); /** * list part unknown to process */ public List lsUnknowPart = new ArrayList(); /** * Get html content, when withEmbedImg = true, convert embedded image to base64 and embed to html content. * @param withEmbedImg * @return return null when has empty content * @throws Exception */ public String getHtmlContent (boolean withEmbedImg) throws MessagingException, IOException{ if (htmlContentBuild == null || htmlContentBuild.length() == 0) return null; EmailEmbedProvideBase64Data provideBase64Data = new EmailEmbedProvideBase64Data(this); return EmailSrv.embedImgToEmail(htmlContentBuild.toString(), provideBase64Data, "\\s+src\\s*=\\s*(?:3D)?\\s*\"cid:(.*?)\""); } /** * Get embedded image parts * @return * @throws MessagingException * @throws IOException */ public ArrayList getHTMLImageBodyParts() throws MessagingException, IOException{ if (htmlContentBuild == null || htmlContentBuild.length() == 0) return null; EmailEmbedProvideBase64Data provideBase64Data = new EmailEmbedProvideBase64Data(this); return EmailSrv.getEmbededImages(htmlContentBuild.toString(), provideBase64Data, "\\s+src\\s*=\\s*(?:3D)?\\s*\"cid:(.*?)\""); } /** * Get text content * @return return null when has no content */ public String getTextContent (){ //TODO: when email has only html content, consider convert to text content return textContentBuil.toString(); } } }