View Javadoc

1   /**
2    *
3    * Copyright 2003-2004 The Apache Software Foundation
4    *
5    *  Licensed under the Apache License, Version 2.0 (the "License");
6    *  you may not use this file except in compliance with the License.
7    *  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  
18  package javax.mail.internet;
19  
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.OutputStream;
23  import java.io.BufferedInputStream;
24  import java.io.PushbackInputStream;
25  import javax.activation.DataSource;
26  import javax.mail.BodyPart;
27  import javax.mail.MessagingException;
28  import javax.mail.Multipart;
29  import javax.mail.MultipartDataSource;
30  
31  /**
32   * @version $Rev: 398618 $ $Date: 2006-05-01 08:18:57 -0700 (Mon, 01 May 2006) $
33   */
34  public class MimeMultipart extends Multipart {
35      /**
36       * DataSource that provides our InputStream.
37       */
38      protected DataSource ds;
39      /**
40       * Indicates if the data has been parsed.
41       */
42      protected boolean parsed = true;
43  
44      private transient ContentType type;
45  
46      /**
47       * Create an empty MimeMultipart with content type "multipart/mixed"
48       */
49      public MimeMultipart() {
50          this("mixed");
51      }
52  
53      /**
54       * Create an empty MimeMultipart with the subtype supplied.
55       *
56       * @param subtype the subtype
57       */
58      public MimeMultipart(String subtype) {
59          type = new ContentType("multipart", subtype, null);
60          type.setParameter("boundary", getBoundary());
61          contentType = type.toString();
62      }
63  
64      /**
65       * Create a MimeMultipart from the supplied DataSource.
66       *
67       * @param dataSource the DataSource to use
68       * @throws MessagingException
69       */
70      public MimeMultipart(DataSource dataSource) throws MessagingException {
71          ds = dataSource;
72          if (dataSource instanceof MultipartDataSource) {
73              super.setMultipartDataSource((MultipartDataSource) dataSource);
74              parsed = true;
75          } else {
76              type = new ContentType(ds.getContentType());
77              contentType = type.toString();
78              parsed = false;
79          }
80      }
81  
82      public void setSubType(String subtype) throws MessagingException {
83          type.setSubType(subtype);
84          contentType = type.toString();
85      }
86  
87      public int getCount() throws MessagingException {
88          parse();
89          return super.getCount();
90      }
91  
92      public synchronized BodyPart getBodyPart(int part) throws MessagingException {
93          parse();
94          return super.getBodyPart(part);
95      }
96  
97      public BodyPart getBodyPart(String cid) throws MessagingException {
98          parse();
99          for (int i = 0; i < parts.size(); i++) {
100             MimeBodyPart bodyPart = (MimeBodyPart) parts.get(i);
101             if (cid.equals(bodyPart.getContentID())) {
102                 return bodyPart;
103             }
104         }
105         return null;
106     }
107 
108     protected void updateHeaders() throws MessagingException {
109         parse();
110         for (int i = 0; i < parts.size(); i++) {
111             MimeBodyPart bodyPart = (MimeBodyPart) parts.get(i);
112             bodyPart.updateHeaders();
113         }
114     }
115 
116     private static byte[] dash = { '-', '-' };
117     private static byte[] crlf = { 13, 10 };
118 
119     public void writeTo(OutputStream out) throws IOException, MessagingException {
120         parse();
121         String boundary = type.getParameter("boundary");
122         byte[] bytes = boundary.getBytes();
123         for (int i = 0; i < parts.size(); i++) {
124             BodyPart bodyPart = (BodyPart) parts.get(i);
125             out.write(dash);
126             out.write(bytes);
127             out.write(crlf);
128             bodyPart.writeTo(out);
129             out.write(crlf);
130         }
131         out.write(dash);
132         out.write(bytes);
133         out.write(dash);
134         out.write(crlf);
135         out.flush();
136     }
137 
138     protected void parse() throws MessagingException {
139         if (parsed) {
140             return;
141         }
142         try {
143             ContentType cType = new ContentType(contentType);
144             byte[] boundary = ("--" + cType.getParameter("boundary")).getBytes();
145             InputStream is = new BufferedInputStream(ds.getInputStream());
146             PushbackInputStream pushbackInStream = new PushbackInputStream(is,
147                     (boundary.length + 2));
148             readTillFirstBoundary(pushbackInStream, boundary);
149             while (pushbackInStream.available()>0){
150                 MimeBodyPartInputStream partStream;
151                 partStream = new MimeBodyPartInputStream(pushbackInStream,
152                         boundary);
153                 addBodyPart(new MimeBodyPart(partStream));
154             }
155         } catch (Exception e){
156             throw new MessagingException(e.toString(),e);
157         }
158         parsed = true;
159     }
160 
161     /**
162      * Move the read pointer to the begining of the first part
163      * read till the end of first boundary
164      *
165      * @param pushbackInStream
166      * @param boundary
167      * @throws MessagingException
168      */
169     private boolean readTillFirstBoundary(PushbackInputStream pushbackInStream, byte[] boundary) throws MessagingException {
170         try {
171             while (pushbackInStream.available() > 0) {
172                 int value = pushbackInStream.read();
173                 if ((byte) value == boundary[0]) {
174                     int boundaryIndex = 0;
175                     while (pushbackInStream.available() > 0 && (boundaryIndex < boundary.length)
176                             && ((byte) value == boundary[boundaryIndex])) {
177                         value = pushbackInStream.read();
178                         if (value == -1)
179                             throw new MessagingException(
180                                     "Unexpected End of Stream while searching for first Mime Boundary");
181                         boundaryIndex++;
182                     }
183                     if (boundaryIndex == boundary.length) { // boundary found
184                         pushbackInStream.read();
185                         return true;
186                     }
187                 }
188             }
189         } catch (IOException ioe) {
190             throw new MessagingException(ioe.toString(), ioe);
191         }
192         return false;
193     }
194 
195     protected InternetHeaders createInternetHeaders(InputStream in) throws MessagingException {
196         return new InternetHeaders(in);
197     }
198 
199     protected MimeBodyPart createMimeBodyPart(InternetHeaders headers, byte[] data) throws MessagingException {
200         return new MimeBodyPart(headers, data);
201     }
202 
203     protected MimeBodyPart createMimeBodyPart(InputStream in) throws MessagingException {
204         return new MimeBodyPart(in);
205     }
206 
207     private static int part;
208 
209     private synchronized static String getBoundary() {
210         int i;
211         synchronized(MimeMultipart.class) {
212             i = part++;
213         }
214         StringBuffer buf = new StringBuffer(64);
215         buf.append("----=_Part_").append(i).append('_').append((new Object()).hashCode()).append('.').append(System.currentTimeMillis());
216         return buf.toString();
217     }
218 
219     private class MimeBodyPartInputStream extends InputStream {
220         PushbackInputStream inStream;
221         boolean boundaryFound = false;
222         byte[] boundary;
223 
224         public MimeBodyPartInputStream(PushbackInputStream inStream,
225                                        byte[] boundary) {
226             super();
227             this.inStream = inStream;
228             this.boundary = boundary;
229         }
230 
231         public int read() throws IOException {
232             if (boundaryFound) {
233                 return -1;
234             }
235             // read the next value from stream
236             int value = inStream.read();
237             // A problem occured because all the mime parts tends to have a /r/n at the end. Making it hard to transform them to correct DataSources.
238             // This logic introduced to handle it
239             //TODO look more in to this && for a better way to do this
240             if (value == 13) {
241                 value = inStream.read();
242                 if (value != 10) {
243                     inStream.unread(value);
244                     return 13;
245                 } else {
246                     value = inStream.read();
247                     if ((byte) value != boundary[0]) {
248                         inStream.unread(value);
249                         inStream.unread(10);
250                         return 13;
251                     }
252                 }
253             } else if ((byte) value != boundary[0]) {
254                 return value;
255             }
256             // read value is the first byte of the boundary. Start matching the
257             // next characters to find a boundary
258             int boundaryIndex = 0;
259             while ((boundaryIndex < boundary.length)
260                     && ((byte) value == boundary[boundaryIndex])) {
261                 value = inStream.read();
262                 boundaryIndex++;
263             }
264             if (boundaryIndex == boundary.length) { // boundary found
265                 boundaryFound = true;
266                 // read the end of line character
267                 if (inStream.read() == 45 && value == 45) {
268                     //Last mime boundary should have a succeeding "--"
269                     //as we are on it, read the terminating CRLF
270                     inStream.read();
271                     inStream.read();
272                 }
273                 return -1;
274             }
275             // Boundary not found. Restoring bytes skipped.
276             // write first skipped byte, push back the rest
277             if (value != -1) { // Stream might have ended
278                 inStream.unread(value);
279             }
280             inStream.unread(boundary, 1, boundaryIndex - 1);
281             return boundary[0];
282         }
283     }
284 }