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) {
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
236 int value = inStream.read();
237
238
239
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
257
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) {
265 boundaryFound = true;
266
267 if (inStream.read() == 45 && value == 45) {
268
269
270 inStream.read();
271 inStream.read();
272 }
273 return -1;
274 }
275
276
277 if (value != -1) {
278 inStream.unread(value);
279 }
280 inStream.unread(boundary, 1, boundaryIndex - 1);
281 return boundary[0];
282 }
283 }
284 }