Home | History | Annotate | Download | only in util
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.tradefed.util;
     18 
     19 import com.android.ddmlib.Log;
     20 
     21 import java.io.BufferedInputStream;
     22 import java.io.BufferedOutputStream;
     23 import java.io.IOException;
     24 import java.util.ArrayList;
     25 import java.util.Arrays;
     26 import java.util.Collection;
     27 import java.util.Iterator;
     28 import java.util.List;
     29 
     30 /**
     31  * A helper class to send an email.  Note that this class is NOT PLATFORM INDEPENDENT.  It will
     32  * likely fail on Windows, and possibly on Mac OS X as well.  It will fail on any machine where
     33  * The binary pointed at by the {@code mailer} constant doesn't exist.
     34  */
     35 public class Email implements IEmail {
     36     private static final String LOG_TAG = "Email";
     37     private static final String[] mailer = {"/usr/sbin/sendmail", "-t", "-i"};
     38     static final String CRLF = "\r\n";
     39 
     40     private static String join(Collection<String> list, String sep) {
     41         StringBuilder builder = new StringBuilder();
     42         Iterator<String> iter = list.iterator();
     43         while (iter.hasNext()) {
     44             String element = iter.next();
     45             builder.append(element);
     46             if(iter.hasNext()) {
     47                 builder.append(sep);
     48             }
     49         }
     50         return builder.toString();
     51     }
     52 
     53     /**
     54      * A helper method to use ProcessBuilder to create a new process.  This can't use
     55      * {@link com.android.tradefed.util.IRunUtil} because that class doesn't provide a way to pass
     56      * data to the stdin of the spawned process, which is the usage paradigm for most commandline
     57      * mailers such as mailx and sendmail.
     58      * <p/>
     59      * Exposed for mocking
     60      *
     61      * @param cmd The {@code String[]} to pass to the {@link ProcessBuilder} constructor
     62      * @return The {@link Process} returned from from {@link ProcessBuilder#start()}
     63      * @throws IOException if sending email failed in a synchronously-detectable way
     64      */
     65     Process run(String[] cmd) throws IOException {
     66         ProcessBuilder pb = new ProcessBuilder(cmd);
     67         pb.redirectErrorStream(true);
     68         return pb.start();
     69     }
     70 
     71     /**
     72      * A small helper function that adds the specified header to the header list only if the value
     73      * is non-null
     74      */
     75     private void addHeader(List<String> headers, String name, String value) {
     76         if (name == null || value == null) return;
     77         headers.add(String.format("%s: %s", name, value));
     78     }
     79 
     80     /**
     81      * A small helper function that adds the specified header to the header list only if the value
     82      * is non-null
     83      */
     84     private void addHeaders(List<String> headers, String name, Collection<String> values) {
     85         if (name == null || values == null) return;
     86         if (values.isEmpty()) return;
     87 
     88         final String strValues = join(values, ",");
     89         headers.add(String.format("%s: %s", name, strValues));
     90     }
     91 
     92     /**
     93      * {@inheritDoc}
     94      */
     95     @Override
     96     public void send(Message msg) throws IllegalArgumentException, IOException {
     97         // Sanity checks
     98         if (msg.getTo() == null) {
     99             throw new IllegalArgumentException("Message is missing a destination");
    100         } else if (msg.getSubject() == null) {
    101             throw new IllegalArgumentException("Message is missing a subject");
    102         } else if (msg.getBody() == null) {
    103             throw new IllegalArgumentException("Message is missing a body");
    104         }
    105 
    106         // Sender, Recipients, CC, BCC, Subject are all set with appropriate email headers
    107         final ArrayList<String> headers = new ArrayList<String>();
    108         final String[] mailCmd;
    109         if (msg.getSender() != null) {
    110             addHeader(headers, "From", msg.getSender());
    111 
    112             // Envelope Sender (will receive any errors related to the email)
    113             int cmdLen = mailer.length + 2;
    114             mailCmd = Arrays.copyOf(mailer, cmdLen);
    115             mailCmd[cmdLen - 2] = "-f";
    116             mailCmd[cmdLen - 1] = msg.getSender();
    117         } else {
    118             mailCmd = mailer;
    119         }
    120         addHeaders(headers, "To", msg.getTo());
    121         addHeaders(headers, "Cc", msg.getCc());
    122         addHeaders(headers, "Bcc", msg.getBcc());
    123         addHeader(headers, "Content-type", msg.getContentType());
    124         addHeader(headers, "Subject", msg.getSubject());
    125 
    126         final StringBuilder fullMsg = new StringBuilder();
    127         fullMsg.append(join(headers, CRLF));
    128         fullMsg.append(CRLF);
    129         fullMsg.append(CRLF);
    130         fullMsg.append(msg.getBody());
    131 
    132         Log.d(LOG_TAG, String.format("About to send email with command: %s",
    133                 Arrays.toString(mailCmd)));
    134         Process mailerProc = run(mailCmd);
    135         BufferedOutputStream mailerStdin = new BufferedOutputStream(mailerProc.getOutputStream());
    136         /* There is no such thing as a "character" in the land of the shell; there are only bytes.
    137          * Here, we convert the body from a Java string (consisting of characters) to a byte array
    138          * encoding each character with UTF-8.  Each character will be represented as between one
    139          * and four bytes apiece.
    140          */
    141         mailerStdin.write(fullMsg.toString().getBytes("UTF-8"));
    142         mailerStdin.flush();
    143         mailerStdin.close();
    144 
    145         int retValue;
    146         try {
    147             retValue = mailerProc.waitFor();
    148         } catch (InterruptedException e) {
    149             // ignore, but set retValue to something bogus
    150             retValue = -12345;
    151         }
    152         if (retValue != 0) {
    153             Log.e(LOG_TAG, String.format("Mailer finished with non-zero return value: %d", retValue));
    154             BufferedInputStream mailerStdout = new BufferedInputStream(mailerProc.getInputStream());
    155             StringBuilder stdout = new StringBuilder();
    156             int theByte;
    157             while((theByte = mailerStdout.read()) != -1) {
    158                 stdout.append((char)theByte);
    159             }
    160             Log.e(LOG_TAG, "Mailer output was: " + stdout.toString());
    161         } else {
    162             Log.v(LOG_TAG, "Mailer returned successfully.");
    163         }
    164     }
    165 }
    166 
    167