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    }