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 }