001 /**
002 *
003 * Copyright 2003-2004 The Apache Software Foundation
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 package javax.mail.internet;
019
020 import java.io.IOException;
021 import java.io.InputStream;
022 import java.io.OutputStream;
023 import java.io.BufferedInputStream;
024 import java.io.PushbackInputStream;
025 import javax.activation.DataSource;
026 import javax.mail.BodyPart;
027 import javax.mail.MessagingException;
028 import javax.mail.Multipart;
029 import javax.mail.MultipartDataSource;
030
031 /**
032 * @version $Rev: 398618 $ $Date: 2006-05-01 08:18:57 -0700 (Mon, 01 May 2006) $
033 */
034 public class MimeMultipart extends Multipart {
035 /**
036 * DataSource that provides our InputStream.
037 */
038 protected DataSource ds;
039 /**
040 * Indicates if the data has been parsed.
041 */
042 protected boolean parsed = true;
043
044 private transient ContentType type;
045
046 /**
047 * Create an empty MimeMultipart with content type "multipart/mixed"
048 */
049 public MimeMultipart() {
050 this("mixed");
051 }
052
053 /**
054 * Create an empty MimeMultipart with the subtype supplied.
055 *
056 * @param subtype the subtype
057 */
058 public MimeMultipart(String subtype) {
059 type = new ContentType("multipart", subtype, null);
060 type.setParameter("boundary", getBoundary());
061 contentType = type.toString();
062 }
063
064 /**
065 * Create a MimeMultipart from the supplied DataSource.
066 *
067 * @param dataSource the DataSource to use
068 * @throws MessagingException
069 */
070 public MimeMultipart(DataSource dataSource) throws MessagingException {
071 ds = dataSource;
072 if (dataSource instanceof MultipartDataSource) {
073 super.setMultipartDataSource((MultipartDataSource) dataSource);
074 parsed = true;
075 } else {
076 type = new ContentType(ds.getContentType());
077 contentType = type.toString();
078 parsed = false;
079 }
080 }
081
082 public void setSubType(String subtype) throws MessagingException {
083 type.setSubType(subtype);
084 contentType = type.toString();
085 }
086
087 public int getCount() throws MessagingException {
088 parse();
089 return super.getCount();
090 }
091
092 public synchronized BodyPart getBodyPart(int part) throws MessagingException {
093 parse();
094 return super.getBodyPart(part);
095 }
096
097 public BodyPart getBodyPart(String cid) throws MessagingException {
098 parse();
099 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 }