1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package javax.mail.internet;
21
22 import java.io.BufferedInputStream;
23 import java.io.ByteArrayInputStream;
24 import java.io.ByteArrayOutputStream;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.io.OutputStream;
28
29 import java.util.Arrays;
30
31 import javax.activation.DataSource;
32 import javax.mail.BodyPart;
33 import javax.mail.MessagingException;
34 import javax.mail.Multipart;
35 import javax.mail.MultipartDataSource;
36
37 import org.apache.geronimo.mail.util.SessionUtil;
38
39
40
41
42 public class MimeMultipart extends Multipart {
43 private static final String MIME_IGNORE_MISSING_BOUNDARY = "mail.mime.multipart.ignoremissingendboundary";
44
45
46
47
48 protected DataSource ds;
49
50
51
52 protected boolean parsed = true;
53
54
55 private transient ContentType type;
56
57
58 private boolean complete = true;
59
60
61 private String preamble = null;
62
63
64
65
66 public MimeMultipart() {
67 this("mixed");
68 }
69
70
71
72
73
74
75 public MimeMultipart(String subtype) {
76 type = new ContentType("multipart", subtype, null);
77 type.setParameter("boundary", getBoundary());
78 contentType = type.toString();
79 }
80
81
82
83
84
85
86
87 public MimeMultipart(DataSource dataSource) throws MessagingException {
88 ds = dataSource;
89 if (dataSource instanceof MultipartDataSource) {
90 super.setMultipartDataSource((MultipartDataSource) dataSource);
91 parsed = true;
92 } else {
93
94
95
96
97 contentType = ds.getContentType();
98 type = new ContentType(contentType);
99 parsed = false;
100 }
101 }
102
103 public void setSubType(String subtype) throws MessagingException {
104 type.setSubType(subtype);
105 contentType = type.toString();
106 }
107
108 public int getCount() throws MessagingException {
109 parse();
110 return super.getCount();
111 }
112
113 public synchronized BodyPart getBodyPart(int part) throws MessagingException {
114 parse();
115 return super.getBodyPart(part);
116 }
117
118 public BodyPart getBodyPart(String cid) throws MessagingException {
119 parse();
120 for (int i = 0; i < parts.size(); i++) {
121 MimeBodyPart bodyPart = (MimeBodyPart) parts.get(i);
122 if (cid.equals(bodyPart.getContentID())) {
123 return bodyPart;
124 }
125 }
126 return null;
127 }
128
129 protected void updateHeaders() throws MessagingException {
130 parse();
131 for (int i = 0; i < parts.size(); i++) {
132 MimeBodyPart bodyPart = (MimeBodyPart) parts.get(i);
133 bodyPart.updateHeaders();
134 }
135 }
136
137 private static byte[] dash = { '-', '-' };
138 private static byte[] crlf = { 13, 10 };
139
140 public void writeTo(OutputStream out) throws IOException, MessagingException {
141 parse();
142 String boundary = type.getParameter("boundary");
143 byte[] bytes = boundary.getBytes();
144
145 if (preamble != null) {
146 byte[] preambleBytes = preamble.getBytes();
147
148 out.write(preambleBytes);
149 out.write(crlf);
150 }
151
152 for (int i = 0; i < parts.size(); i++) {
153 BodyPart bodyPart = (BodyPart) parts.get(i);
154 out.write(dash);
155 out.write(bytes);
156 out.write(crlf);
157 bodyPart.writeTo(out);
158 out.write(crlf);
159 }
160 out.write(dash);
161 out.write(bytes);
162 out.write(dash);
163 out.write(crlf);
164 out.flush();
165 }
166
167 protected void parse() throws MessagingException {
168 if (parsed) {
169 return;
170 }
171
172 try {
173 ContentType cType = new ContentType(contentType);
174 InputStream is = new BufferedInputStream(ds.getInputStream());
175 BufferedInputStream pushbackInStream = null;
176 String boundaryString = cType.getParameter("boundary");
177 byte[] boundary = null;
178 if (boundaryString == null) {
179 pushbackInStream = new BufferedInputStream(is, 1200);
180
181 boundary = readTillFirstBoundary(pushbackInStream);
182 }
183 else {
184 boundary = ("--" + boundaryString).getBytes();
185 pushbackInStream = new BufferedInputStream(is, boundary.length + 1000);
186 readTillFirstBoundary(pushbackInStream, boundary);
187 }
188
189 while (true) {
190 MimeBodyPartInputStream partStream;
191 partStream = new MimeBodyPartInputStream(pushbackInStream, boundary);
192 addBodyPart(new MimeBodyPart(partStream));
193
194
195 if (!partStream.boundaryFound) {
196 if (!SessionUtil.getBooleanProperty(MIME_IGNORE_MISSING_BOUNDARY, true)) {
197 throw new MessagingException("Missing Multi-part end boundary");
198 }
199 complete = false;
200 }
201
202 if (partStream.finalBoundaryFound) {
203 break;
204 }
205 }
206 } catch (Exception e){
207 throw new MessagingException(e.toString(),e);
208 }
209 parsed = true;
210 }
211
212
213
214
215
216
217
218
219
220
221 private byte[] readTillFirstBoundary(BufferedInputStream pushbackInStream) throws MessagingException {
222 ByteArrayOutputStream preambleStream = new ByteArrayOutputStream();
223
224 try {
225 while (true) {
226
227 byte[] line = readLine(pushbackInStream);
228
229 if (line == null) {
230 throw new MessagingException("Unexpected End of Stream while searching for first Mime Boundary");
231 }
232
233 if (line.length > 2 && line[0] == '-' && line[1] == '-') {
234
235 byte[] preambleBytes = preambleStream.toByteArray();
236 if (preambleBytes.length > 0) {
237 preamble = new String(preambleBytes);
238 }
239 return stripLinearWhiteSpace(line);
240 }
241 else {
242
243 preambleStream.write(line);
244 preambleStream.write('\r');
245 preambleStream.write('\n');
246 }
247 }
248 } catch (IOException ioe) {
249 throw new MessagingException(ioe.toString(), ioe);
250 }
251 }
252
253
254
255
256
257
258
259
260
261
262
263
264 private byte[] stripLinearWhiteSpace(byte[] line) {
265 int index = line.length - 1;
266
267
268 if (line[index] != ' ' && line[index] != '\t') {
269 return line;
270 }
271
272 for (; index > 0; index--) {
273 if (line[index] != ' ' && line[index] != '\t') {
274 break;
275 }
276 }
277
278 byte[] newLine = new byte[index + 1];
279 System.arraycopy(line, 0, newLine, 0, index + 1);
280 return newLine;
281 }
282
283
284
285
286
287
288
289
290
291
292 private void readTillFirstBoundary(BufferedInputStream pushbackInStream, byte[] boundary) throws MessagingException {
293 ByteArrayOutputStream preambleStream = new ByteArrayOutputStream();
294
295 try {
296 while (true) {
297
298 byte[] line = readLine(pushbackInStream);
299
300 if (line == null) {
301 throw new MessagingException("Unexpected End of Stream while searching for first Mime Boundary");
302 }
303
304
305 if (compareBoundary(line, boundary)) {
306
307 byte[] preambleBytes = preambleStream.toByteArray();
308 if (preambleBytes.length > 0) {
309 preamble = new String(preambleBytes);
310 }
311 return;
312 }
313
314
315 preambleStream.write(line);
316 preambleStream.write('\r');
317 preambleStream.write('\n');
318 }
319 } catch (IOException ioe) {
320 throw new MessagingException(ioe.toString(), ioe);
321 }
322 }
323
324
325
326
327
328
329
330
331
332
333
334
335 private boolean compareBoundary(byte[] line, byte[] boundary) {
336
337 if (line.length < boundary.length) {
338 return false;
339 }
340
341
342 if (line.length == boundary.length) {
343 return Arrays.equals(line, boundary);
344 }
345
346 for (int i = 0; i < boundary.length; i++) {
347
348 if (line[i] != boundary[i]) {
349 return false;
350 }
351 }
352
353 for (int i = boundary.length; i < line.length; i++) {
354
355 if (line[i] != ' ' && line[i] != '\t') {
356 return false;
357 }
358 }
359
360 return true;
361 }
362
363
364
365
366
367
368
369
370
371
372
373 private byte[] readLine(BufferedInputStream in) throws IOException
374 {
375 ByteArrayOutputStream line = new ByteArrayOutputStream();
376
377 while (in.available() > 0) {
378 int value = in.read();
379 if (value == -1) {
380
381 if (line.size() == 0) {
382 return null;
383 }
384 break;
385 }
386 else if (value == '\r') {
387 in.mark(10);
388 value = in.read();
389
390
391 if (value != '\n') {
392 in.reset();
393 }
394 break;
395 }
396 else if (value == '\n') {
397
398 break;
399 }
400 else {
401
402 line.write((byte)value);
403 }
404 }
405
406 return line.toByteArray();
407 }
408
409
410 protected InternetHeaders createInternetHeaders(InputStream in) throws MessagingException {
411 return new InternetHeaders(in);
412 }
413
414 protected MimeBodyPart createMimeBodyPart(InternetHeaders headers, byte[] data) throws MessagingException {
415 return new MimeBodyPart(headers, data);
416 }
417
418 protected MimeBodyPart createMimeBodyPart(InputStream in) throws MessagingException {
419 return new MimeBodyPart(in);
420 }
421
422
423 private static int part;
424
425 private synchronized static String getBoundary() {
426 int i;
427 synchronized(MimeMultipart.class) {
428 i = part++;
429 }
430 StringBuffer buf = new StringBuffer(64);
431 buf.append("----=_Part_").append(i).append('_').append((new Object()).hashCode()).append('.').append(System.currentTimeMillis());
432 return buf.toString();
433 }
434
435 private class MimeBodyPartInputStream extends InputStream {
436 BufferedInputStream inStream;
437 public boolean boundaryFound = false;
438 byte[] boundary;
439 public boolean finalBoundaryFound = false;
440
441 public MimeBodyPartInputStream(BufferedInputStream inStream, byte[] boundary) {
442 super();
443 this.inStream = inStream;
444 this.boundary = boundary;
445 }
446
447
448
449
450
451
452
453
454 public int read() throws IOException {
455 if (boundaryFound) {
456 return -1;
457 }
458
459
460 int firstChar = inStream.read();
461
462 if (firstChar == -1) {
463 boundaryFound = true;
464
465 finalBoundaryFound = true;
466 return -1;
467 }
468
469
470
471
472
473
474
475
476 if (firstChar != '\r' && firstChar != '\n') {
477
478 return firstChar;
479 }
480
481
482 inStream.mark(boundary.length + 1000);
483
484
485 int value = firstChar;
486
487 if (value == '\r') {
488
489 value = inStream.read();
490 if (value != '\n') {
491
492
493 inStream.reset();
494 return '\r';
495 }
496 }
497
498 value = inStream.read();
499
500
501 if ((byte) value != boundary[0]) {
502
503 inStream.reset();
504 return firstChar;
505 }
506
507
508
509
510
511
512
513 int boundaryIndex = 0;
514 while ((boundaryIndex < boundary.length) && ((byte) value == boundary[boundaryIndex])) {
515 value = inStream.read();
516 boundaryIndex++;
517 }
518
519
520 if (boundaryIndex != boundary.length) {
521
522
523 inStream.reset();
524 return firstChar;
525 }
526
527
528
529 if (value == '-') {
530 value = inStream.read();
531
532
533 if (value != '-') {
534
535
536 inStream.reset();
537 return firstChar;
538 }
539
540 value = inStream.read();
541
542 while (value == ' ' || value == '\t') {
543 value = inStream.read();
544 }
545
546
547
548
549
550
551 if (value == -1) {
552
553 finalBoundaryFound = true;
554
555 boundaryFound = true;
556 return -1;
557 }
558
559
560 if (value != '\r' && value != '\n') {
561
562
563 inStream.reset();
564 return firstChar;
565 }
566
567
568 if (value == '\r') {
569
570 value = inStream.read();
571 if (value != '\n') {
572
573
574
575 inStream.reset();
576 return firstChar;
577 }
578 }
579
580
581 finalBoundaryFound = true;
582 }
583 else {
584
585 while (value == ' ' || value == '\t') {
586 value = inStream.read();
587 }
588
589 if (value != '\r' && value != '\n') {
590
591
592 inStream.reset();
593 return firstChar;
594 }
595
596
597 if (value == '\r') {
598
599 value = inStream.read();
600 if (value != '\n') {
601
602
603
604 inStream.reset();
605 return firstChar;
606 }
607 }
608 }
609
610 boundaryFound = true;
611 return -1;
612 }
613 }
614
615
616
617
618
619
620
621
622
623 public boolean isComplete() throws MessagingException {
624
625 parse();
626 return complete;
627 }
628
629
630
631
632
633
634
635
636
637
638 public String getPreamble() throws MessagingException {
639 parse();
640 return preamble;
641 }
642
643
644
645
646
647
648
649
650
651
652 public void setPreamble(String preamble) throws MessagingException {
653 this.preamble = preamble;
654 }
655 }