src/share/jaxws_classes/com/sun/xml/internal/messaging/saaj/packaging/mime/internet/MimeMultipart.java

changeset 0
373ffda63c9a
child 637
9c07ef4934dd
equal deleted inserted replaced
-1:000000000000 0:373ffda63c9a
1 /*
2 * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 /*
27 * @(#)MimeMultipart.java 1.31 03/01/29
28 */
29
30
31
32 package com.sun.xml.internal.messaging.saaj.packaging.mime.internet;
33
34 import java.io.*;
35
36 import javax.activation.DataSource;
37
38 import com.sun.xml.internal.messaging.saaj.packaging.mime.*;
39 import com.sun.xml.internal.messaging.saaj.packaging.mime.util.*;
40 import com.sun.xml.internal.messaging.saaj.util.FinalArrayList;
41 import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream;
42 import com.sun.xml.internal.messaging.saaj.util.SAAJUtil;
43
44 /**
45 * The MimeMultipart class is an implementation
46 * that uses MIME conventions for the multipart data. <p>
47 *
48 * A MimeMultipart is obtained from a MimeBodyPart whose primary type
49 * is "multipart" (by invoking the part's <code>getContent()</code> method)
50 * or it can be created by a client as part of creating a new MimeMessage. <p>
51 *
52 * The default multipart subtype is "mixed". The other multipart
53 * subtypes, such as "alternative", "related", and so on, can be
54 * implemented as subclasses of MimeMultipart with additional methods
55 * to implement the additional semantics of that type of multipart
56 * content. The intent is that service providers, mail JavaBean writers
57 * and mail clients will write many such subclasses and their Command
58 * Beans, and will install them into the JavaBeans Activation
59 * Framework, so that any JavaMail implementation and its clients can
60 * transparently find and use these classes. Thus, a MIME multipart
61 * handler is treated just like any other type handler, thereby
62 * decoupling the process of providing multipart handlers from the
63 * JavaMail API. Lacking these additional MimeMultipart subclasses,
64 * all subtypes of MIME multipart data appear as MimeMultipart objects. <p>
65 *
66 * An application can directly construct a MIME multipart object of any
67 * subtype by using the <code>MimeMultipart(String subtype)</code>
68 * constructor. For example, to create a "multipart/alternative" object,
69 * use <code>new MimeMultipart("alternative")</code>.
70 *
71 * @version 1.31, 03/01/29
72 * @author John Mani
73 * @author Bill Shannon
74 * @author Max Spivak
75 */
76
77 //BM MimeMultipart can extend this
78 public class MimeMultipart {
79
80 /**
81 * The DataSource supplying our InputStream.
82 */
83 protected DataSource ds = null;
84
85 /**
86 * Have we parsed the data from our InputStream yet?
87 * Defaults to true; set to false when our constructor is
88 * given a DataSource with an InputStream that we need to
89 * parse.
90 */
91 protected boolean parsed = true;
92
93 /**
94 * Vector of MimeBodyPart objects.
95 */
96 protected FinalArrayList parts = new FinalArrayList(); // Holds BodyParts
97
98 /**
99 * This field specifies the content-type of this multipart
100 * object. It defaults to "multipart/mixed".
101 */
102 protected ContentType contentType;
103
104 /**
105 * The <code>MimeBodyPart</code> containing this <code>MimeMultipart</code>,
106 * if known.
107 * @since JavaMail 1.1
108 */
109 protected MimeBodyPart parent;
110
111 protected static final boolean ignoreMissingEndBoundary;
112 static {
113 ignoreMissingEndBoundary = SAAJUtil.getSystemBoolean("saaj.mime.multipart.ignoremissingendboundary");
114 }
115
116 /**
117 * Default constructor. An empty MimeMultipart object
118 * is created. Its content type is set to "multipart/mixed".
119 * A unique boundary string is generated and this string is
120 * setup as the "boundary" parameter for the
121 * <code>contentType</code> field. <p>
122 *
123 * MimeBodyParts may be added later.
124 */
125 public MimeMultipart() {
126 this("mixed");
127 }
128
129 /**
130 * Construct a MimeMultipart object of the given subtype.
131 * A unique boundary string is generated and this string is
132 * setup as the "boundary" parameter for the
133 * <code>contentType</code> field. <p>
134 *
135 * MimeBodyParts may be added later.
136 */
137 public MimeMultipart(String subtype) {
138 //super();
139 /*
140 * Compute a boundary string.
141 */
142 String boundary = UniqueValue.getUniqueBoundaryValue();
143 contentType = new ContentType("multipart", subtype, null);
144 contentType.setParameter("boundary", boundary);
145 }
146
147 /**
148 * Constructs a MimeMultipart object and its bodyparts from the
149 * given DataSource. <p>
150 *
151 * This constructor handles as a special case the situation where the
152 * given DataSource is a MultipartDataSource object.
153 *
154 * Otherwise, the DataSource is assumed to provide a MIME multipart
155 * byte stream. The <code>parsed</code> flag is set to false. When
156 * the data for the body parts are needed, the parser extracts the
157 * "boundary" parameter from the content type of this DataSource,
158 * skips the 'preamble' and reads bytes till the terminating
159 * boundary and creates MimeBodyParts for each part of the stream.
160 *
161 * @param ds DataSource, can be a MultipartDataSource
162 * @param ct
163 * This must be the same information as {@link DataSource#getContentType()}.
164 * All the callers of this method seem to have this object handy, so
165 * for performance reason this method accepts it. Can be null.
166 */
167 public MimeMultipart(DataSource ds, ContentType ct) throws MessagingException {
168 // 'ds' was not a MultipartDataSource, we have
169 // to parse this ourself.
170 parsed = false;
171 this.ds = ds;
172 if (ct==null)
173 contentType = new ContentType(ds.getContentType());
174 else
175 contentType = ct;
176 }
177
178 /**
179 * Set the subtype. This method should be invoked only on a new
180 * MimeMultipart object created by the client. The default subtype
181 * of such a multipart object is "mixed". <p>
182 *
183 * @param subtype Subtype
184 */
185 public void setSubType(String subtype) {
186 contentType.setSubType(subtype);
187 }
188
189 /**
190 * Return the number of enclosed MimeBodyPart objects.
191 *
192 * @return number of parts
193 */
194 public int getCount() throws MessagingException {
195 parse();
196 if (parts == null)
197 return 0;
198
199 return parts.size();
200 }
201
202 /**
203 * Get the specified MimeBodyPart. BodyParts are numbered starting at 0.
204 *
205 * @param index the index of the desired MimeBodyPart
206 * @return the MimeBodyPart
207 * @exception MessagingException if no such MimeBodyPart exists
208 */
209 public MimeBodyPart getBodyPart(int index)
210 throws MessagingException {
211 parse();
212 if (parts == null)
213 throw new IndexOutOfBoundsException("No such BodyPart");
214
215 return (MimeBodyPart)parts.get(index);
216 }
217
218 /**
219 * Get the MimeBodyPart referred to by the given ContentID (CID).
220 * Returns null if the part is not found.
221 *
222 * @param CID the ContentID of the desired part
223 * @return the MimeBodyPart
224 */
225 public MimeBodyPart getBodyPart(String CID)
226 throws MessagingException {
227 parse();
228
229 int count = getCount();
230 for (int i = 0; i < count; i++) {
231 MimeBodyPart part = getBodyPart(i);
232 String s = part.getContentID();
233 // Old versions of AXIS2 put angle brackets around the content
234 // id but not the start param
235 String sNoAngle = (s!= null) ? s.replaceFirst("^<", "").replaceFirst(">$", "")
236 :null;
237 if (s != null && (s.equals(CID) || CID.equals(sNoAngle)))
238 return part;
239 }
240 return null;
241 }
242
243 /**
244 * Update headers. The default implementation here just
245 * calls the <code>updateHeaders</code> method on each of its
246 * children BodyParts. <p>
247 *
248 * Note that the boundary parameter is already set up when
249 * a new and empty MimeMultipart object is created. <p>
250 *
251 * This method is called when the <code>saveChanges</code>
252 * method is invoked on the Message object containing this
253 * MimeMultipart. This is typically done as part of the Message
254 * send process, however note that a client is free to call
255 * it any number of times. So if the header updating process is
256 * expensive for a specific MimeMultipart subclass, then it
257 * might itself want to track whether its internal state actually
258 * did change, and do the header updating only if necessary.
259 */
260 protected void updateHeaders() throws MessagingException {
261 for (int i = 0; i < parts.size(); i++)
262 ((MimeBodyPart)parts.get(i)).updateHeaders();
263 }
264
265 /**
266 * Iterates through all the parts and outputs each Mime part
267 * separated by a boundary.
268 */
269 public void writeTo(OutputStream os)
270 throws IOException, MessagingException {
271 parse();
272
273 String boundary = "--" + contentType.getParameter("boundary");
274
275 for (int i = 0; i < parts.size(); i++) {
276 OutputUtil.writeln(boundary, os); // put out boundary
277 getBodyPart(i).writeTo(os);
278 OutputUtil.writeln(os); // put out empty line
279 }
280
281 // put out last boundary
282 OutputUtil.writeAsAscii(boundary, os);
283 OutputUtil.writeAsAscii("--", os);
284 os.flush();
285 }
286
287 /**
288 * Parse the InputStream from our DataSource, constructing the
289 * appropriate MimeBodyParts. The <code>parsed</code> flag is
290 * set to true, and if true on entry nothing is done. This
291 * method is called by all other methods that need data for
292 * the body parts, to make sure the data has been parsed.
293 *
294 * @since JavaMail 1.2
295 */
296 protected void parse() throws MessagingException {
297 if (parsed)
298 return;
299
300 InputStream in;
301 SharedInputStream sin = null;
302 long start = 0, end = 0;
303 boolean foundClosingBoundary = false;
304
305 try {
306 in = ds.getInputStream();
307 if (!(in instanceof ByteArrayInputStream) &&
308 !(in instanceof BufferedInputStream) &&
309 !(in instanceof SharedInputStream))
310 in = new BufferedInputStream(in);
311 } catch (Exception ex) {
312 throw new MessagingException("No inputstream from datasource");
313 }
314 if (in instanceof SharedInputStream)
315 sin = (SharedInputStream)in;
316
317 String boundary = "--" + contentType.getParameter("boundary");
318 byte[] bndbytes = ASCIIUtility.getBytes(boundary);
319 int bl = bndbytes.length;
320
321 try {
322 // Skip the preamble
323 LineInputStream lin = new LineInputStream(in);
324 String line;
325 while ((line = lin.readLine()) != null) {
326 /*
327 * Strip trailing whitespace. Can't use trim method
328 * because it's too aggressive. Some bogus MIME
329 * messages will include control characters in the
330 * boundary string.
331 */
332 int i;
333 for (i = line.length() - 1; i >= 0; i--) {
334 char c = line.charAt(i);
335 if (!(c == ' ' || c == '\t'))
336 break;
337 }
338 line = line.substring(0, i + 1);
339 if (line.equals(boundary))
340 break;
341 }
342 if (line == null)
343 throw new MessagingException("Missing start boundary");
344
345 /*
346 * Read and process body parts until we see the
347 * terminating boundary line (or EOF).
348 */
349 boolean done = false;
350 getparts:
351 while (!done) {
352 InternetHeaders headers = null;
353 if (sin != null) {
354 start = sin.getPosition();
355 // skip headers
356 while ((line = lin.readLine()) != null && line.length() > 0)
357 ;
358 if (line == null) {
359 if (!ignoreMissingEndBoundary) {
360 throw new MessagingException("Missing End Boundary for Mime Package : EOF while skipping headers");
361 }
362 // assume there's just a missing end boundary
363 break getparts;
364 }
365 } else {
366 // collect the headers for this body part
367 headers = createInternetHeaders(in);
368 }
369
370 if (!in.markSupported())
371 throw new MessagingException("Stream doesn't support mark");
372
373 ByteOutputStream buf = null;
374 // if we don't have a shared input stream, we copy the data
375 if (sin == null)
376 buf = new ByteOutputStream();
377 int b;
378 boolean bol = true; // beginning of line flag
379 // the two possible end of line characters
380 int eol1 = -1, eol2 = -1;
381
382 /*
383 * Read and save the content bytes in buf.
384 */
385 for (;;) {
386 if (bol) {
387 /*
388 * At the beginning of a line, check whether the
389 * next line is a boundary.
390 */
391 int i;
392 in.mark(bl + 4 + 1000); // bnd + "--\r\n" + lots of LWSP
393 // read bytes, matching against the boundary
394 for (i = 0; i < bl; i++)
395 if (in.read() != bndbytes[i])
396 break;
397 if (i == bl) {
398 // matched the boundary, check for last boundary
399 int b2 = in.read();
400 if (b2 == '-') {
401 if (in.read() == '-') {
402 done = true;
403 foundClosingBoundary = true;
404 break; // ignore trailing text
405 }
406 }
407 // skip linear whitespace
408 while (b2 == ' ' || b2 == '\t')
409 b2 = in.read();
410 // check for end of line
411 if (b2 == '\n')
412 break; // got it! break out of the loop
413 if (b2 == '\r') {
414 in.mark(1);
415 if (in.read() != '\n')
416 in.reset();
417 break; // got it! break out of the loop
418 }
419 }
420 // failed to match, reset and proceed normally
421 in.reset();
422
423 // if this is not the first line, write out the
424 // end of line characters from the previous line
425 if (buf != null && eol1 != -1) {
426 buf.write(eol1);
427 if (eol2 != -1)
428 buf.write(eol2);
429 eol1 = eol2 = -1;
430 }
431 }
432
433 // read the next byte
434 if ((b = in.read()) < 0) {
435 done = true;
436 break;
437 }
438
439 /*
440 * If we're at the end of the line, save the eol characters
441 * to be written out before the beginning of the next line.
442 */
443 if (b == '\r' || b == '\n') {
444 bol = true;
445 if (sin != null)
446 end = sin.getPosition() - 1;
447 eol1 = b;
448 if (b == '\r') {
449 in.mark(1);
450 if ((b = in.read()) == '\n')
451 eol2 = b;
452 else
453 in.reset();
454 }
455 } else {
456 bol = false;
457 if (buf != null)
458 buf.write(b);
459 }
460 }
461
462 /*
463 * Create a MimeBody element to represent this body part.
464 */
465 MimeBodyPart part;
466 if (sin != null)
467 part = createMimeBodyPart(sin.newStream(start, end));
468 else
469 part = createMimeBodyPart(headers, buf.getBytes(), buf.getCount());
470 addBodyPart(part);
471 }
472 } catch (IOException ioex) {
473 throw new MessagingException("IO Error", ioex);
474 }
475
476 if (!ignoreMissingEndBoundary && !foundClosingBoundary && sin== null) {
477 throw new MessagingException("Missing End Boundary for Mime Package : EOF while skipping headers");
478 }
479 parsed = true;
480 }
481
482 /**
483 * Create and return an InternetHeaders object that loads the
484 * headers from the given InputStream. Subclasses can override
485 * this method to return a subclass of InternetHeaders, if
486 * necessary. This implementation simply constructs and returns
487 * an InternetHeaders object.
488 *
489 * @param is the InputStream to read the headers from
490 * @exception MessagingException
491 * @since JavaMail 1.2
492 */
493 protected InternetHeaders createInternetHeaders(InputStream is)
494 throws MessagingException {
495 return new InternetHeaders(is);
496 }
497
498 /**
499 * Create and return a MimeBodyPart object to represent a
500 * body part parsed from the InputStream. Subclasses can override
501 * this method to return a subclass of MimeBodyPart, if
502 * necessary. This implementation simply constructs and returns
503 * a MimeBodyPart object.
504 *
505 * @param headers the headers for the body part
506 * @param content the content of the body part
507 * @since JavaMail 1.2
508 */
509 protected MimeBodyPart createMimeBodyPart(InternetHeaders headers, byte[] content, int len) {
510 return new MimeBodyPart(headers, content,len);
511 }
512
513 /**
514 * Create and return a MimeBodyPart object to represent a
515 * body part parsed from the InputStream. Subclasses can override
516 * this method to return a subclass of MimeBodyPart, if
517 * necessary. This implementation simply constructs and returns
518 * a MimeBodyPart object.
519 *
520 * @param is InputStream containing the body part
521 * @exception MessagingException
522 * @since JavaMail 1.2
523 */
524 protected MimeBodyPart createMimeBodyPart(InputStream is) throws MessagingException {
525 return new MimeBodyPart(is);
526 }
527
528 /**
529 * Setup this MimeMultipart object from the given MultipartDataSource. <p>
530 *
531 * The method adds the MultipartDataSource's MimeBodyPart
532 * objects into this MimeMultipart. This MimeMultipart's contentType is
533 * set to that of the MultipartDataSource. <p>
534 *
535 * This method is typically used in those cases where one
536 * has a multipart data source that has already been pre-parsed into
537 * the individual body parts (for example, an IMAP datasource), but
538 * needs to create an appropriate MimeMultipart subclass that represents
539 * a specific multipart subtype.
540 *
541 * @param mp MimeMultipart datasource
542 */
543
544 protected void setMultipartDataSource(MultipartDataSource mp)
545 throws MessagingException {
546 contentType = new ContentType(mp.getContentType());
547
548 int count = mp.getCount();
549 for (int i = 0; i < count; i++)
550 addBodyPart(mp.getBodyPart(i));
551 }
552
553 /**
554 * Return the content-type of this MimeMultipart. <p>
555 *
556 * This implementation just returns the value of the
557 * <code>contentType</code> field.
558 *
559 * @return content-type
560 * @see #contentType
561 */
562 public ContentType getContentType() {
563 return contentType;
564 }
565
566 /**
567 * Remove the specified part from the multipart message.
568 * Shifts all the parts after the removed part down one.
569 *
570 * @param part The part to remove
571 * @return true if part removed, false otherwise
572 * @exception MessagingException if no such MimeBodyPart exists
573 */
574 public boolean removeBodyPart(MimeBodyPart part) throws MessagingException {
575 if (parts == null)
576 throw new MessagingException("No such body part");
577
578 boolean ret = parts.remove(part);
579 part.setParent(null);
580 return ret;
581 }
582
583 /**
584 * Remove the part at specified location (starting from 0).
585 * Shifts all the parts after the removed part down one.
586 *
587 * @param index Index of the part to remove
588 * @exception IndexOutOfBoundsException if the given index
589 * is out of range.
590 */
591 public void removeBodyPart(int index) {
592 if (parts == null)
593 throw new IndexOutOfBoundsException("No such BodyPart");
594
595 MimeBodyPart part = (MimeBodyPart)parts.get(index);
596 parts.remove(index);
597 part.setParent(null);
598 }
599
600 /**
601 * Adds a MimeBodyPart to the multipart. The MimeBodyPart is appended to
602 * the list of existing Parts.
603 *
604 * @param part The MimeBodyPart to be appended
605 */
606 public synchronized void addBodyPart(MimeBodyPart part) {
607 if (parts == null)
608 parts = new FinalArrayList();
609
610 parts.add(part);
611 part.setParent(this);
612 }
613
614 /**
615 * Adds a MimeBodyPart at position <code>index</code>.
616 * If <code>index</code> is not the last one in the list,
617 * the subsequent parts are shifted up. If <code>index</code>
618 * is larger than the number of parts present, the
619 * MimeBodyPart is appended to the end.
620 *
621 * @param part The MimeBodyPart to be inserted
622 * @param index Location where to insert the part
623 */
624 public synchronized void addBodyPart(MimeBodyPart part, int index) {
625 if (parts == null)
626 parts = new FinalArrayList();
627
628 parts.add(index,part);
629 part.setParent(this);
630 }
631
632 /**
633 * Return the <code>MimeBodyPart</code> that contains this <code>MimeMultipart</code>
634 * object, or <code>null</code> if not known.
635 * @since JavaMail 1.1
636 */
637 MimeBodyPart getParent() {
638 return parent;
639 }
640
641 /**
642 * Set the parent of this <code>MimeMultipart</code> to be the specified
643 * <code>MimeBodyPart</code>. Normally called by the <code>Message</code>
644 * or <code>MimeBodyPart</code> <code>setContent(MimeMultipart)</code> method.
645 * <code>parent</code> may be <code>null</code> if the
646 * <code>MimeMultipart</code> is being removed from its containing
647 * <code>MimeBodyPart</code>.
648 * @since JavaMail 1.1
649 */
650 void setParent(MimeBodyPart parent) {
651 this.parent = parent;
652 }
653 }

mercurial