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 package org.apache.james.core;
20
21 import org.apache.avalon.framework.container.ContainerUtil;
22 import org.apache.mailet.RFC2822Headers;
23
24 import javax.mail.BodyPart;
25 import javax.mail.Session;
26 import javax.mail.internet.InternetHeaders;
27 import javax.mail.internet.MimeBodyPart;
28 import javax.mail.internet.MimeMessage;
29 import javax.mail.internet.MimeMultipart;
30
31 import java.io.ByteArrayInputStream;
32 import java.io.ByteArrayOutputStream;
33 import java.util.Arrays;
34 import java.util.Enumeration;
35 import java.util.Properties;
36
37 import junit.framework.TestCase;
38
39 /***
40 * Test the subject folding issue.
41 */
42 public class MimeMessageTest extends TestCase {
43
44 protected MimeMessage getSimpleMessage() throws Exception {
45 MimeMessage mmCreated = new MimeMessage(Session.getDefaultInstance(new Properties()));
46 mmCreated.setSubject("test");
47 mmCreated.setText("test body");
48 mmCreated.saveChanges();
49 return mmCreated;
50 }
51
52 protected String getSimpleMessageCleanedSource() throws Exception {
53 return "Subject: test\r\n"
54 +"MIME-Version: 1.0\r\n"
55 +"Content-Type: text/plain; charset=us-ascii\r\n"
56 +"Content-Transfer-Encoding: 7bit\r\n"
57 +"\r\n"
58 +"test body";
59 }
60
61
62 protected MimeMessage getMessageWithBadReturnPath() throws Exception {
63 MimeMessage mmCreated = new MimeMessage(Session.getDefaultInstance(new Properties()));
64 mmCreated.setSubject("test");
65 mmCreated.setHeader(RFC2822Headers.RETURN_PATH, "<mybadreturn@example.com>");
66 mmCreated.setText("test body");
67 mmCreated.saveChanges();
68 return mmCreated;
69 }
70
71 protected String getMessageWithBadReturnPathSource() throws Exception {
72 return "Subject: test\r\n"
73 +"Return-Path: <mybadreturn@example.com>\r\n"
74 +"MIME-Version: 1.0\r\n"
75 +"Content-Type: text/plain; charset=us-ascii\r\n"
76 +"Content-Transfer-Encoding: 7bit\r\n"
77 +"\r\n"
78 +"test body";
79 }
80
81 protected String getSimpleMessageCleanedSourceHeaderExpected() throws Exception {
82 return "X-Test: foo\r\n"+getSimpleMessageCleanedSource();
83 }
84
85
86
87
88 public void testSimpleMessage() throws Exception {
89 assertEquals(getSimpleMessageCleanedSource(), getCleanedMessageSource(getSimpleMessage()));
90 }
91
92
93 protected MimeMessage getMultipartMessage() throws Exception {
94 MimeMessage mmCreated = new MimeMessage(Session.getDefaultInstance(new Properties()));
95 mmCreated.setSubject("test");
96 MimeMultipart mm = new MimeMultipart("alternative");
97 mm.addBodyPart(new MimeBodyPart(new InternetHeaders(new ByteArrayInputStream("X-header: test1\r\nContent-Type: text/plain; charset=Cp1252\r\n".getBytes())),"first part ???".getBytes()));
98 mm.addBodyPart(new MimeBodyPart(new InternetHeaders(new ByteArrayInputStream("X-header: test2\r\nContent-Type: text/plain; charset=Cp1252\r\nContent-Transfer-Encoding: quoted-printable\r\n".getBytes())),"second part =E8=E8".getBytes()));
99 mmCreated.setContent(mm);
100 mmCreated.saveChanges();
101 return mmCreated;
102 }
103
104 protected String getMultipartMessageSource() {
105 return "Subject: test\r\n"
106 +"MIME-Version: 1.0\r\n"
107 +"Content-Type: multipart/alternative; \r\n"
108 +"\tboundary=\"----=_Part_0_XXXXXXXXXXX.XXXXXXXXXXX\"\r\n"
109 +"\r\n"
110 +"------=_Part_0_XXXXXXXXXXX.XXXXXXXXXXX\r\n"
111 +"X-header: test1\r\n"
112 +"Content-Type: text/plain; charset=Cp1252\r\n"
113 +"Content-Transfer-Encoding: quoted-printable\r\n"
114 +"\r\n"
115 +"first part =E8\r\n"
116 +"------=_Part_0_XXXXXXXXXXX.XXXXXXXXXXX\r\n"
117 +"X-header: test2\r\n"
118 +"Content-Type: text/plain; charset=Cp1252\r\n"
119 +"Content-Transfer-Encoding: quoted-printable\r\n"
120 +"\r\n"
121 +"second part =E8=E8\r\n"
122 +"------=_Part_0_XXXXXXXXXXX.XXXXXXXXXXX--\r\n";
123 }
124
125 protected String getMultipartMessageExpected1() {
126 return "Subject: test\r\n"
127 +"MIME-Version: 1.0\r\n"
128 +"Content-Type: multipart/alternative; \r\n"
129 +"\tboundary=\"----=_Part_0_XXXXXXXXXXX.XXXXXXXXXXX\"\r\n"
130 +"\r\n"
131 +"------=_Part_0_XXXXXXXXXXX.XXXXXXXXXXX\r\n"
132 +"X-header: test1\r\n"
133 +"Content-Type: text/plain; charset=Cp1252\r\n"
134 +"Content-Transfer-Encoding: quoted-printable\r\n"
135 +"\r\n"
136 +"test=80\r\n"
137 +"------=_Part_0_XXXXXXXXXXX.XXXXXXXXXXX\r\n"
138 +"X-header: test2\r\n"
139 +"Content-Type: text/plain; charset=Cp1252\r\n"
140 +"Content-Transfer-Encoding: quoted-printable\r\n"
141 +"\r\n"
142 +"second part =E8=E8\r\n"
143 +"------=_Part_0_XXXXXXXXXXX.XXXXXXXXXXX--\r\n";
144 }
145
146 protected String getMultipartMessageExpected2() {
147 return "Subject: test\r\n"
148 +"MIME-Version: 1.0\r\n"
149 +"Content-Type: multipart/alternative; \r\n"
150 +"\tboundary=\"----=_Part_0_XXXXXXXXXXX.XXXXXXXXXXX\"\r\n"
151 +"\r\n"
152 +"------=_Part_0_XXXXXXXXXXX.XXXXXXXXXXX\r\n"
153 +"X-header: test1\r\n"
154 +"Content-Type: text/plain; charset=Cp1252\r\n"
155 +"Content-Transfer-Encoding: quoted-printable\r\n"
156 +"\r\n"
157 +"test=80\r\n"
158 +"------=_Part_0_XXXXXXXXXXX.XXXXXXXXXXX\r\n"
159 +"X-header: test2\r\n"
160 +"Content-Type: text/plain; charset=Cp1252\r\n"
161 +"Content-Transfer-Encoding: quoted-printable\r\n"
162 +"\r\n"
163 +"second part =E8=E8\r\n"
164 +"------=_Part_0_XXXXXXXXXXX.XXXXXXXXXXX\r\n"
165 +"Subject: test3\r\n"
166 +"Content-Transfer-Encoding: 7bit\r\n"
167 +"Content-Type: text/plain; charset=us-ascii\r\n"
168 +"\r\n"
169 +"second part\r\n"
170 +"------=_Part_0_XXXXXXXXXXX.XXXXXXXXXXX--\r\n";
171 }
172
173 protected String getMultipartMessageExpected3() {
174 return "Subject: test\r\n"
175 +"MIME-Version: 1.0\r\n"
176 +"Content-Type: binary/octet-stream\r\n"
177 +"Content-Transfer-Encoding: quoted-printable\r\n"
178 +"\r\n"
179 +"mynewco=F2=E0=F9ntent=80=E0!";
180 }
181
182
183
184
185 public void testMultipartMessageChanges() throws Exception {
186
187 MimeMessage mm = getMultipartMessage();
188
189
190
191
192
193
194
195 MimeMultipart content1 = (MimeMultipart) mm.getContent();
196 BodyPart b1 = content1.getBodyPart(0);
197 b1.setContent("test\u20AC","text/plain; charset=Cp1252");
198 mm.setContent(content1,mm.getContentType());
199
200 mm.saveChanges();
201
202 assertEquals(getMultipartMessageExpected1(),getCleanedMessageSource(mm));
203
204 MimeMultipart content2 = (MimeMultipart) mm.getContent();
205 content2.addBodyPart(new MimeBodyPart(new InternetHeaders(new ByteArrayInputStream("Subject: test3\r\n".getBytes())),"second part".getBytes()));
206 mm.setContent(content2,mm.getContentType());
207 mm.saveChanges();
208
209 assertEquals(getMultipartMessageExpected2(),getCleanedMessageSource(mm));
210
211 mm.setContent("mynewco\u00F2\u00E0\u00F9ntent\u20AC\u00E0!","text/plain; charset=cp1252");
212 mm.setHeader(RFC2822Headers.CONTENT_TYPE,"binary/octet-stream");
213
214 mm.saveChanges();
215
216 assertEquals(getMultipartMessageExpected3(),getCleanedMessageSource(mm));
217
218 ContainerUtil.dispose(mm);
219
220 }
221
222 protected MimeMessage getMissingEncodingAddHeaderMessage() throws Exception {
223 MimeMessage m = new MimeMessage(Session.getDefaultInstance(new Properties()));
224 m.setText("Test\u00E0\r\n");
225 m.setSubject("test");
226 m.saveChanges();
227 return m;
228 }
229
230
231 protected String getMissingEncodingAddHeaderSource() {
232 return "Subject: test\r\n"+
233 "\r\n"+
234 "Test\u00E0\r\n";
235 }
236
237 protected String getMissingEncodingAddHeaderExpected() {
238 return "Subject: test\r\n"
239 +"MIME-Version: 1.0\r\n"
240 +"Content-Type: text/plain; charset=Cp1252\r\n"
241 +"Content-Transfer-Encoding: quoted-printable\r\n"
242 +"\r\n"
243 +"Test=E0\r\n";
244 }
245
246
247 /***
248 * This test is not usable in different locale environment.
249 */
250
251
252
253
254
255
256
257
258
259
260
261
262
263 protected String getCleanedMessageSource(MimeMessage mm) throws Exception {
264 ByteArrayOutputStream out2;
265 out2 = new ByteArrayOutputStream();
266 mm.writeTo(out2,new String[] {"Message-ID"});
267
268 String res = out2.toString();
269
270 int p = res.indexOf("\r\n\r\n");
271 if (p > 0) {
272 String head = res.substring(0,p);
273 String[] str = head.split("\r\n");
274 Arrays.sort(str);
275 StringBuffer outputHead = new StringBuffer();
276 for (int i = str.length-1; i >= 0; i--) {
277 outputHead.append(str[i]);
278 outputHead.append("\r\n");
279 }
280 outputHead.append(res.substring(p+2));
281 res = outputHead.toString();
282 }
283
284 res = res.replaceAll("----=_Part_//d+_//d+//.//d+","----=_Part_//0_XXXXXXXXXXX.XXXXXXXXXXX");
285 return res;
286 }
287
288 protected void debugMessage(MimeMessage mm) throws Exception {
289 System.out.println("-------------------");
290 System.out.println(getCleanedMessageSource(mm));
291 System.out.println("-------------------");
292 }
293
294
295 protected MimeMessage getMissingEncodingMessage() throws Exception {
296 MimeMessage mmCreated = new MimeMessage(Session.getDefaultInstance(new Properties()));
297 mmCreated.setSubject("test");
298 MimeMultipart mm = new MimeMultipart("alternative");
299 mm.addBodyPart(new MimeBodyPart(new InternetHeaders(new ByteArrayInputStream("X-header: test2\r\nContent-Type: text/plain; charset=Cp1252\r\nContent-Transfer-Encoding: quoted-printable\r\n".getBytes())),"second part =E8=E8".getBytes()));
300 mmCreated.setContent(mm);
301 mmCreated.saveChanges();
302 return mmCreated;
303 }
304
305
306 protected String getMissingEncodingMessageSource() {
307 return "Subject: test\r\n"
308 +"MIME-Version: 1.0\r\n"
309 +"Content-Type: multipart/alternative; \r\n"
310 +"\tboundary=\"----=_Part_0_XXXXXXXXXXX.XXXXXXXXXXX\"\r\n"
311 +"\r\n"
312 +"------=_Part_0_XXXXXXXXXXX.XXXXXXXXXXX\r\n"
313 +"X-header: test2\r\n"
314 +"Content-Type: text/plain; charset=Cp1252\r\n"
315 +"Content-Transfer-Encoding: quoted-printable\r\n"
316 +"\r\n"
317 +"second part =E8=E8\r\n"
318 +"------=_Part_0_XXXXXXXXXXX.XXXXXXXXXXX--\r\n";
319 }
320
321
322 public void testGetLineCount() throws Exception {
323 MimeMessage mm = getMissingEncodingMessage();
324 try {
325 int count = mm.getLineCount();
326 assertTrue(count == -1 || count == 7);
327 } catch (Exception e) {
328 fail("Unexpected exception in getLineCount");
329 }
330 ContainerUtil.dispose(mm);
331 }
332
333 /***
334 * This test throw a NullPointerException when the original message was created by
335 * a MimeMessageInputStreamSource.
336 */
337 public void testMessageCloningViaCoW() throws Exception {
338 MimeMessage mmorig = getSimpleMessage();
339
340 MimeMessage mm = new MimeMessageCopyOnWriteProxy(mmorig);
341
342 MimeMessage mm2 = new MimeMessageCopyOnWriteProxy(mm);
343
344 mm2.setHeader("Subject", "Modified");
345
346 ContainerUtil.dispose(mm2);
347 System.gc();
348 Thread.sleep(200);
349
350
351 mm.setHeader("Subject", "Modified");
352
353 ContainerUtil.dispose(mm);
354 ContainerUtil.dispose(mmorig);
355 }
356
357 /***
358 * This test throw a NullPointerException when the original message was created by
359 * a MimeMessageInputStreamSource.
360 */
361 public void testMessageCloningViaCoW2() throws Exception {
362 MimeMessage mmorig = getSimpleMessage();
363
364 MimeMessage mm = new MimeMessageCopyOnWriteProxy(mmorig);
365
366 MimeMessage mm2 = new MimeMessageCopyOnWriteProxy(mm);
367
368 ContainerUtil.dispose(mm);
369 mm = null;
370 System.gc();
371 Thread.sleep(200);
372
373 try {
374 mm2.writeTo(System.out);
375 } catch (Exception e) {
376 e.printStackTrace();
377 fail("Exception while writing the message to output");
378 }
379
380 ContainerUtil.dispose(mm2);
381 ContainerUtil.dispose(mmorig);
382 }
383
384
385 /***
386 * This test throw a NullPointerException when the original message was created by
387 * a MimeMessageInputStreamSource.
388 */
389 public void testMessageCloningViaCoWSubjectLost() throws Exception {
390 MimeMessage mmorig = getSimpleMessage();
391
392 MimeMessage mm = new MimeMessageCopyOnWriteProxy(mmorig);
393
394 mm.setHeader("X-Test", "foo");
395 mm.saveChanges();
396
397 assertEquals(getSimpleMessageCleanedSourceHeaderExpected(),getCleanedMessageSource(mm));
398
399 ContainerUtil.dispose(mm);
400 ContainerUtil.dispose(mmorig);
401 }
402
403 public void testReturnPath() throws Exception {
404 MimeMessage message = getSimpleMessage();
405 assertNull(message.getHeader(RFC2822Headers.RETURN_PATH));
406 }
407
408 public void testHeaderOrder() throws Exception {
409 MimeMessage message = getSimpleMessage();
410 message.setHeader(RFC2822Headers.RETURN_PATH, "<test@test.de>");
411 Enumeration h = message.getAllHeaderLines();
412
413 assertEquals(h.nextElement(),"Return-Path: <test@test.de>");
414 }
415
416 }