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