1 /****************************************************************
2 * Licensed to the Apache Software Foundation (ASF) under one *
3 * or more contributor license agreements. See the NOTICE file *
4 * distributed with this work for additional information *
5 * regarding copyright ownership. The ASF licenses this file *
6 * to you under the Apache License, Version 2.0 (the *
7 * "License"); you may not use this file except in compliance *
8 * with the License. You may obtain a copy of the License at *
9 * *
10 * http://www.apache.org/licenses/LICENSE-2.0 *
11 * *
12 * Unless required by applicable law or agreed to in writing, *
13 * software distributed under the License is distributed on an *
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
15 * KIND, either express or implied. See the License for the *
16 * specific language governing permissions and limitations *
17 * under the License. *
18 ****************************************************************/
19
20
21 package org.apache.james.core;
22
23 import org.apache.james.util.InternetPrintWriter;
24 import org.apache.james.util.io.IOUtil;
25
26 import javax.activation.UnsupportedDataTypeException;
27 import javax.mail.MessagingException;
28 import javax.mail.internet.MimeMessage;
29 import javax.mail.internet.MimeUtility;
30
31 import java.io.BufferedWriter;
32 import java.io.ByteArrayInputStream;
33 import java.io.ByteArrayOutputStream;
34 import java.io.IOException;
35 import java.io.InputStream;
36 import java.io.OutputStream;
37 import java.io.OutputStreamWriter;
38 import java.io.PrintWriter;
39 import java.util.Enumeration;
40
41 /**
42 * Utility class to provide optimized write methods for the various MimeMessage
43 * implementations.
44 */
45 public class MimeMessageUtil {
46
47 /**
48 * Convenience method to take any MimeMessage and write the headers and body to two
49 * different output streams
50 *
51 * @param message the MimeMessage reading from
52 * @param headerOs the OutputStream writting the headers to
53 * @param bodyOs the OutputStream writting the body to
54 * @throws IOException get thrown if an IO Error detected while writing to the streams
55 * @throws MessagingException get thrown if an error detected while reading the message
56 */
57 public static void writeTo(MimeMessage message, OutputStream headerOs, OutputStream bodyOs) throws IOException, MessagingException {
58 writeTo(message, headerOs, bodyOs, null);
59 }
60
61 /**
62 * Convenience method to take any MimeMessage and write the headers and body to two
63 * different output streams, with an ignore list
64 *
65 * @param message the MimeMessage reading from
66 * @param headerOs the OutputStream writting the headers to
67 * @param bodyOs the OutputStream writting the body to
68 * @param ignoreList the String[] which contains headers which should be ignored
69 * @throws IOException get thrown if an IO Error detected while writing to the streams
70 * @throws MessagingException get thrown if an error detected while reading the message
71 */
72 public static void writeTo(MimeMessage message, OutputStream headerOs, OutputStream bodyOs, String[] ignoreList) throws IOException, MessagingException {
73 MimeMessage testMessage = message;
74 if (message instanceof MimeMessageCopyOnWriteProxy) {
75 MimeMessageCopyOnWriteProxy wr = (MimeMessageCopyOnWriteProxy) message;
76 testMessage = wr.getWrappedMessage();
77 }
78 if (testMessage instanceof MimeMessageWrapper) {
79 MimeMessageWrapper wrapper = (MimeMessageWrapper)testMessage;
80 if (!wrapper.isModified()) {
81 wrapper.writeTo(headerOs, bodyOs, ignoreList);
82 return;
83 }
84 }
85 writeToInternal(message, headerOs, bodyOs, ignoreList);
86 }
87
88 /**
89 *
90 * @param message
91 * @param headerOs
92 * @param bodyOs
93 * @param ignoreList
94 * @throws MessagingException
95 * @throws IOException
96 * @throws UnsupportedDataTypeException
97 */
98 public static void writeToInternal(MimeMessage message, OutputStream headerOs, OutputStream bodyOs, String[] ignoreList) throws MessagingException, IOException, UnsupportedDataTypeException {
99 if(message.getMessageID() == null) {
100 message.saveChanges();
101 }
102
103 writeHeadersTo(message, headerOs, ignoreList);
104
105 // Write the body to the output stream
106 writeMessageBodyTo(message, bodyOs);
107 }
108
109 /**
110 * Write message body of given mimeessage to the given outputStream
111 *
112 * @param message the MimeMessage used as input
113 * @param bodyOs the OutputStream to write the message body to
114 * @throws IOException
115 * @throws UnsupportedDataTypeException
116 * @throws MessagingException
117 */
118 public static void writeMessageBodyTo(MimeMessage message, OutputStream bodyOs) throws IOException, UnsupportedDataTypeException, MessagingException {
119 OutputStream bos;
120 InputStream bis;
121
122 try {
123 // Get the message as a stream. This will encode
124 // objects as necessary, and we have some input from
125 // decoding an re-encoding the stream. I'd prefer the
126 // raw stream, but see
127 bos = MimeUtility.encode(bodyOs, message.getEncoding());
128 bis = message.getInputStream();
129 } catch(UnsupportedDataTypeException udte) {
130 /* If we get an UnsupportedDataTypeException try using
131 * the raw input stream as a "best attempt" at rendering
132 * a message.
133 *
134 * WARNING: JavaMail v1.3 getRawInputStream() returns
135 * INVALID (unchanged) content for a changed message.
136 * getInputStream() works properly, but in this case
137 * has failed due to a missing DataHandler.
138 *
139 * MimeMessage.getRawInputStream() may throw a "no
140 * content" MessagingException. In JavaMail v1.3, when
141 * you initially create a message using MimeMessage
142 * APIs, there is no raw content available.
143 * getInputStream() works, but getRawInputStream()
144 * throws an exception. If we catch that exception,
145 * throw the UDTE. It should mean that someone has
146 * locally constructed a message part for which JavaMail
147 * doesn't have a DataHandler.
148 */
149
150 try {
151 bis = message.getRawInputStream();
152 bos = bodyOs;
153 } catch(javax.mail.MessagingException _) {
154 throw udte;
155 }
156 }
157 catch(javax.mail.MessagingException me) {
158 /* This could be another kind of MessagingException
159 * thrown by MimeMessage.getInputStream(), such as a
160 * javax.mail.internet.ParseException.
161 *
162 * The ParseException is precisely one of the reasons
163 * why the getRawInputStream() method exists, so that we
164 * can continue to stream the content, even if we cannot
165 * handle it. Again, if we get an exception, we throw
166 * the one that caused us to call getRawInputStream().
167 */
168 try {
169 bis = message.getRawInputStream();
170 bos = bodyOs;
171 } catch(javax.mail.MessagingException _) {
172 throw me;
173 }
174 }
175
176 try {
177 copyStream(bis, bos);
178 }
179 finally {
180 IOUtil.shutdownStream(bis);
181 }
182 }
183
184 /**
185 * Convenience method to copy streams
186 *
187 * @param in the InputStream used as copy source
188 * @param out the OutputStram used as copy destination
189 * @throws IOException
190 */
191 public static void copyStream(InputStream in, OutputStream out) throws IOException {
192 // TODO: This is really a bad way to do this sort of thing. A shared buffer to
193 // allow simultaneous read/writes would be a substantial improvement
194 byte[] block = new byte[1024];
195 int read = 0;
196 while ((read = in.read(block)) > -1) {
197 out.write(block, 0, read);
198 }
199 out.flush();
200 }
201
202
203 /**
204 * Write the message headers to the given outputstream
205 *
206 * @param message the MimeMessage to read from
207 * @param headerOs the OutputStream to which the headers get written
208 * @param ignoreList the String[] which holds headers which should be ignored
209 * @throws MessagingException
210 */
211 private static void writeHeadersTo(MimeMessage message, OutputStream headerOs, String[] ignoreList) throws MessagingException {
212 //Write the headers (minus ignored ones)
213 Enumeration headers = message.getNonMatchingHeaderLines(ignoreList);
214 writeHeadersTo(headers, headerOs);
215 }
216
217 /**
218 * Write the message headers to the given outputstream
219 *
220 * @param headers the Enumeration which holds the headers
221 * @param headerOs the OutputStream to which the headers get written
222 * @throws MessagingException
223 */
224 public static void writeHeadersTo(Enumeration headers, OutputStream headerOs) throws MessagingException {
225 PrintWriter hos = new InternetPrintWriter(new BufferedWriter(new OutputStreamWriter(headerOs), 512), true);
226 while (headers.hasMoreElements()) {
227 hos.println((String)headers.nextElement());
228 }
229 // Print header/data separator
230 hos.println();
231 hos.flush();
232 }
233
234 /**
235 * Get an InputStream which holds all headers of the given MimeMessage
236 *
237 * @param message the MimeMessage used as source
238 * @param ignoreList the String[] which holds headers which should be ignored
239 * @return stream the InputStream which holds the headers
240 * @throws MessagingException
241 */
242 public static InputStream getHeadersInputStream(MimeMessage message, String[] ignoreList) throws MessagingException {
243 ByteArrayOutputStream bo = new ByteArrayOutputStream();
244 writeHeadersTo(message,bo,ignoreList);
245 return new ByteArrayInputStream(bo.toByteArray());
246 }
247
248
249 /**
250 * Slow method to calculate the exact size of a message!
251 */
252 private static final class SizeCalculatorOutputStream extends OutputStream {
253 long size = 0;
254
255 public void write(int arg0) throws IOException {
256 size++;
257 }
258
259 public long getSize() {
260 return size;
261 }
262
263 public void write(byte[] arg0, int arg1, int arg2) throws IOException {
264 size += arg2;
265 }
266
267 public void write(byte[] arg0) throws IOException {
268 size += arg0.length;
269 }
270 }
271
272 /**
273 * Return the full site of an mimeMessage
274 *
275 * @return size of full message including headers
276 * @throws MessagingException if a problem occours while computing the message size
277 */
278 public static long getMessageSize(MimeMessage message) throws MessagingException {
279 //If we have a MimeMessageWrapper, then we can ask it for just the
280 // message size and skip calculating it
281 long size = -1;
282
283 if (message instanceof MimeMessageWrapper) {
284 MimeMessageWrapper wrapper = (MimeMessageWrapper) message;
285 size = wrapper.getMessageSize();
286 } else if (message instanceof MimeMessageCopyOnWriteProxy) {
287 MimeMessageCopyOnWriteProxy wrapper = (MimeMessageCopyOnWriteProxy) message;
288 size = wrapper.getMessageSize();
289 }
290
291 if (size == -1) {
292 size = calculateMessageSize(message);
293 }
294
295 return size;
296 }
297
298 /**
299 * Calculate the size of the give mimeMessage
300 *
301 * @param message the MimeMessage
302 * @return size the calculated size
303 * @throws MessagingException if a problem occours while calculate the message size
304 */
305 public static long calculateMessageSize(MimeMessage message) throws MessagingException {
306 long size;
307 //SK: Should probably eventually store this as a locally
308 // maintained value (so we don't have to load and reparse
309 // messages each time).
310 size = message.getSize();
311 if (size != -1) {
312 Enumeration e = message.getAllHeaderLines();
313 if (e.hasMoreElements()) {
314 size += 2;
315 }
316 while (e.hasMoreElements()) {
317 // add 2 bytes for the CRLF
318 size += ((String) e.nextElement()).length()+2;
319 }
320 }
321
322
323 if (size == -1) {
324 SizeCalculatorOutputStream out = new SizeCalculatorOutputStream();
325 try {
326 message.writeTo(out);
327 } catch (IOException e) {
328 // should never happen as SizeCalculator does not actually throw IOExceptions.
329 throw new MessagingException("IOException wrapped by getMessageSize",e);
330 }
331 size = out.getSize();
332 }
333 return size;
334 }
335
336
337 }