13f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen/* 23f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * Copyright (C) 2015 The Android Open Source Project 33f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * 43f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * Licensed under the Apache License, Version 2.0 (the "License"); 53f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * you may not use this file except in compliance with the License. 63f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * You may obtain a copy of the License at 73f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * 83f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * http://www.apache.org/licenses/LICENSE-2.0 93f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * 103f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * Unless required by applicable law or agreed to in writing, software 113f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * distributed under the License is distributed on an "AS IS" BASIS, 123f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 133f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * See the License for the specific language governing permissions and 143f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * limitations under the License. 153f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen */ 163f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chenpackage com.android.phone.common.mail.internet; 173f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 183f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chenimport com.android.phone.common.mail.Address; 193f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chenimport com.android.phone.common.mail.Body; 203f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chenimport com.android.phone.common.mail.BodyPart; 213f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chenimport com.android.phone.common.mail.Message; 223f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chenimport com.android.phone.common.mail.MessagingException; 233f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chenimport com.android.phone.common.mail.Multipart; 243f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chenimport com.android.phone.common.mail.Part; 253f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chenimport com.android.phone.common.mail.utils.LogUtils; 263f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 273f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chenimport org.apache.james.mime4j.BodyDescriptor; 283f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chenimport org.apache.james.mime4j.ContentHandler; 293f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chenimport org.apache.james.mime4j.EOLConvertingInputStream; 303f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chenimport org.apache.james.mime4j.MimeStreamParser; 313f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chenimport org.apache.james.mime4j.field.DateTimeField; 323f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chenimport org.apache.james.mime4j.field.Field; 333f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 343f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chenimport android.text.TextUtils; 353f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 363f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chenimport java.io.BufferedWriter; 373f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chenimport java.io.IOException; 383f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chenimport java.io.InputStream; 393f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chenimport java.io.OutputStream; 403f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chenimport java.io.OutputStreamWriter; 413f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chenimport java.text.SimpleDateFormat; 423f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chenimport java.util.Date; 433f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chenimport java.util.Locale; 443f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chenimport java.util.Stack; 453f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chenimport java.util.regex.Pattern; 463f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 473f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen/** 483f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * An implementation of Message that stores all of its metadata in RFC 822 and 493f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * RFC 2045 style headers. 503f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * 513f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * NOTE: Automatic generation of a local message-id is becoming unwieldy and should be removed. 523f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * It would be better to simply do it explicitly on local creation of new outgoing messages. 533f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen */ 543f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chenpublic class MimeMessage extends Message { 553f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen private MimeHeader mHeader; 563f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen private MimeHeader mExtendedHeader; 573f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 583f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen // NOTE: The fields here are transcribed out of headers, and values stored here will supersede 593f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen // the values found in the headers. Use caution to prevent any out-of-phase errors. In 603f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen // particular, any adds/changes/deletes here must be echoed by changes in the parse() function. 613f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen private Address[] mFrom; 623f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen private Address[] mTo; 633f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen private Address[] mCc; 643f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen private Address[] mBcc; 653f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen private Address[] mReplyTo; 663f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen private Date mSentDate; 673f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen private Body mBody; 683f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen protected int mSize; 693f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen private boolean mInhibitLocalMessageId = false; 703f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen private boolean mComplete = true; 713f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 723f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen // Shared random source for generating local message-id values 733f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen private static final java.util.Random sRandom = new java.util.Random(); 743f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 753f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen // In MIME, en_US-like date format should be used. In other words "MMM" should be encoded to 763f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen // "Jan", not the other localized format like "Ene" (meaning January in locale es). 773f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen // This conversion is used when generating outgoing MIME messages. Incoming MIME date 783f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen // headers are parsed by org.apache.james.mime4j.field.DateTimeField which does not have any 793f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen // localization code. 803f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen private static final SimpleDateFormat DATE_FORMAT = 813f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.US); 823f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 833f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen // regex that matches content id surrounded by "<>" optionally. 843f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen private static final Pattern REMOVE_OPTIONAL_BRACKETS = Pattern.compile("^<?([^>]+)>?$"); 853f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen // regex that matches end of line. 863f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen private static final Pattern END_OF_LINE = Pattern.compile("\r?\n"); 873f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 883f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public MimeMessage() { 893f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen mHeader = null; 903f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 913f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 923f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen /** 933f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * Generate a local message id. This is only used when none has been assigned, and is 943f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * installed lazily. Any remote (typically server-assigned) message id takes precedence. 953f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * @return a long, locally-generated message-ID value 963f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen */ 973f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen private static String generateMessageId() { 983f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen final StringBuilder sb = new StringBuilder(); 993f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen sb.append("<"); 1003f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen for (int i = 0; i < 24; i++) { 1013f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen // We'll use a 5-bit range (0..31) 1023f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen final int value = sRandom.nextInt() & 31; 1033f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen final char c = "0123456789abcdefghijklmnopqrstuv".charAt(value); 1043f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen sb.append(c); 1053f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 1063f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen sb.append("."); 1073f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen sb.append(Long.toString(System.currentTimeMillis())); 1083f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen sb.append("@email.android.com>"); 1093f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen return sb.toString(); 1103f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 1113f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 1123f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen /** 1133f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * Parse the given InputStream using Apache Mime4J to build a MimeMessage. 1143f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * 1153f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * @param in InputStream providing message content 1163f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * @throws IOException 1173f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * @throws MessagingException 1183f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen */ 1193f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public MimeMessage(InputStream in) throws IOException, MessagingException { 1203f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen parse(in); 1213f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 1223f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 1233f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen private MimeStreamParser init() { 1243f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen // Before parsing the input stream, clear all local fields that may be superceded by 1253f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen // the new incoming message. 1263f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen getMimeHeaders().clear(); 1273f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen mInhibitLocalMessageId = true; 1283f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen mFrom = null; 1293f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen mTo = null; 1303f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen mCc = null; 1313f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen mBcc = null; 1323f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen mReplyTo = null; 1333f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen mSentDate = null; 1343f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen mBody = null; 1353f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 1363f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen final MimeStreamParser parser = new MimeStreamParser(); 1373f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen parser.setContentHandler(new MimeMessageBuilder()); 1383f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen return parser; 1393f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 1403f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 1413f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen protected void parse(InputStream in) throws IOException, MessagingException { 1423f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen final MimeStreamParser parser = init(); 1433f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen parser.parse(new EOLConvertingInputStream(in)); 1443f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen mComplete = !parser.getPrematureEof(); 1453f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 1463f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 1473f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public void parse(InputStream in, EOLConvertingInputStream.Callback callback) 1483f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen throws IOException, MessagingException { 1493f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen final MimeStreamParser parser = init(); 1503f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen parser.parse(new EOLConvertingInputStream(in, getSize(), callback)); 1513f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen mComplete = !parser.getPrematureEof(); 1523f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 1533f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 1543f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen /** 1553f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * Return the internal mHeader value, with very lazy initialization. 1563f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * The goal is to save memory by not creating the headers until needed. 1573f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen */ 1583f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen private MimeHeader getMimeHeaders() { 1593f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen if (mHeader == null) { 1603f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen mHeader = new MimeHeader(); 1613f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 1623f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen return mHeader; 1633f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 1643f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 1653f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen @Override 1663f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public Date getReceivedDate() throws MessagingException { 1673f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen return null; 1683f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 1693f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 1703f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen @Override 1713f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public Date getSentDate() throws MessagingException { 1723f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen if (mSentDate == null) { 1733f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen try { 1743f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen DateTimeField field = (DateTimeField)Field.parse("Date: " 1753f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen + MimeUtility.unfoldAndDecode(getFirstHeader("Date"))); 1763f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen mSentDate = field.getDate(); 1773f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen // TODO: We should make it more clear what exceptions can be thrown here, 1783f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen // and whether they reflect a normal or error condition. 1793f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } catch (Exception e) { 1803f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen LogUtils.v(LogUtils.TAG, "Message missing Date header"); 1813f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 1823f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 1833f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen if (mSentDate == null) { 1843f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen // If we still don't have a date, fall back to "Delivery-date" 1853f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen try { 1863f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen DateTimeField field = (DateTimeField)Field.parse("Date: " 1873f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen + MimeUtility.unfoldAndDecode(getFirstHeader("Delivery-date"))); 1883f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen mSentDate = field.getDate(); 1893f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen // TODO: We should make it more clear what exceptions can be thrown here, 1903f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen // and whether they reflect a normal or error condition. 1913f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } catch (Exception e) { 1923f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen LogUtils.v(LogUtils.TAG, "Message also missing Delivery-Date header"); 1933f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 1943f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 1953f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen return mSentDate; 1963f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 1973f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 1983f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen @Override 1993f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public void setSentDate(Date sentDate) throws MessagingException { 2003f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen setHeader("Date", DATE_FORMAT.format(sentDate)); 2013f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen this.mSentDate = sentDate; 2023f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 2033f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 2043f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen @Override 2053f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public String getContentType() throws MessagingException { 2063f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen final String contentType = getFirstHeader(MimeHeader.HEADER_CONTENT_TYPE); 2073f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen if (contentType == null) { 2083f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen return "text/plain"; 2093f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } else { 2103f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen return contentType; 2113f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 2123f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 2133f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 2143f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen @Override 2153f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public String getDisposition() throws MessagingException { 2163f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen return getFirstHeader(MimeHeader.HEADER_CONTENT_DISPOSITION); 2173f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 2183f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 2193f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen @Override 2203f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public String getContentId() throws MessagingException { 2213f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen final String contentId = getFirstHeader(MimeHeader.HEADER_CONTENT_ID); 2223f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen if (contentId == null) { 2233f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen return null; 2243f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } else { 2253f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen // remove optionally surrounding brackets. 2263f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen return REMOVE_OPTIONAL_BRACKETS.matcher(contentId).replaceAll("$1"); 2273f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 2283f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 2293f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 2303f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public boolean isComplete() { 2313f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen return mComplete; 2323f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 2333f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 2343f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen @Override 2353f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public String getMimeType() throws MessagingException { 2363f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen return MimeUtility.getHeaderParameter(getContentType(), null); 2373f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 2383f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 2393f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen @Override 2403f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public int getSize() throws MessagingException { 2413f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen return mSize; 2423f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 2433f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 2443f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen /** 2453f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * Returns a list of the given recipient type from this message. If no addresses are 2463f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * found the method returns an empty array. 2473f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen */ 2483f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen @Override 2493f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public Address[] getRecipients(String type) throws MessagingException { 2503f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen if (type == RECIPIENT_TYPE_TO) { 2513f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen if (mTo == null) { 2523f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen mTo = Address.parse(MimeUtility.unfold(getFirstHeader("To"))); 2533f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 2543f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen return mTo; 2553f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } else if (type == RECIPIENT_TYPE_CC) { 2563f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen if (mCc == null) { 2573f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen mCc = Address.parse(MimeUtility.unfold(getFirstHeader("CC"))); 2583f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 2593f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen return mCc; 2603f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } else if (type == RECIPIENT_TYPE_BCC) { 2613f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen if (mBcc == null) { 2623f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen mBcc = Address.parse(MimeUtility.unfold(getFirstHeader("BCC"))); 2633f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 2643f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen return mBcc; 2653f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } else { 2663f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen throw new MessagingException("Unrecognized recipient type."); 2673f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 2683f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 2693f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 2703f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen @Override 2713f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public void setRecipients(String type, Address[] addresses) throws MessagingException { 2723f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen final int TO_LENGTH = 4; // "To: " 2733f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen final int CC_LENGTH = 4; // "Cc: " 2743f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen final int BCC_LENGTH = 5; // "Bcc: " 2753f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen if (type == RECIPIENT_TYPE_TO) { 2763f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen if (addresses == null || addresses.length == 0) { 2773f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen removeHeader("To"); 2783f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen this.mTo = null; 2793f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } else { 2803f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen setHeader("To", MimeUtility.fold(Address.toHeader(addresses), TO_LENGTH)); 2813f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen this.mTo = addresses; 2823f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 2833f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } else if (type == RECIPIENT_TYPE_CC) { 2843f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen if (addresses == null || addresses.length == 0) { 2853f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen removeHeader("CC"); 2863f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen this.mCc = null; 2873f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } else { 2883f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen setHeader("CC", MimeUtility.fold(Address.toHeader(addresses), CC_LENGTH)); 2893f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen this.mCc = addresses; 2903f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 2913f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } else if (type == RECIPIENT_TYPE_BCC) { 2923f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen if (addresses == null || addresses.length == 0) { 2933f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen removeHeader("BCC"); 2943f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen this.mBcc = null; 2953f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } else { 2963f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen setHeader("BCC", MimeUtility.fold(Address.toHeader(addresses), BCC_LENGTH)); 2973f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen this.mBcc = addresses; 2983f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 2993f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } else { 3003f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen throw new MessagingException("Unrecognized recipient type."); 3013f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 3023f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 3033f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 3043f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen /** 3053f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * Returns the unfolded, decoded value of the Subject header. 3063f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen */ 3073f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen @Override 3083f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public String getSubject() throws MessagingException { 3093f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen return MimeUtility.unfoldAndDecode(getFirstHeader("Subject")); 3103f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 3113f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 3123f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen @Override 3133f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public void setSubject(String subject) throws MessagingException { 3143f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen final int HEADER_NAME_LENGTH = 9; // "Subject: " 3153f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen setHeader("Subject", MimeUtility.foldAndEncode2(subject, HEADER_NAME_LENGTH)); 3163f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 3173f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 3183f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen @Override 3193f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public Address[] getFrom() throws MessagingException { 3203f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen if (mFrom == null) { 3213f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen String list = MimeUtility.unfold(getFirstHeader("From")); 3223f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen if (list == null || list.length() == 0) { 3233f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen list = MimeUtility.unfold(getFirstHeader("Sender")); 3243f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 3253f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen mFrom = Address.parse(list); 3263f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 3273f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen return mFrom; 3283f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 3293f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 3303f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen @Override 3313f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public void setFrom(Address from) throws MessagingException { 3323f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen final int FROM_LENGTH = 6; // "From: " 3333f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen if (from != null) { 3343f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen setHeader("From", MimeUtility.fold(from.toHeader(), FROM_LENGTH)); 3353f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen this.mFrom = new Address[] { 3363f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen from 3373f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen }; 3383f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } else { 3393f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen this.mFrom = null; 3403f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 3413f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 3423f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 3433f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen @Override 3443f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public Address[] getReplyTo() throws MessagingException { 3453f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen if (mReplyTo == null) { 3463f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen mReplyTo = Address.parse(MimeUtility.unfold(getFirstHeader("Reply-to"))); 3473f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 3483f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen return mReplyTo; 3493f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 3503f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 3513f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen @Override 3523f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public void setReplyTo(Address[] replyTo) throws MessagingException { 3533f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen final int REPLY_TO_LENGTH = 10; // "Reply-to: " 3543f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen if (replyTo == null || replyTo.length == 0) { 3553f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen removeHeader("Reply-to"); 3563f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen mReplyTo = null; 3573f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } else { 3583f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen setHeader("Reply-to", MimeUtility.fold(Address.toHeader(replyTo), REPLY_TO_LENGTH)); 3593f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen mReplyTo = replyTo; 3603f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 3613f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 3623f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 3633f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen /** 3643f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * Set the mime "Message-ID" header 3653f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * @param messageId the new Message-ID value 3663f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * @throws MessagingException 3673f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen */ 3683f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen @Override 3693f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public void setMessageId(String messageId) throws MessagingException { 3703f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen setHeader("Message-ID", messageId); 3713f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 3723f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 3733f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen /** 3743f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * Get the mime "Message-ID" header. This value will be preloaded with a locally-generated 3753f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * random ID, if the value has not previously been set. Local generation can be inhibited/ 3763f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * overridden by explicitly clearing the headers, removing the message-id header, etc. 3773f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * @return the Message-ID header string, or null if explicitly has been set to null 3783f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen */ 3793f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen @Override 3803f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public String getMessageId() throws MessagingException { 3813f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen String messageId = getFirstHeader("Message-ID"); 3823f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen if (messageId == null && !mInhibitLocalMessageId) { 3833f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen messageId = generateMessageId(); 3843f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen setMessageId(messageId); 3853f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 3863f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen return messageId; 3873f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 3883f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 3893f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen @Override 3903f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public void saveChanges() throws MessagingException { 3913f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen throw new MessagingException("saveChanges not yet implemented"); 3923f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 3933f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 3943f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen @Override 3953f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public Body getBody() throws MessagingException { 3963f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen return mBody; 3973f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 3983f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 3993f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen @Override 4003f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public void setBody(Body body) throws MessagingException { 4013f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen this.mBody = body; 4023f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen if (body instanceof Multipart) { 4033f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen final Multipart multipart = ((Multipart)body); 4043f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen multipart.setParent(this); 4053f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen setHeader(MimeHeader.HEADER_CONTENT_TYPE, multipart.getContentType()); 4063f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen setHeader("MIME-Version", "1.0"); 4073f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 4083f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen else if (body instanceof TextBody) { 4093f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen setHeader(MimeHeader.HEADER_CONTENT_TYPE, String.format("%s;\n charset=utf-8", 4103f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen getMimeType())); 4113f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "base64"); 4123f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 4133f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 4143f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 4153f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen protected String getFirstHeader(String name) throws MessagingException { 4163f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen return getMimeHeaders().getFirstHeader(name); 4173f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 4183f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 4193f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen @Override 4203f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public void addHeader(String name, String value) throws MessagingException { 4213f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen getMimeHeaders().addHeader(name, value); 4223f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 4233f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 4243f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen @Override 4253f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public void setHeader(String name, String value) throws MessagingException { 4263f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen getMimeHeaders().setHeader(name, value); 4273f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 4283f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 4293f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen @Override 4303f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public String[] getHeader(String name) throws MessagingException { 4313f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen return getMimeHeaders().getHeader(name); 4323f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 4333f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 4343f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen @Override 4353f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public void removeHeader(String name) throws MessagingException { 4363f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen getMimeHeaders().removeHeader(name); 4373f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen if ("Message-ID".equalsIgnoreCase(name)) { 4383f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen mInhibitLocalMessageId = true; 4393f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 4403f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 4413f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 4423f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen /** 4433f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * Set extended header 4443f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * 4453f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * @param name Extended header name 4463f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * @param value header value - flattened by removing CR-NL if any 4473f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * remove header if value is null 4483f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * @throws MessagingException 4493f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen */ 4503f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen @Override 4513f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public void setExtendedHeader(String name, String value) throws MessagingException { 4523f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen if (value == null) { 4533f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen if (mExtendedHeader != null) { 4543f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen mExtendedHeader.removeHeader(name); 4553f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 4563f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen return; 4573f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 4583f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen if (mExtendedHeader == null) { 4593f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen mExtendedHeader = new MimeHeader(); 4603f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 4613f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen mExtendedHeader.setHeader(name, END_OF_LINE.matcher(value).replaceAll("")); 4623f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 4633f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 4643f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen /** 4653f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * Get extended header 4663f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * 4673f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * @param name Extended header name 4683f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * @return header value - null if header does not exist 4693f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * @throws MessagingException 4703f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen */ 4713f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen @Override 4723f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public String getExtendedHeader(String name) throws MessagingException { 4733f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen if (mExtendedHeader == null) { 4743f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen return null; 4753f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 4763f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen return mExtendedHeader.getFirstHeader(name); 4773f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 4783f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 4793f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen /** 4803f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * Set entire extended headers from String 4813f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * 4823f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * @param headers Extended header and its value - "CR-NL-separated pairs 4833f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * if null or empty, remove entire extended headers 4843f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * @throws MessagingException 4853f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen */ 4863f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public void setExtendedHeaders(String headers) throws MessagingException { 4873f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen if (TextUtils.isEmpty(headers)) { 4883f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen mExtendedHeader = null; 4893f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } else { 4903f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen mExtendedHeader = new MimeHeader(); 4913f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen for (final String header : END_OF_LINE.split(headers)) { 4923f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen final String[] tokens = header.split(":", 2); 4933f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen if (tokens.length != 2) { 4943f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen throw new MessagingException("Illegal extended headers: " + headers); 4953f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 4963f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen mExtendedHeader.setHeader(tokens[0].trim(), tokens[1].trim()); 4973f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 4983f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 4993f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 5003f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 5013f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen /** 5023f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * Get entire extended headers as String 5033f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * 5043f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * @return "CR-NL-separated extended headers - null if extended header does not exist 5053f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen */ 5063f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public String getExtendedHeaders() { 5073f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen if (mExtendedHeader != null) { 5083f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen return mExtendedHeader.writeToString(); 5093f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 5103f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen return null; 5113f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 5123f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 5133f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen /** 5143f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * Write message header and body to output stream 5153f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * 5163f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen * @param out Output steam to write message header and body. 5173f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen */ 5183f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen @Override 5193f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public void writeTo(OutputStream out) throws IOException, MessagingException { 5203f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen final BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out), 1024); 5213f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen // Force creation of local message-id 5223f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen getMessageId(); 5233f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen getMimeHeaders().writeTo(out); 5243f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen // mExtendedHeader will not be write out to external output stream, 5253f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen // because it is intended to internal use. 5263f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen writer.write("\r\n"); 5273f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen writer.flush(); 5283f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen if (mBody != null) { 5293f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen mBody.writeTo(out); 5303f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 5313f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 5323f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 5333f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen @Override 5343f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public InputStream getInputStream() throws MessagingException { 5353f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen return null; 5363f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 5373f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 5383f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen class MimeMessageBuilder implements ContentHandler { 5393f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen private final Stack<Object> stack = new Stack<Object>(); 5403f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 5413f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public MimeMessageBuilder() { 5423f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 5433f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 5443f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen private void expect(Class<?> c) { 5453f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen if (!c.isInstance(stack.peek())) { 5463f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen throw new IllegalStateException("Internal stack error: " + "Expected '" 5473f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen + c.getName() + "' found '" + stack.peek().getClass().getName() + "'"); 5483f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 5493f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 5503f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 5513f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen @Override 5523f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public void startMessage() { 5533f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen if (stack.isEmpty()) { 5543f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen stack.push(MimeMessage.this); 5553f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } else { 5563f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen expect(Part.class); 5573f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen try { 5583f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen final MimeMessage m = new MimeMessage(); 5593f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen ((Part)stack.peek()).setBody(m); 5603f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen stack.push(m); 5613f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } catch (MessagingException me) { 5623f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen throw new Error(me); 5633f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 5643f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 5653f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 5663f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 5673f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen @Override 5683f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public void endMessage() { 5693f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen expect(MimeMessage.class); 5703f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen stack.pop(); 5713f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 5723f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 5733f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen @Override 5743f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public void startHeader() { 5753f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen expect(Part.class); 5763f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 5773f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 5783f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen @Override 5793f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public void field(String fieldData) { 5803f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen expect(Part.class); 5813f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen try { 5823f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen final String[] tokens = fieldData.split(":", 2); 5833f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen ((Part)stack.peek()).addHeader(tokens[0], tokens[1].trim()); 5843f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } catch (MessagingException me) { 5853f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen throw new Error(me); 5863f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 5873f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 5883f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 5893f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen @Override 5903f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public void endHeader() { 5913f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen expect(Part.class); 5923f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 5933f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 5943f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen @Override 5953f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public void startMultipart(BodyDescriptor bd) { 5963f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen expect(Part.class); 5973f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 5983f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen final Part e = (Part)stack.peek(); 5993f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen try { 6003f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen final MimeMultipart multiPart = new MimeMultipart(e.getContentType()); 6013f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen e.setBody(multiPart); 6023f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen stack.push(multiPart); 6033f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } catch (MessagingException me) { 6043f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen throw new Error(me); 6053f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 6063f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 6073f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 6083f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen @Override 6093f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public void body(BodyDescriptor bd, InputStream in) throws IOException { 6103f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen expect(Part.class); 6113f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen final Body body = MimeUtility.decodeBody(in, bd.getTransferEncoding()); 6123f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen try { 6133f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen ((Part)stack.peek()).setBody(body); 6143f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } catch (MessagingException me) { 6153f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen throw new Error(me); 6163f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 6173f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 6183f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 6193f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen @Override 6203f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public void endMultipart() { 6213f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen stack.pop(); 6223f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 6233f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 6243f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen @Override 6253f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public void startBodyPart() { 6263f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen expect(MimeMultipart.class); 6273f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 6283f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen try { 6293f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen final MimeBodyPart bodyPart = new MimeBodyPart(); 6303f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen ((MimeMultipart)stack.peek()).addBodyPart(bodyPart); 6313f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen stack.push(bodyPart); 6323f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } catch (MessagingException me) { 6333f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen throw new Error(me); 6343f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 6353f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 6363f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 6373f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen @Override 6383f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public void endBodyPart() { 6393f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen expect(BodyPart.class); 6403f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen stack.pop(); 6413f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 6423f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 6433f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen @Override 6443f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public void epilogue(InputStream is) throws IOException { 6453f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen expect(MimeMultipart.class); 6463f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen final StringBuilder sb = new StringBuilder(); 6473f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen int b; 6483f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen while ((b = is.read()) != -1) { 6493f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen sb.append((char)b); 6503f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 6513f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen // TODO: why is this commented out? 6523f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen // ((Multipart) stack.peek()).setEpilogue(sb.toString()); 6533f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 6543f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 6553f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen @Override 6563f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public void preamble(InputStream is) throws IOException { 6573f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen expect(MimeMultipart.class); 6583f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen final StringBuilder sb = new StringBuilder(); 6593f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen int b; 6603f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen while ((b = is.read()) != -1) { 6613f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen sb.append((char)b); 6623f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 6633f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen try { 6643f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen ((MimeMultipart)stack.peek()).setPreamble(sb.toString()); 6653f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } catch (MessagingException me) { 6663f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen throw new Error(me); 6673f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 6683f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 6693f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen 6703f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen @Override 6713f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen public void raw(InputStream is) throws IOException { 6723f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen throw new UnsupportedOperationException("Not supported"); 6733f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 6743f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen } 6753f51d09afa61649a1bcf02599bc1df5aafccf088Nancy Chen} 676