com.tct.emailcommon.internet.Rfc822Output.java Source code

Java tutorial

Introduction

Here is the source code for com.tct.emailcommon.internet.Rfc822Output.java

Source

/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/******************************************************************************/
/*                                                               Date:04/2014 */
/*                                PRESENTATION                                */
/*                                                                            */
/*       Copyright 2014 TCL Communication Technology Holdings Limited.        */
/*                                                                            */
/* This material is company confidential, cannot be reproduced in any form    */
/* without the written permission of TCL Communication Technology Holdings    */
/* Limited.                                                                   */
/*                                                                            */
/* -------------------------------------------------------------------------- */
/*  Author :  fu.zhang                                                        */
/*  Email  :  fu.zhang@jrdcom.com                                             */
/*  Role   :                                                                  */
/*  Reference documents :                                                     */
/* -------------------------------------------------------------------------- */
/*  Comments :                                                                */
/*  File     :                                                                */
/*  Labels   :                                                                */
/* -------------------------------------------------------------------------- */
/* ========================================================================== */
/*     Modifications on Features list / Changes Request / Problems Report     */
/* -------------------------------------------------------------------------- */
/*    date   |        author        |         Key          |     comment      */
/* ----------|----------------------|----------------------|----------------- */
/* 04/03/2014|fu.zhang              |622697                |[HOMO][HOMO][Oran */
/*           |                      |                      |ge][Homologation] */
/*           |                      |                      | Exchange Active  */
/*           |                      |                      |Sync Priority     */
/* ----------|----------------------|----------------------|----------------- */
/*
 ==========================================================================
 *HISTORY
 *
 *Tag           Date         Author        Description
 *============== ============ =============== ==============================
 *CONFLICT-50003 2014/11/04   zhaotianyong     Modify the package conflict
 *BUGFIX-861660  2014/12/05   junwei-xu       [Android5.0][Email] Display Chinese audio name as chaos.
 *BUGFIX-962573  2013/03/31   zheng.zou       [REG][Email]Table change to text after mail is forwarded.
 *BUGFIX-964415  2015/04/04   zheng.zou       [HOMO][EMAIL] [ALWE] other received emails text are added to email when you answer one email
 *BUGFIX-962560  2015/04/14   zhaotianyong    [Email]Can not attach the inner picture when Fwd/Reply/Reply all the HTML email
 ============================================================================
 */
/******************************************************************************/
package com.tct.emailcommon.internet;

import android.content.Context;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Base64OutputStream;

//TS: MOD by zhaotianyong for CONFLICT_50003 START
//import org.apache.commons.io.IOUtils;
//TS: MOD by zhaotianyong for CONFLICT_50003 START

import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
//[FEATURE]-Add-BEGIN by TCTNj.fu.zhang,04/03/2014,622697
import com.tct.emailcommon.mail.Address;
import com.tct.emailcommon.mail.MessagingException;
import com.tct.emailcommon.provider.EmailContent;
import com.tct.emailcommon.provider.EmailContent.Attachment;
import com.tct.emailcommon.provider.EmailContent.Body;
import com.tct.emailcommon.provider.EmailContent.Message;
import com.tct.emailcommon.utility.AttachmentUtilities;
import com.tct.mail.utils.IOUtils;
import com.tct.mail.utils.LogUtils;

//[FEATURE]-Add-END by TCTNB.fu.zhang
/**
 * Utility class to output RFC 822 messages from provider email messages
 */
public class Rfc822Output {
    private static final String TAG = "Email";

