• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.tradefed.log.LogUtil.CLog;
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[] mailer = {"/usr/sbin/sendmail", "-t", "-i"};
37     static final String CRLF = "\r\n";
38 
join(Collection<String> list, String sep)39     private static String join(Collection<String> list, String sep) {
40         StringBuilder builder = new StringBuilder();
41         Iterator<String> iter = list.iterator();
42         while (iter.hasNext()) {
43             String element = iter.next();
44             builder.append(element);
45             if(iter.hasNext()) {
46                 builder.append(sep);
47             }
48         }
49         return builder.toString();
50     }
51 
52     /**
53      * A helper method to use ProcessBuilder to create a new process.  This can't use
54      * {@link com.android.tradefed.util.IRunUtil} because that class doesn't provide a way to pass
55      * data to the stdin of the spawned process, which is the usage paradigm for most commandline
56      * mailers such as mailx and sendmail.
57      * <p/>
58      * Exposed for mocking
59      *
60      * @param cmd The {@code String[]} to pass to the {@link ProcessBuilder} constructor
61      * @return The {@link Process} returned from from {@link ProcessBuilder#start()}
62      * @throws IOException if sending email failed in a synchronously-detectable way
63      */
run(String[] cmd)64     Process run(String[] cmd) throws IOException {
65         ProcessBuilder pb = new ProcessBuilder(cmd);
66         pb.redirectErrorStream(true);
67         return pb.start();
68     }
69 
70     /**
71      * A small helper function that adds the specified header to the header list only if the value
72      * is non-null
73      */
addHeader(List<String> headers, String name, String value)74     private void addHeader(List<String> headers, String name, String value) {
75         if (name == null || value == null) {
76             return;
77         }
78         headers.add(String.format("%s: %s", name, value));
79     }
80 
81     /**
82      * A small helper function that adds the specified header to the header list only if the value
83      * is non-null
84      */
addHeaders(List<String> headers, String name, Collection<String> values)85     private void addHeaders(List<String> headers, String name, Collection<String> values) {
86         if (name == null || values == null) {
87             return;
88         }
89         if (values.isEmpty()) {
90             return;
91         }
92 
93         final String strValues = join(values, ",");
94         headers.add(String.format("%s: %s", name, strValues));
95     }
96 
97     /**
98      * {@inheritDoc}
99      */
100     @Override
send(Message msg)101     public void send(Message msg) throws IllegalArgumentException, IOException {
102         // Validity checks
103         if (msg.getTo() == null) {
104             throw new IllegalArgumentException("Message is missing a destination");
105         } else if (msg.getSubject() == null) {
106             throw new IllegalArgumentException("Message is missing a subject");
107         } else if (msg.getBody() == null) {
108             throw new IllegalArgumentException("Message is missing a body");
109         }
110 
111         // Sender, Recipients, CC, BCC, Subject are all set with appropriate email headers
112         final ArrayList<String> headers = new ArrayList<String>();
113         final String[] mailCmd;
114         if (msg.getSender() != null) {
115             addHeader(headers, "From", msg.getSender());
116 
117             // Envelope Sender (will receive any errors related to the email)
118             int cmdLen = mailer.length + 2;
119             mailCmd = Arrays.copyOf(mailer, cmdLen);
120             mailCmd[cmdLen - 2] = "-f";
121             mailCmd[cmdLen - 1] = msg.getSender();
122         } else {
123             mailCmd = mailer;
124         }
125         addHeaders(headers, "To", msg.getTo());
126         addHeaders(headers, "Cc", msg.getCc());
127         addHeaders(headers, "Bcc", msg.getBcc());
128         addHeader(headers, "Content-type", msg.getContentType());
129         addHeader(headers, "Subject", msg.getSubject());
130 
131         final StringBuilder fullMsg = new StringBuilder();
132         fullMsg.append(join(headers, CRLF));
133         fullMsg.append(CRLF);
134         fullMsg.append(CRLF);
135         fullMsg.append(msg.getBody());
136 
137         CLog.d("About to send email with command: %s", Arrays.toString(mailCmd));
138         Process mailerProc = run(mailCmd);
139         BufferedOutputStream mailerStdin = new BufferedOutputStream(mailerProc.getOutputStream());
140         /* There is no such thing as a "character" in the land of the shell; there are only bytes.
141          * Here, we convert the body from a Java string (consisting of characters) to a byte array
142          * encoding each character with UTF-8.  Each character will be represented as between one
143          * and four bytes apiece.
144          */
145         mailerStdin.write(fullMsg.toString().getBytes("UTF-8"));
146         mailerStdin.flush();
147         mailerStdin.close();
148 
149         int retValue;
150         try {
151             retValue = mailerProc.waitFor();
152         } catch (InterruptedException e) {
153             // ignore, but set retValue to something bogus
154             retValue = -12345;
155         }
156         if (retValue != 0) {
157             CLog.e("Mailer finished with non-zero return value: %d", retValue);
158             BufferedInputStream mailerStdout = new BufferedInputStream(mailerProc.getInputStream());
159             StringBuilder stdout = new StringBuilder();
160             int theByte;
161             while((theByte = mailerStdout.read()) != -1) {
162                 stdout.append((char)theByte);
163             }
164             CLog.e("Mailer output was: " + stdout.toString());
165         } else {
166             CLog.v("Mailer returned successfully.");
167         }
168     }
169 }
170 
171