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  package org.apache.james.mime4j.parser;
21  
22  import org.apache.james.mime4j.descriptor.BodyDescriptor;
23  import org.apache.james.mime4j.parser.AbstractContentHandler;
24  import org.apache.james.mime4j.parser.MimeStreamParser;
25  import org.apache.james.mime4j.util.ByteSequence;
26  import org.apache.james.mime4j.util.ContentUtil;
27  import org.apache.log4j.BasicConfigurator;
28  
29  import java.io.ByteArrayInputStream;
30  import java.io.IOException;
31  import java.io.InputStream;
32  import java.util.LinkedList;
33  
34  import junit.framework.TestCase;
35  
36  public class MimeStreamParserTest extends TestCase {
37  
38      @Override
39      public void setUp() {
40          BasicConfigurator.resetConfiguration();
41          BasicConfigurator.configure();
42      }
43          /*
44      public void testRootAndBodyStreamsAreSynched() throws IOException {
45          File dir = new File("testmsgs");
46          File[] files = dir.listFiles();
47          
48          for (int i = 0; i < files.length; i++) {
49              File f = files[i];
50              
51              if (f.getName().toLowerCase().endsWith(".msg")) {
52                  final RandomAccessFile file = new RandomAccessFile(f, "r");
53                  final EOLTrackingInputStream rootStream = 
54                      new EOLTrackingInputStream(
55                         new RandomAccessFileInputStream(file, 0, file.length()));
56                  
57                  ContentHandler handler = new AbstractContentHandler() {
58                      public void body(BodyDescriptor bd, InputStream expected) throws IOException {
59                         int pos = rootStream.getMark();
60                         if (expected instanceof RootInputStream) {
61                             pos++;
62                         }
63                         InputStream actual = 
64                             new EOLConvertingInputStream(
65                                   new RandomAccessFileInputStream(file, pos, file.length()));
66                         
67                         StringBuilder sb1 = new StringBuilder();
68                         StringBuilder sb2 = new StringBuilder();
69                         int b = 0;
70                         while ((b = expected.read()) != -1) {
71                             sb1.append((char) (b & 0xff));
72                             sb2.append((char) (actual.read() & 0xff));
73                         }
74                         assertEquals(sb1.toString(), sb2.toString());
75                      }
76                  };
77                  
78                  System.out.println("Testing synch of " + f.getName());
79                  
80                  MimeStreamParser parser = new MimeStreamParser();
81                  parser.setContentHandler(handler);
82                  parser.parse(rootStream);
83              }
84          }
85      }*/
86      
87      public void testBoundaryInEpilogue() throws Exception {
88          StringBuilder sb = new StringBuilder();
89          sb.append("From: foo@bar.com\r\n");
90          sb.append("To: someone@else.com\r\n");
91          sb.append("Content-type: multipart/something; boundary=myboundary\r\n");
92          sb.append("\r\n");
93          sb.append("This is the preamble.\r\n");
94          sb.append("--myboundary\r\n");
95          sb.append("Content-type: text/plain\r\n");
96          sb.append("\r\n");
97          sb.append("This is the first body.\r\n");
98          sb.append("It's completely meaningless.\r\n");
99          sb.append("After this line the body ends.\r\n");
100         sb.append("\r\n");
101         sb.append("--myboundary--\r\n");
102         
103         StringBuilder epilogue = new StringBuilder();
104         epilogue.append("Content-type: text/plain\r\n");
105         epilogue.append("\r\n");
106         epilogue.append("This is actually the epilogue but it looks like a second body.\r\n");
107         epilogue.append("Yada yada yada.\r\n");
108         epilogue.append("\r\n");
109         epilogue.append("--myboundary--\r\n");
110         epilogue.append("This is still the epilogue.\r\n");
111         
112         sb.append(epilogue.toString());
113         
114         ByteArrayInputStream bais = new ByteArrayInputStream(sb.toString().getBytes("US-ASCII"));
115         
116         final StringBuilder actual = new StringBuilder();
117         
118         MimeStreamParser parser = new MimeStreamParser();
119         parser.setContentHandler(new AbstractContentHandler() {
120             @Override
121             public void epilogue(InputStream is) throws IOException {
122                 int b;
123                 while ((b = is.read()) != -1) {
124                     actual.append((char) b);
125                 }
126             }
127         });
128         parser.parse(bais);
129         
130         assertEquals(epilogue.toString(), actual.toString());
131     }
132     
133     public void testParseOneLineFields() throws Exception {
134         StringBuilder sb = new StringBuilder();
135         final LinkedList<String> expected = new LinkedList<String>();
136         expected.add("From: foo@abr.com");
137         sb.append(expected.getLast() + "\r\n");
138         expected.add("Subject: A subject");
139         sb.append(expected.getLast() + "\r\n");
140         
141         MimeStreamParser parser = new MimeStreamParser();
142         parser.setContentHandler(new AbstractContentHandler() {
143             @Override
144             public void field(Field field) {
145                 assertEquals(expected.removeFirst(), decode(field.getRaw()));
146             }
147         });
148         
149         parser.parse(new ByteArrayInputStream(sb.toString().getBytes()));
150         
151         assertEquals(0, expected.size());
152     }
153     
154     public void testCRWithoutLFInHeader() throws Exception {
155         /*
156          * Test added because \r:s not followed by \n:s in the header would
157          * cause an infinite loop. 
158          */
159         StringBuilder sb = new StringBuilder();
160         final LinkedList<String> expected = new LinkedList<String>();
161         expected.add("The-field: This field\r\rcontains CR:s\r\r"
162                         + "not\r\n\tfollowed by LF");
163         sb.append(expected.getLast() + "\r\n");
164         
165         MimeStreamParser parser = new MimeStreamParser();
166         parser.setContentHandler(new AbstractContentHandler() {
167             @Override
168             public void field(Field field) {
169                 assertEquals(expected.removeFirst(), decode(field.getRaw()));
170             }
171         });
172         
173         parser.parse(new ByteArrayInputStream(sb.toString().getBytes()));
174         
175         assertEquals(0, expected.size());
176     }
177     
178     public void testParseMultiLineFields() throws Exception {
179         StringBuilder sb = new StringBuilder();
180         final LinkedList<String> expected = new LinkedList<String>();
181         expected.add("Received: by netmbx.netmbx.de (/\\==/\\ Smail3.1.28.1)\r\n"
182                    + "\tfrom mail.cs.tu-berlin.de with smtp\r\n"
183                    + "\tid &lt;m0uWPrO-0004wpC&gt;;"
184                         + " Wed, 19 Jun 96 18:12 MES");
185         sb.append(expected.getLast() + "\r\n");
186         expected.add("Subject: A folded subject\r\n Line 2\r\n\tLine 3");
187         sb.append(expected.getLast() + "\r\n");
188         
189         MimeStreamParser parser = new MimeStreamParser();
190         parser.setContentHandler(new AbstractContentHandler() {
191             @Override
192             public void field(Field field) {
193                 assertEquals(expected.removeFirst(), decode(field.getRaw()));
194             }
195         });
196         
197         parser.parse(new ByteArrayInputStream(sb.toString().getBytes()));
198         
199         assertEquals(0, expected.size());
200     }
201     
202     public void testStop() throws Exception {
203         final MimeStreamParser parser = new MimeStreamParser();
204         TestHandler handler = new TestHandler() {
205             @Override
206             public void endHeader() {
207                 super.endHeader();
208                 parser.stop();
209             }
210         };
211         parser.setContentHandler(handler);
212 
213         String msg = "Subject: Yada yada\r\n"
214                    + "From: foo@bar.com\r\n"
215                    + "\r\n"
216                    + "Line 1\r\n"
217                    + "Line 2\r\n";
218         String expected = "<message>\r\n"
219                         + "<header>\r\n"
220                         + "<field>\r\n"
221                         + "Subject: Yada yada"
222                         + "</field>\r\n"
223                         + "<field>\r\n"
224                         + "From: foo@bar.com"
225                         + "</field>\r\n"
226                         + "</header>\r\n"
227                         + "<body>\r\n"
228                         + "</body>\r\n"
229                         + "</message>\r\n";
230         
231         parser.parse(new ByteArrayInputStream(msg.getBytes()));
232         String result = handler.sb.toString();
233         
234         assertEquals(expected, result);
235     }
236     
237     /*
238      * Tests that invalid fields are ignored.
239      */
240     public void testInvalidFields() throws Exception {
241         StringBuilder sb = new StringBuilder();
242         final LinkedList<String> expected = new LinkedList<String>();
243         sb.append("From - foo@abr.com\r\n");
244         expected.add("From: some@one.com");
245         sb.append(expected.getLast() + "\r\n");
246         expected.add("Subject: A subject");
247         sb.append(expected.getLast() + "\r\n");
248         sb.append("A line which should be ignored\r\n");
249         
250         MimeStreamParser parser = new MimeStreamParser();
251         parser.setContentHandler(new AbstractContentHandler() {
252             @Override
253             public void field(Field field) {
254                 assertEquals(expected.removeFirst(), decode(field.getRaw()));
255             }
256         });
257         
258         parser.parse(new ByteArrayInputStream(sb.toString().getBytes()));
259         
260         assertEquals(0, expected.size());
261     }
262 
263     /*
264      * Tests that empty streams still generate the expected series of events.
265      */
266     public void testEmptyStream() throws Exception {
267         final LinkedList<String> expected = new LinkedList<String>();
268         expected.add("startMessage");
269         expected.add("startHeader");
270         expected.add("endHeader");
271         expected.add("body");
272         expected.add("endMessage");
273         
274         MimeStreamParser parser = new MimeStreamParser();
275         parser.setContentHandler(new AbstractContentHandler() {
276             @Override
277             public void body(BodyDescriptor bd, InputStream is) {
278                 assertEquals(expected.removeFirst(), "body");
279             }
280             
281             @Override
282             public void endMultipart() {
283                 fail("endMultipart shouldn't be called for empty stream");
284             }
285 
286             @Override
287             public void endBodyPart() {
288                 fail("endBodyPart shouldn't be called for empty stream");
289             }
290 
291             @Override
292             public void endHeader() {
293                 assertEquals(expected.removeFirst(), "endHeader");
294             }
295 
296             @Override
297             public void endMessage() {
298                 assertEquals(expected.removeFirst(), "endMessage");
299             }
300 
301             @Override
302             public void field(Field field) {
303                 fail("field shouldn't be called for empty stream");
304             }
305 
306             @Override
307             public void startMultipart(BodyDescriptor bd) {
308                 fail("startMultipart shouldn't be called for empty stream");
309             }
310 
311             @Override
312             public void startBodyPart() {
313                 fail("startBodyPart shouldn't be called for empty stream");
314             }
315 
316             @Override
317             public void startHeader() {
318                 assertEquals(expected.removeFirst(), "startHeader");
319             }
320 
321             @Override
322             public void startMessage() {
323                 assertEquals(expected.removeFirst(), "startMessage");
324             }
325         });
326         
327         parser.parse(new ByteArrayInputStream(new byte[0]));
328         
329         assertEquals(0, expected.size());
330     }
331     
332     /*
333      * Tests parsing of empty headers.
334      */
335     public void testEmpyHeader() throws Exception {
336         StringBuilder sb = new StringBuilder();
337         sb.append("\r\n");
338         sb.append("The body is right here\r\n");
339         
340         final StringBuilder body = new StringBuilder();
341         
342         MimeStreamParser parser = new MimeStreamParser();
343         parser.setContentHandler(new AbstractContentHandler() {
344             @Override
345             public void field(Field field) {
346                 fail("No fields should be reported");
347             }
348             @Override
349             public void body(BodyDescriptor bd, InputStream is) throws IOException {
350                 int b;
351                 while ((b = is.read()) != -1) {
352                     body.append((char) b);
353                 }
354             }
355         });
356         
357         parser.parse(new ByteArrayInputStream(sb.toString().getBytes()));
358         
359         assertEquals("The body is right here\r\n", body.toString());
360     }
361     
362     /*
363      * Tests parsing of empty body.
364      */
365     public void testEmptyBody() throws Exception {
366         StringBuilder sb = new StringBuilder();
367         final LinkedList<String> expected = new LinkedList<String>();
368         expected.add("From: some@one.com");
369         sb.append(expected.getLast() + "\r\n");
370         expected.add("Subject: A subject");
371         sb.append(expected.getLast() + "\r\n\r\n");
372         
373         MimeStreamParser parser = new MimeStreamParser();
374         parser.setContentHandler(new AbstractContentHandler() {
375             @Override
376             public void field(Field field) {
377                 assertEquals(expected.removeFirst(), decode(field.getRaw()));
378             }
379             @Override
380             public void body(BodyDescriptor bd, InputStream is) throws IOException {
381                 assertEquals(-1, is.read());
382             }
383         });
384         
385         parser.parse(new ByteArrayInputStream(sb.toString().getBytes()));
386         
387         assertEquals(0, expected.size());
388     }
389     
390     /*
391      * Tests that invalid fields are ignored.
392      */
393     public void testPrematureEOFAfterFields() throws Exception {
394         StringBuilder sb = new StringBuilder();
395         final LinkedList<String> expected = new LinkedList<String>();
396         expected.add("From: some@one.com");
397         sb.append(expected.getLast() + "\r\n");
398         expected.add("Subject: A subject");
399         sb.append(expected.getLast());
400         
401         MimeStreamParser parser = new MimeStreamParser();
402         parser.setContentHandler(new AbstractContentHandler() {
403             @Override
404             public void field(Field field) {
405                 assertEquals(expected.removeFirst(), decode(field.getRaw()));
406             }
407         });
408         
409         parser.parse(new ByteArrayInputStream(sb.toString().getBytes()));
410         
411         assertEquals(0, expected.size());
412         
413         sb = new StringBuilder();
414         expected.clear();
415         expected.add("From: some@one.com");
416         sb.append(expected.getLast() + "\r\n");
417         expected.add("Subject: A subject");
418         sb.append(expected.getLast() + "\r\n");
419         
420         parser = new MimeStreamParser();
421         parser.setContentHandler(new AbstractContentHandler() {
422             @Override
423             public void field(Field field) {
424                 assertEquals(expected.removeFirst(), decode(field.getRaw()));
425             }
426         });
427         
428         parser.parse(new ByteArrayInputStream(sb.toString().getBytes()));
429         
430         assertEquals(0, expected.size());
431     }
432     
433     public void testAutomaticContentDecoding() throws Exception {
434         MimeStreamParser parser = new MimeStreamParser();
435         parser.setContentDecoding(true);
436         TestHandler handler = new TestHandler();
437         parser.setContentHandler(handler);
438 
439         String msg = "Subject: Yada yada\r\n"
440                    + "From: foo@bar.com\r\n"
441                    + "Content-Type: application/octet-stream\r\n"
442                    + "Content-Transfer-Encoding: base64\r\n"
443                    + "\r\n"
444                    + "V2hvIGF0ZSBteSBjYWtlPwo=";
445         String expected = "<message>\r\n"
446                         + "<header>\r\n"
447                         + "<field>\r\n"
448                         + "Subject: Yada yada"
449                         + "</field>\r\n"
450                         + "<field>\r\n"
451                         + "From: foo@bar.com"
452                         + "</field>\r\n"
453                         + "<field>\r\n"
454                         + "Content-Type: application/octet-stream"
455                         + "</field>\r\n"
456                         + "<field>\r\n"
457                         + "Content-Transfer-Encoding: base64"
458                         + "</field>\r\n"
459                         + "</header>\r\n"
460                         + "<body>\r\n"
461                         + "Who ate my cake?\n"
462                         + "</body>\r\n"
463                         + "</message>\r\n";
464         
465         parser.parse(new ByteArrayInputStream(msg.getBytes()));
466         String result = handler.sb.toString();
467         
468         assertEquals(expected, result);
469     }
470     
471     protected String decode(ByteSequence byteSequence) {
472         return ContentUtil.decode(byteSequence);
473     }
474     
475 }