    // In MIME, en_US-like date format should be used. In other words "MMM" should be encoded to
    // "Jan", not the other localized format like "Ene" (meaning January in locale es).
    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z",
            Locale.US);

    /** A less-than-perfect pattern to pull out <body> content */
    private static final Pattern BODY_PATTERN = Pattern.compile("(?:<\\s*body[^>]*>)(.*)(?:<\\s*/\\s*body\\s*>)",
            Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
    /** Match group in {@code BODY_PATTERN} for the body HTML */
    private static final int BODY_PATTERN_GROUP = 1;
    /** Index of the plain text version of the message body */
    private final static int INDEX_BODY_TEXT = 0;
    /** Index of the HTML version of the message body */
    private final static int INDEX_BODY_HTML = 1;
    /** Single digit [0-9] to ensure uniqueness of the MIME boundary */
    /*package*/ static byte sBoundaryDigit;

    /**
     * Returns just the content between the <body></body> tags. This is not perfect and breaks
     * with malformed HTML or if there happens to be special characters in the attributes of
     * the <body> tag (e.g. a '>' in a java script block).
     */
    /*package*/ static String getHtmlBody(String html) {
        final Matcher match = BODY_PATTERN.matcher(html);
        if (match.find()) {
            return match.group(BODY_PATTERN_GROUP); // Found body; return
        } else {
            return html; // Body not found; return the full HTML and hope for the best
        }
    }

    /**
     * Gets both the plain text and HTML versions of the message body.
     */
    /*package*/ static String[] buildBodyText(Body body, boolean useSmartReply) {
        if (body == null) {
            return new String[2];
        }
        final String[] messageBody = new String[] { body.mTextContent, body.mHtmlContent };
        final int pos = body.mQuotedTextStartPos;
        if (useSmartReply && pos > 0) {
            // TS: zheng.zou 2015-04-04 EMAIL BUGFIX-964415 MOD_S
            //note: process the content of both html and text for later use
            if (messageBody[0] != null) {
                if (pos < messageBody[0].length()) {
                    messageBody[0] = messageBody[0].substring(0, pos);
                }
            }
            if (messageBody[1] != null) {
                if (pos < messageBody[1].length()) {
                    messageBody[1] = messageBody[1].substring(0, pos);
                }
            }
            // TS: zheng.zou 2015-04-04 EMAIL BUGFIX-964415 MOD_E
        }
        return messageBody;
    }

    /**
     * Write the entire message to an output stream.  This method provides buffering, so it is
     * not necessary to pass in a buffered output stream here.
     *
     * @param context system context for accessing the provider
     * @param message the message to write out
     * @param out the output stream to write the message to
     * @param useSmartReply whether or not quoted text is appended to a reply/forward
     * @param sendBcc Whether to add the bcc header
     * @param attachments list of attachments to send (or null if retrieved from the message itself)
     */
    public static void writeTo(Context context, Message message, OutputStream out, boolean useSmartReply,
            boolean sendBcc, List<Attachment> attachments) throws IOException, MessagingException {
        if (message == null) {
            // throw something?
            return;
        }

        final OutputStream stream = new BufferedOutputStream(out, 1024);
        final Writer writer = new OutputStreamWriter(stream);

        // Write the fixed headers.  Ordering is arbitrary (the legacy code iterated through a
        // hashmap here).

        final String date = DATE_FORMAT.format(new Date(message.mTimeStamp));
        writeHeader(writer, "Date", date);

        writeEncodedHeader(writer, "Subject", message.mSubject);

        writeHeader(writer, "Message-ID", message.mMessageId);

        writeAddressHeader(writer, "From", message.mFrom);
        writeAddressHeader(writer, "To", message.mTo);
        writeAddressHeader(writer, "Cc", message.mCc);
        // Address fields.  Note that we skip bcc unless the sendBcc argument is true
        // SMTP should NOT send bcc headers, but EAS must send it!
        if (sendBcc) {
            writeAddressHeader(writer, "Bcc", message.mBcc);
        }
        writeAddressHeader(writer, "Reply-To", message.mReplyTo);
        //[FEATURE]-Add-BEGIN by TCTNj.fu.zhang,04/03/2014,622697
        if (message.mPriority == EmailContent.Message.FLAG_PRIORITY_HIGH) {
            writeHeader(writer, "Importance", "high");
            writeHeader(writer, "Priorty", "urgent");
        } else if (message.mPriority == EmailContent.Message.FLAG_PRIORITY_LOW) {
            writeHeader(writer, "Importance", "low");
            writeHeader(writer, "Priorty", "non-urgent");
        }
        //[FEATURE]-Add-END by TCTNB.fu.zhang
        writeHeader(writer, "MIME-Version", "1.0");

        // Analyze message and determine if we have multiparts
        final Body body = Body.restoreBodyWithMessageId(context, message.mId);
        final String[] bodyText = buildBodyText(body, useSmartReply);

        // If a list of attachments hasn't been passed in, build one from the message
        if (attachments == null) {
            attachments = Arrays.asList(Attachment.restoreAttachmentsWithMessageId(context, message.mId));
        }

        final boolean multipart = attachments.size() > 0;

        // Simplified case for no multipart - just emit text and be done.
        if (!multipart) {
            //TS: zhaotianyong 2015-04-14 EMAIL BUGFIX_962560 MOD_S
            writeTextWithHeaders(writer, stream, bodyText, null);
            //TS: zhaotianyong 2015-04-14 EMAIL BUGFIX_962560 MOD_E
        } else {
            // continue with multipart headers, then into multipart body
            final String multipartBoundary = getNextBoundary();
            String multipartType = "mixed";

            // Move to the first attachment; this must succeed because multipart is true
            if (attachments.size() == 1) {
                // If we've got one attachment and it's an ics "attachment", we want to send
                // this as multipart/alternative instead of multipart/mixed
                final int flags = attachments.get(0).mFlags;
                if ((flags & Attachment.FLAG_ICS_ALTERNATIVE_PART) != 0) {
                    multipartType = "alternative";
                }
            }

            writeHeader(writer, "Content-Type",
                    "multipart/" + multipartType + "; boundary=\"" + multipartBoundary + "\"");
            // Finish headers and prepare for body section(s)
            writer.write("\r\n");

            // first multipart element is the body
            if (bodyText[INDEX_BODY_TEXT] != null || bodyText[INDEX_BODY_HTML] != null) {
                writeBoundary(writer, multipartBoundary, false);
                //TS: zhaotianyong 2015-04-14 EMAIL BUGFIX_962560 MOD_S
                writeTextWithHeaders(writer, stream, bodyText, attachments);
                //TS: zhaotianyong 2015-04-14 EMAIL BUGFIX_962560 MOD_E
            }

            // Write out the attachments until we run out
            for (final Attachment att : attachments) {
                writeBoundary(writer, multipartBoundary, false);
                writeOneAttachment(context, writer, stream, att);
                writer.write("\r\n");
            }

            // end of multipart section
            writeBoundary(writer, multipartBoundary, true);
        }

        writer.flush();
        out.flush();
    }

    /**
     * Write a single attachment and its payload
     */
    private static void writeOneAttachment(Context context, Writer writer, OutputStream out, Attachment attachment)
            throws IOException, MessagingException {
        writeHeader(writer, "Content-Type", attachment.mMimeType + ";\n name=\"" +
        // TS: junwei-xu 2014-12-05 EMAIL BUGFIX_861660 MOD_S
                MimeUtility.foldAndEncode2(attachment.mFileName, "ContentType".length() + 2) + "\"");
        // TS: junwei-xu 2014-12-05 EMAIL BUGFIX_861660 MOD_E
        writeHeader(writer, "Content-Transfer-Encoding", "base64");
        // Most attachments (real files) will send Content-Disposition.  The suppression option
        // is used when sending calendar invites.
        if ((attachment.mFlags & Attachment.FLAG_ICS_ALTERNATIVE_PART) == 0) {
            writeHeader(writer, "Content-Disposition", "attachment;" + "\n filename=\"" +
            // TS: junwei-xu 2014-12-05 EMAIL BUGFIX_861660 MOD_S
                    MimeUtility.foldAndEncode2(attachment.mFileName, "ContentType".length() + 2)
                    // TS: junwei-xu 2014-12-05 EMAIL BUGFIX_861660 MOD_E
                    + "\";" + "\n size=" + Long.toString(attachment.mSize));
        }
        if (attachment.mContentId != null) {
            writeHeader(writer, "Content-ID", attachment.mContentId);
        }
        writer.append("\r\n");

        // Set up input stream and write it out via base64
        InputStream inStream = null;
        try {
            // Use content, if provided; otherwise, use the contentUri
            if (attachment.mContentBytes != null) {
                inStream = new ByteArrayInputStream(attachment.mContentBytes);
            } else {
                // First try the cached file
                final String cachedFile = attachment.getCachedFileUri();
                if (!TextUtils.isEmpty(cachedFile)) {
                    final Uri cachedFileUri = Uri.parse(cachedFile);
                    try {
                        inStream = context.getContentResolver().openInputStream(cachedFileUri);
                    } catch (FileNotFoundException e) {
                        // Couldn't open the cached file, fall back to the original content uri
                        inStream = null;

                        LogUtils.i(TAG, "Rfc822Output#writeOneAttachment(), failed to load"
                                + "cached file, falling back to: %s", attachment.getContentUri());
                    }
                }

                if (inStream == null) {
                    // try to open the file
                    final Uri fileUri = Uri.parse(attachment.getContentUri());
                    inStream = context.getContentResolver().openInputStream(fileUri);
                }
            }
            // switch to output stream for base64 text output
            writer.flush();
            Base64OutputStream base64Out = new Base64OutputStream(out, Base64.CRLF | Base64.NO_CLOSE);
            // copy base64 data and close up
            IOUtils.copy(inStream, base64Out);
            base64Out.close();

            // The old Base64OutputStream wrote an extra CRLF after
            // the output.  It's not required by the base-64 spec; not
            // sure if it's required by RFC 822 or not.
            out.write('\r');
            out.write('\n');
            out.flush();
        } catch (FileNotFoundException fnfe) {
            // Ignore this - empty file is OK
            LogUtils.e(TAG, fnfe,
                    "Rfc822Output#writeOneAttachment(), FileNotFoundException" + "when sending attachment");
        } catch (IOException ioe) {
            LogUtils.e(TAG, ioe, "Rfc822Output#writeOneAttachment(), IOException" + "when sending attachment");
            throw new MessagingException("Invalid attachment.", ioe);
        }
    }

    /**
     * Write a single header with no wrapping or encoding
     *
     * @param writer the output writer
     * @param name the header name
     * @param value the header value
     */
    private static void writeHeader(Writer writer, String name, String value) throws IOException {
        if (value != null && value.length() > 0) {
            writer.append(name);
            writer.append(": ");
            writer.append(value);
            writer.append("\r\n");
        }
    }

    /**
     * Write a single header using appropriate folding & encoding
     *
     * @param writer the output writer
     * @param name the header name
     * @param value the header value
     */
    private static void writeEncodedHeader(Writer writer, String name, String value) throws IOException {
        if (value != null && value.length() > 0) {
            writer.append(name);
            writer.append(": ");
            writer.append(MimeUtility.foldAndEncode2(value, name.length() + 2));
            writer.append("\r\n");
        }
    }

    /**
     * Unpack, encode, and fold address(es) into a header
     *
     * @param writer the output writer
     * @param name the header name
     * @param value the header value (a packed list of addresses)
     */
    private static void writeAddressHeader(Writer writer, String name, String value) throws IOException {
        if (value != null && value.length() > 0) {
            writer.append(name);
            writer.append(": ");
            writer.append(MimeUtility.fold(Address.reformatToHeader(value), name.length() + 2));
            writer.append("\r\n");
        }
    }

    /**
     * Write a multipart boundary
     *
     * @param writer the output writer
     * @param boundary the boundary string
     * @param end false if inner boundary, true if final boundary
     */
    private static void writeBoundary(Writer writer, String boundary, boolean end) throws IOException {
        writer.append("--");
        writer.append(boundary);
        if (end) {
            writer.append("--");
        }
        writer.append("\r\n");
    }

    /**
     * Write the body text.
     *
     * Note this always uses base64, even when not required.  Slightly less efficient for
     * US-ASCII text, but handles all formats even when non-ascii chars are involved.  A small
     * optimization might be to prescan the string for safety and send raw if possible.
     *
     * @param writer the output writer
     * @param out the output stream inside the writer (used for byte[] access)
     * @param bodyText Plain text and HTML versions of the original text of the message
     */
    private static void writeTextWithHeaders(Writer writer, OutputStream out, String[] bodyText,
            List<Attachment> atts) throws IOException {
        boolean html = false;
        String text = bodyText[INDEX_BODY_TEXT];
        // TS: zheng.zou 2015-03-31 EMAIL BUGFIX-962573 MOD_S
        String htmlText = bodyText[INDEX_BODY_HTML];
        if (TextUtils.isEmpty(text) || !TextUtils.isEmpty(htmlText)) {
            // TS: zheng.zou 2015-03-31 EMAIL BUGFIX-962573 MOD_E
            text = bodyText[INDEX_BODY_HTML];
            html = true;
        }
        //TS: zhaotianyong 2015-04-13 EMAIL BUGFIX_962560 ADD_S
        if (atts != null && html) {
            for (Attachment att : atts) {
                text = AttachmentUtilities.refactorHtmlBody(text, att);
            }
        }
        //TS: zhaotianyong 2015-04-13 EMAIL BUGFIX_962560 ADD_E
        if (TextUtils.isEmpty(text)) {
            writer.write("\r\n"); // a truly empty message
        } else {
            // first multipart element is the body
            final String mimeType = "text/" + (html ? "html" : "plain");
            writeHeader(writer, "Content-Type", mimeType + "; charset=utf-8");
            writeHeader(writer, "Content-Transfer-Encoding", "base64");
            writer.write("\r\n");
            final byte[] textBytes = text.getBytes("UTF-8");
            writer.flush();
            out.write(Base64.encode(textBytes, Base64.CRLF));
        }
    }

    /**
     * Returns a unique boundary string.
     */
    /*package*/ static String getNextBoundary() {
        final StringBuilder boundary = new StringBuilder();
        boundary.append("--_com.tct.email_").append(System.nanoTime());
        synchronized (Rfc822Output.class) {
            boundary.append(sBoundaryDigit);
            sBoundaryDigit = (byte) ((sBoundaryDigit + 1) % 10);
        }
        return boundary.toString();
    }
}