Java tutorial
/* * Copyright (c) 2014 Wael Chatila / Icegreen Technologies. All Rights Reserved. * This software is released under the Apache license 2.0 * This file has been modified by the copyright holder. * Original file can be found at http://james.apache.org */ package com.icegreen.greenmail.store; import com.icegreen.greenmail.mail.MailAddress; import com.icegreen.greenmail.util.GreenMailUtil; import org.apache.commons.lang3.StringEscapeUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.mail.BodyPart; import javax.mail.MessagingException; import javax.mail.internet.*; import java.util.*; /** * Attributes of a Message in IMAP4rev1 style. Message * Attributes should be set when a message enters a mailbox. * <p> Note that the message in a mailbox have the same order using either * Message Sequence Numbers or UIDs. * <p> reinitialize() must be called on deserialization to reset Logger * <p/> * Reference: RFC 2060 - para 2.3 * * @author <a href="mailto:sascha@kulawik.de">Sascha Kulawik</a> * @author <a href="mailto:charles@benett1.demon.co.uk">Charles Benett</a> * @version 0.2 on 04 Aug 2002 */ public class SimpleMessageAttributes implements MailMessageAttributes { // Logging. protected final Logger log = LoggerFactory.getLogger(getClass()); private static final String SP = " "; private static final String NIL = "NIL"; private static final String Q = "\""; private static final String LB = "("; private static final String RB = ")"; private static final String MULTIPART = "MULTIPART"; private static final String MESSAGE = "MESSAGE"; private int uid; private int messageSequenceNumber; private Date receivedDate; private String bodyStructure; private String envelope; private int size; private int lineCount; public MailMessageAttributes[] parts; private List headers; //rfc822 or MIME header fields //arrays only if multiple values allowed under rfc822 private String subject; private String[] from; private String[] sender; private String[] replyTo; private String[] to; private String[] cc; private String[] bcc; private String[] inReplyTo; private String[] date; private String[] messageID; private String contentType; private String primaryType; // parsed from contentType private String secondaryType; // parsed from contentType private Set parameters; // parsed from contentType private String contentID; private String contentDesc; private String contentEncoding; private String receivedDateString; private String sentDateEnvelopeString; private Header contentDisposition; SimpleMessageAttributes(MimeMessage msg, Date receivedDate) throws MessagingException { Date sentDate = getSentDate(msg, receivedDate); this.receivedDate = receivedDate; this.receivedDateString = new MailDateFormat().format(receivedDate); this.sentDateEnvelopeString = new MailDateFormat().format(sentDate); if (msg != null) { parseMimePart(msg); } } /** * Compute "sent" date * * @param msg Message to take sent date from. May be null to use default * @param defaultVal Default if sent date is not present * @return Sent date or now if no date could be found */ private static Date getSentDate(MimeMessage msg, Date defaultVal) { if (msg == null) { return defaultVal; } try { Date sentDate = msg.getSentDate(); if (sentDate == null) { return defaultVal; } else { return sentDate; } } catch (MessagingException me) { return new Date(); } } void setUID(int thisUID) { uid = thisUID; } /** * Parses key data items from a MimeMessage for seperate storage. * TODO this is a mess, and should be completely revamped. */ void parseMimePart(MimePart part) throws MessagingException { size = GreenMailUtil.getBody(part).length(); // Section 1 - Message Headers if (part instanceof MimeMessage) { try { subject = ((MimeMessage) part).getSubject(); } catch (MessagingException me) { // if (DEBUG) getLogger().debug("Messaging Exception for getSubject: " + me); } } try { from = part.getHeader("From"); } catch (MessagingException me) { // if (DEBUG) getLogger().debug("Messaging Exception for getHeader(From): " + me); } try { sender = part.getHeader("Sender"); } catch (MessagingException me) { // if (DEBUG) getLogger().debug("Messaging Exception for getHeader(Sender): " + me); } try { replyTo = part.getHeader("Reply To"); } catch (MessagingException me) { // if (DEBUG) getLogger().debug("Messaging Exception for getHeader(Reply To): " + me); } try { to = part.getHeader("To"); } catch (MessagingException me) { // if (DEBUG) getLogger().debug("Messaging Exception for getHeader(To): " + me); } try { cc = part.getHeader("Cc"); } catch (MessagingException me) { // if (DEBUG) getLogger().debug("Messaging Exception for getHeader(To): " + me); } try { bcc = part.getHeader("Bcc"); } catch (MessagingException me) { // if (DEBUG) getLogger().debug("Messaging Exception for getHeader(To): " + me); } try { inReplyTo = part.getHeader("In Reply To"); } catch (MessagingException me) { // if (DEBUG) getLogger().debug("Messaging Exception for getHeader(In Reply To): " + me); } try { date = part.getHeader("Date"); } catch (MessagingException me) { // if (DEBUG) getLogger().debug("Messaging Exception for getHeader(Date): " + me); } try { messageID = part.getHeader("Message-ID"); } catch (MessagingException me) { // if (DEBUG) getLogger().debug("Messaging Exception for getHeader(messageID): " + me); } String contentTypeLine = null; try { contentTypeLine = part.getContentType(); } catch (MessagingException me) { // if (DEBUG) getLogger().debug("Messaging Exception for getContentType(): " + me); } if (contentTypeLine != null) { decodeContentType(contentTypeLine); } try { contentID = part.getContentID(); } catch (MessagingException me) { // if (DEBUG) getLogger().debug("Messaging Exception for getContentUD(): " + me); } try { contentDesc = part.getDescription(); } catch (MessagingException me) { // if (DEBUG) getLogger().debug("Messaging Exception for getDescription(): " + me); } try { contentEncoding = part.getEncoding(); // default value. if (contentEncoding == null) { contentEncoding = "7BIT"; } } catch (MessagingException me) { // if (DEBUG) getLogger().debug("Messaging Exception for getEncoding(): " + me); } try { // contentDisposition = part.getDisposition(); contentDisposition = Header.create(part.getHeader("Content-Disposition")); } catch (MessagingException me) { if (log.isDebugEnabled()) { log.debug("Can not create content disposition for part " + part, me); } } try { // TODO this doesn't work lineCount = getLineCount(part); } catch (Exception e) { if (log.isDebugEnabled()) { log.debug("Can not get line count for part " + part, e); } } // Recurse through any embedded parts if (primaryType.equalsIgnoreCase(MULTIPART)) { MimeMultipart container; try { container = (MimeMultipart) part.getContent(); int count = container.getCount(); parts = new SimpleMessageAttributes[count]; for (int i = 0; i < count; i++) { BodyPart nextPart = container.getBodyPart(i); if (nextPart instanceof MimePart) { SimpleMessageAttributes partAttrs = new SimpleMessageAttributes(null, receivedDate); partAttrs.parseMimePart((MimePart) nextPart); parts[i] = partAttrs; } } } catch (Exception e) { if (log.isDebugEnabled()) { log.debug("Can not recurse through multipart content", e); } } } else if (primaryType.equalsIgnoreCase("message")) { if (secondaryType.equalsIgnoreCase("RFC822")) { //try { /* MimeMessageWrapper message = new MimeMessageWrapper(part.getInputStream()); SimpleMessageAttributes msgAttrs = new SimpleMessageAttributes(); msgAttrs.setAttributesFor(message); if (part instanceof MimeMessage) { Comments out because I don't know what it should do here MimeMessage msg1 = (MimeMessage) part; MimeMessageWrapper message2 = new MimeMessageWrapper(msg1); SimpleMessageAttributes msgAttrs2 = new SimpleMessageAttributes(); msgAttrs.setAttributesFor(message2); } parts = new SimpleMessageAttributes[1]; parts[0] = msgAttrs; */ //} catch (Exception e) { //getLogger().error("Error interpreting a message/rfc822: " + e); //e.printStackTrace(); //} // TODO: Warn till fixed! log.warn("Unknown/unhandled subtype " + secondaryType + " of message encountered."); } else { log.warn("Unknown/unhandled subtype " + secondaryType + " of message encountered."); } } } private int getLineCount(MimePart part) throws MessagingException { return GreenMailUtil.getLineCount(GreenMailUtil.getBody(part)); } /** * Builds IMAP envelope String from pre-parsed data. */ String parseEnvelope() { List<String> response = new ArrayList<String>(); //1. Date --------------- response.add(LB + Q + sentDateEnvelopeString + Q + SP); //2. Subject --------------- if (subject != null && (subject.length() != 0)) { response.add(Q + StringEscapeUtils.escapeJava(subject) + Q + SP); } else { response.add(NIL + SP); } //3. From --------------- if (from != null && from.length > 0) { response.add(LB); for (String aFrom : from) { response.add(parseAddress(aFrom)); } response.add(RB); } else { response.add(NIL); } response.add(SP); //4. Sender --------------- if (sender != null && sender.length > 0) { // if (DEBUG) getLogger().debug("parsingEnvelope - sender[0] is: " + sender[0]); //Check for Netscape feature - sender is local part only if (sender[0].indexOf('@') == -1) { response.add(LB + response.get(3) + RB); //first From address } else { response.add(LB); for (String aSender : sender) { response.add(parseAddress(aSender)); } response.add(RB); } } else { if (from != null && from.length > 0) { response.add(LB + response.get(3) + RB); //first From address } else { response.add(NIL); } } response.add(SP); if (replyTo != null && replyTo.length > 0) { if (replyTo[0].indexOf('@') == -1) { response.add(LB + response.get(3) + RB); //first From address } else { response.add(LB); for (String aReplyTo : replyTo) { response.add(parseAddress(aReplyTo)); } response.add(RB); } } else { if (from != null && from.length > 0) { response.add(LB + response.get(3) + RB); //first From address } else { response.add(NIL); } } response.add(SP); if (to != null && to.length > 0) { response.add(LB); for (String aTo : to) { response.add(parseAddress(aTo)); } response.add(RB); } else { response.add(NIL); } response.add(SP); if (cc != null && cc.length > 0) { response.add(LB); for (String aCc : cc) { response.add(parseAddress(aCc)); } response.add(RB); } else { response.add(NIL); } response.add(SP); if (bcc != null && bcc.length > 0) { response.add(LB); for (String aBcc : bcc) { response.add(parseAddress(aBcc)); } response.add(RB); } else { response.add(NIL); } response.add(SP); if (inReplyTo != null && inReplyTo.length > 0) { response.add(inReplyTo[0]); } else { response.add(NIL); } response.add(SP); if (messageID != null && messageID.length > 0) { messageID[0] = StringEscapeUtils.escapeJava(messageID[0]); response.add(Q + messageID[0] + Q); } else { response.add(NIL); } response.add(RB); StringBuilder buf = new StringBuilder(16 * response.size()); for (String aResponse : response) { buf.append(aResponse); } return buf.toString(); } /** * Parses a String email address to an IMAP address string. */ String parseAddress(String address) { int comma = address.indexOf(','); StringBuilder buf = new StringBuilder(); if (comma == -1) { //single address buf.append(LB); InternetAddress netAddr; try { netAddr = new InternetAddress(address); } catch (AddressException ae) { return null; } String personal = netAddr.getPersonal(); if (personal != null && (personal.length() != 0)) { buf.append(Q).append(personal).append(Q); } else { buf.append(NIL); } buf.append(SP); buf.append(NIL); // should add route-addr buf.append(SP); try { MailAddress mailAddr = new MailAddress(netAddr.getAddress()); buf.append(Q).append(mailAddr.getUser()).append(Q); buf.append(SP); buf.append(Q).append(mailAddr.getHost()).append(Q); } catch (Exception pe) { buf.append(NIL + SP + NIL); } buf.append(RB); } else { buf.append(parseAddress(address.substring(0, comma))); buf.append(SP); buf.append(parseAddress(address.substring(comma + 1))); } return buf.toString(); } /** * Decode a content Type header line into types and parameters pairs */ void decodeContentType(String rawLine) { int slash = rawLine.indexOf('/'); if (slash == -1) { // if (DEBUG) getLogger().debug("decoding ... no slash found"); return; } else { primaryType = rawLine.substring(0, slash).trim(); } int semicolon = rawLine.indexOf(';'); if (semicolon == -1) { // if (DEBUG) getLogger().debug("decoding ... no semicolon found"); secondaryType = rawLine.substring(slash + 1).trim(); return; } // have parameters secondaryType = rawLine.substring(slash + 1, semicolon).trim(); Header h = new Header(rawLine); parameters = h.getParams(); } String parseBodyFields() { StringBuilder buf = new StringBuilder(); getParameters(buf); buf.append(SP); if (contentID == null) { buf.append(NIL); } else { buf.append(Q).append(contentID).append(Q); } buf.append(SP); if (contentDesc == null) { buf.append(NIL); } else { buf.append(Q).append(contentDesc).append(Q); } buf.append(SP); if (contentEncoding == null) { buf.append(NIL); } else { buf.append(Q).append(contentEncoding).append(Q); } buf.append(SP); buf.append(size); return buf.toString(); } private void getParameters(StringBuilder buf) { if (parameters == null || parameters.isEmpty()) { buf.append(NIL); } else { buf.append(LB); Iterator it = parameters.iterator(); while (it.hasNext()) { buf.append((String) it.next()); // Space separated if (it.hasNext()) { buf.append(SP); } } buf.append(RB); } } /** * Produce the IMAP formatted String for the BodyStructure of a pre-parsed MimeMessage * TODO handle extension elements - Content-disposition, Content-Language and other parameters. */ String parseBodyStructure(boolean includeExtension) { try { String fields = parseBodyFields(); StringBuilder buf = new StringBuilder(); buf.append(LB); if (primaryType.equalsIgnoreCase("Text")) { buf.append("\"TEXT\" \""); buf.append(secondaryType.toUpperCase()); buf.append("\" "); buf.append(fields); buf.append(' '); buf.append(lineCount); // is: * 1 FETCH (BODYSTRUCTURE ("Text" "plain" NIL NIL NIL NIL 4 -1)) // wants: * 1 FETCH (BODYSTRUCTURE ("text" "plain" NIL NIL NIL "8bit" 6 1 NIL NIL NIL)) // or: * 1 FETCH (BODYSTRUCTURE ("text" "plain" NIL NIL NIL "7bit" 28 1 NIL NIL NIL)) } else if (primaryType.equalsIgnoreCase(MESSAGE) && secondaryType.equalsIgnoreCase("rfc822")) { buf.append("\"MESSAGE\" \"RFC822\" "); buf.append(fields).append(SP); // setupLogger(parts[0]); // reset transient logger buf.append(parts[0].getEnvelope()).append(SP); buf.append(parts[0].getBodyStructure(false)).append(SP); buf.append(lineCount); } else if (primaryType.equalsIgnoreCase(MULTIPART)) { for (MailMessageAttributes part : parts) { // setupLogger(parts[i]); // reset transient getLogger() buf.append(part.getBodyStructure(includeExtension)); } buf.append(SP + Q).append(secondaryType).append(Q); } else { //1. primary type ------- buf.append('\"'); buf.append(primaryType.toUpperCase()); buf.append('\"'); //2. sec type ------- buf.append(" \""); buf.append(secondaryType.toUpperCase()); buf.append('\"'); //3. params ------- buf.append(' '); getParameters(buf); //4. body id ------- buf.append(' '); buf.append(NIL); //5. content desc ------- buf.append(' '); if (null != contentDesc) { buf.append('\"'); buf.append(contentDesc); buf.append('\"'); } else { buf.append(NIL); } //6. encoding ------- buf.append(' '); if (null != contentEncoding) { buf.append('\"'); buf.append(contentEncoding); buf.append('\"'); } else { buf.append(NIL); } //7. size ------- buf.append(' '); buf.append(size); } if (includeExtension) { //extension is different for multipart and single parts if (primaryType.equalsIgnoreCase(MULTIPART)) { //8. ext1 params ------- buf.append(' '); getParameters(buf); //9. ext2 disposition ------- buf.append(' '); if (null != contentDisposition) { buf.append(contentDisposition); } else { buf.append(NIL); } //10. ext3 language ------- buf.append(' '); buf.append(NIL); } else { // ext1 md5 ------- buf.append(' '); buf.append(NIL); // ext2 disposition ------- buf.append(' '); if (null != contentDisposition) { buf.append(contentDisposition); } else { buf.append(NIL); } //ext3 language ------- buf.append(' '); buf.append(NIL); } } buf.append(RB); return buf.toString(); } catch (Exception e) { throw new RuntimeException("Can not parse body structure", e); } } /** * Provides the current Message Sequence Number for this message. MSNs * change when messages are expunged from the mailbox. * * @return int a positive non-zero integer */ public int getMessageSequenceNumber() { return messageSequenceNumber; } void setMessageSequenceNumber(int newMsn) { messageSequenceNumber = newMsn; } /** * Provides the unique identity value for this message. UIDs combined with * a UIDValidity value form a unique reference for a message in a given * mailbox. UIDs persist across sessions unless the UIDValidity value is * incremented. UIDs are not copied if a message is copied to another * mailbox. * * @return int a 32-bit value */ public int getUID() { return uid; } @Override public Date getReceivedDate() { return receivedDate; } @Override public String getReceivedDateAsString() { return receivedDateString; } @Override public int getSize() { return size; } @Override public String getEnvelope() { return parseEnvelope(); } @Override public String getBodyStructure(boolean includeExtensions) { return parseBodyStructure(includeExtensions); } //~ inner class private static class Header { String value; Set<String> params = null; public Header(String line) { String[] strs = line.split(";"); value = strs[0]; if (0 != strs.length) { params = new HashSet<String>(); for (int i = 1; i < strs.length; i++) { String p = strs[i].trim(); int e = p.indexOf('='); String key = p.substring(0, e); String val = p.substring(e + 1, p.length()); p = Q + strip(key) + Q + SP + Q + strip(val) + Q; params.add(p); } } } public Set getParams() { return params; } private String strip(String s) { return s.replaceAll("\\\"", ""); } public String toString() { StringBuilder ret = new StringBuilder(); if (null == params) { ret.append(Q).append(value).append(Q); } else { ret.append(LB); ret.append(Q).append(value).append(Q + SP); ret.append(LB); int i = 0; for (String param : params) { if (i++ > 0) { ret.append(SP); } ret.append(param); } ret.append(RB); ret.append(RB); } return ret.toString(); } public static Header create(String[] header) { if (null == header || 0 == header.length) { return null; } if (header.length > 1) { throw new IllegalArgumentException( "Header creation assumes only one occurrence of header instead of " + header.length); } return new Header(header[0]); } } }