001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019 020 package javax.mail.internet; 021 022 import java.io.IOException; 023 import java.io.InputStream; 024 import java.io.OutputStream; 025 import java.io.BufferedInputStream; 026 import java.io.PushbackInputStream; 027 import javax.activation.DataSource; 028 import javax.mail.BodyPart; 029 import javax.mail.MessagingException; 030 import javax.mail.Multipart; 031 import javax.mail.MultipartDataSource; 032 033 /** 034 * @version $Rev: 467553 $ $Date: 2006-10-25 06:01:51 +0200 (Mi, 25. Okt 2006) $ 035 */ 036 public class MimeMultipart extends Multipart { 037 /** 038 * DataSource that provides our InputStream. 039 */ 040 protected DataSource ds; 041 /** 042 * Indicates if the data has been parsed. 043 */ 044 protected boolean parsed = true; 045 046 private transient ContentType type; 047 048 /** 049 * Create an empty MimeMultipart with content type "multipart/mixed" 050 */ 051 public MimeMultipart() { 052 this("mixed"); 053 } 054 055 /** 056 * Create an empty MimeMultipart with the subtype supplied. 057 * 058 * @param subtype the subtype 059 */ 060 public MimeMultipart(String subtype) { 061 type = new ContentType("multipart", subtype, null); 062 type.setParameter("boundary", getBoundary()); 063 contentType = type.toString(); 064 } 065 066 /** 067 * Create a MimeMultipart from the supplied DataSource. 068 * 069 * @param dataSource the DataSource to use 070 * @throws MessagingException 071 */ 072 public MimeMultipart(DataSource dataSource) throws MessagingException { 073 ds = dataSource; 074 if (dataSource instanceof MultipartDataSource) { 075 super.setMultipartDataSource((MultipartDataSource) dataSource); 076 parsed = true; 077 } else { 078 type = new ContentType(ds.getContentType()); 079 contentType = type.toString(); 080 parsed = false; 081 } 082 } 083 084 public void setSubType(String subtype) throws MessagingException { 085 type.setSubType(subtype); 086 contentType = type.toString(); 087 } 088 089 public int getCount() throws MessagingException { 090 parse(); 091 return super.getCount(); 092 } 093 094 public synchronized BodyPart getBodyPart(int part) throws MessagingException { 095 parse(); 096 return super.getBodyPart(part); 097 } 098 099 public BodyPart getBodyPart(String cid) throws MessagingException { 100 parse(); 101 for (int i = 0; i < parts.size(); i++) { 102 MimeBodyPart bodyPart = (MimeBodyPart) parts.get(i); 103 if (cid.equals(bodyPart.getContentID())) { 104 return bodyPart; 105 } 106 } 107 return null; 108 } 109 110 protected void updateHeaders() throws MessagingException { 111 parse(); 112 for (int i = 0; i < parts.size(); i++) { 113 MimeBodyPart bodyPart = (MimeBodyPart) parts.get(i); 114 bodyPart.updateHeaders(); 115 } 116 } 117 118 private static byte[] dash = { '-', '-' }; 119 private static byte[] crlf = { 13, 10 }; 120 121 public void writeTo(OutputStream out) throws IOException, MessagingException { 122 parse(); 123 String boundary = type.getParameter("boundary"); 124 byte[] bytes = boundary.getBytes(); 125 for (int i = 0; i < parts.size(); i++) { 126 BodyPart bodyPart = (BodyPart) parts.get(i); 127 out.write(dash); 128 out.write(bytes); 129 out.write(crlf); 130 bodyPart.writeTo(out); 131 out.write(crlf); 132 } 133 out.write(dash); 134 out.write(bytes); 135 out.write(dash); 136 out.write(crlf); 137 out.flush(); 138 } 139 140 protected void parse() throws MessagingException { 141 if (parsed) { 142 return; 143 } 144 try { 145 ContentType cType = new ContentType(contentType); 146 byte[] boundary = ("--" + cType.getParameter("boundary")).getBytes(); 147 InputStream is = new BufferedInputStream(ds.getInputStream()); 148 PushbackInputStream pushbackInStream = new PushbackInputStream(is, 149 (boundary.length + 2)); 150 readTillFirstBoundary(pushbackInStream, boundary); 151 while (pushbackInStream.available()>0){ 152 MimeBodyPartInputStream partStream; 153 partStream = new MimeBodyPartInputStream(pushbackInStream, 154 boundary); 155 addBodyPart(new MimeBodyPart(partStream)); 156 } 157 } catch (Exception e){ 158 throw new MessagingException(e.toString(),e); 159 } 160 parsed = true; 161 } 162 163 /** 164 * Move the read pointer to the begining of the first part 165 * read till the end of first boundary 166 * 167 * @param pushbackInStream 168 * @param boundary 169 * @throws MessagingException 170 */ 171 private boolean readTillFirstBoundary(PushbackInputStream pushbackInStream, byte[] boundary) throws MessagingException { 172 try { 173 while (pushbackInStream.available() > 0) { 174 int value = pushbackInStream.read(); 175 if ((byte) value == boundary[0]) { 176 int boundaryIndex = 0; 177 while (pushbackInStream.available() > 0 && (boundaryIndex < boundary.length) 178 && ((byte) value == boundary[boundaryIndex])) { 179 value = pushbackInStream.read(); 180 if (value == -1) 181 throw new MessagingException( 182 "Unexpected End of Stream while searching for first Mime Boundary"); 183 boundaryIndex++; 184 } 185 if (boundaryIndex == boundary.length) { // boundary found 186 pushbackInStream.read(); 187 return true; 188 } 189 } 190 } 191 } catch (IOException ioe) { 192 throw new MessagingException(ioe.toString(), ioe); 193 } 194 return false; 195 } 196 197 protected InternetHeaders createInternetHeaders(InputStream in) throws MessagingException { 198 return new InternetHeaders(in); 199 } 200 201 protected MimeBodyPart createMimeBodyPart(InternetHeaders headers, byte[] data) throws MessagingException { 202 return new MimeBodyPart(headers, data); 203 } 204 205 protected MimeBodyPart createMimeBodyPart(InputStream in) throws MessagingException { 206 return new MimeBodyPart(in); 207 } 208 209 private static int part; 210 211 private synchronized static String getBoundary() { 212 int i; 213 synchronized(MimeMultipart.class) { 214 i = part++; 215 } 216 StringBuffer buf = new StringBuffer(64); 217 buf.append("----=_Part_").append(i).append('_').append((new Object()).hashCode()).append('.').append(System.currentTimeMillis()); 218 return buf.toString(); 219 } 220 221 private class MimeBodyPartInputStream extends InputStream { 222 PushbackInputStream inStream; 223 boolean boundaryFound = false; 224 byte[] boundary; 225 226 public MimeBodyPartInputStream(PushbackInputStream inStream, 227 byte[] boundary) { 228 super(); 229 this.inStream = inStream; 230 this.boundary = boundary; 231 } 232 233 public int read() throws IOException { 234 if (boundaryFound) { 235 return -1; 236 } 237 // read the next value from stream 238 int value = inStream.read(); 239 // 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. 240 // This logic introduced to handle it 241 //TODO look more in to this && for a better way to do this 242 if (value == 13) { 243 value = inStream.read(); 244 if (value != 10) { 245 inStream.unread(value); 246 return 13; 247 } else { 248 value = inStream.read(); 249 if ((byte) value != boundary[0]) { 250 inStream.unread(value); 251 inStream.unread(10); 252 return 13; 253 } 254 } 255 } else if ((byte) value != boundary[0]) { 256 return value; 257 } 258 // read value is the first byte of the boundary. Start matching the 259 // next characters to find a boundary 260 int boundaryIndex = 0; 261 while ((boundaryIndex < boundary.length) 262 && ((byte) value == boundary[boundaryIndex])) { 263 value = inStream.read(); 264 boundaryIndex++; 265 } 266 if (boundaryIndex == boundary.length) { // boundary found 267 boundaryFound = true; 268 // read the end of line character 269 if (inStream.read() == 45 && value == 45) { 270 //Last mime boundary should have a succeeding "--" 271 //as we are on it, read the terminating CRLF 272 inStream.read(); 273 inStream.read(); 274 } 275 return -1; 276 } 277 // Boundary not found. Restoring bytes skipped. 278 // write first skipped byte, push back the rest 279 if (value != -1) { // Stream might have ended 280 inStream.unread(value); 281 } 282 inStream.unread(boundary, 1, boundaryIndex - 1); 283 return boundary[0]; 284 } 285 } 286 }