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 org.apache.james.mime4j.parser;
021    
022    import java.io.IOException;
023    import java.io.InputStream;
024    
025    import org.apache.james.mime4j.MimeException;
026    import org.apache.james.mime4j.codec.Base64InputStream;
027    import org.apache.james.mime4j.codec.QuotedPrintableInputStream;
028    import org.apache.james.mime4j.descriptor.BodyDescriptor;
029    import org.apache.james.mime4j.io.BufferedLineReaderInputStream;
030    import org.apache.james.mime4j.io.LimitedInputStream;
031    import org.apache.james.mime4j.io.LineNumberSource;
032    import org.apache.james.mime4j.io.LineReaderInputStream;
033    import org.apache.james.mime4j.io.LineReaderInputStreamAdaptor;
034    import org.apache.james.mime4j.io.MimeBoundaryInputStream;
035    import org.apache.james.mime4j.util.ByteSequence;
036    import org.apache.james.mime4j.util.ContentUtil;
037    import org.apache.james.mime4j.util.MimeUtil;
038    
039    public class MimeEntity extends AbstractEntity {
040    
041        /**
042         * Internal state, not exposed.
043         */
044        private static final int T_IN_BODYPART = -2;
045        /**
046         * Internal state, not exposed.
047         */
048        private static final int T_IN_MESSAGE = -3;
049    
050        private final LineNumberSource lineSource;
051        private final BufferedLineReaderInputStream inbuffer;
052        
053        private int recursionMode;
054        private MimeBoundaryInputStream mimeStream;
055        private LineReaderInputStreamAdaptor dataStream;
056        private boolean skipHeader;
057        
058        private byte[] tmpbuf;
059        
060        public MimeEntity(
061                LineNumberSource lineSource,
062                BufferedLineReaderInputStream inbuffer,
063                BodyDescriptor parent, 
064                int startState, 
065                int endState,
066                MimeEntityConfig config) {
067            super(parent, startState, endState, config);
068            this.lineSource = lineSource;
069            this.inbuffer = inbuffer;
070            this.dataStream = new LineReaderInputStreamAdaptor(
071                    inbuffer,
072                    config.getMaxLineLen());
073            this.skipHeader = false;
074        }
075    
076        public MimeEntity(
077                LineNumberSource lineSource,
078                BufferedLineReaderInputStream inbuffer,
079                BodyDescriptor parent, 
080                int startState, 
081                int endState) {
082            this(lineSource, inbuffer, parent, startState, endState, 
083                    new MimeEntityConfig());
084        }
085    
086        public int getRecursionMode() {
087            return recursionMode;
088        }
089    
090        public void setRecursionMode(int recursionMode) {
091            this.recursionMode = recursionMode;
092        }
093    
094        public void skipHeader(String contentType) {
095            if (state != EntityStates.T_START_MESSAGE) {
096                throw new IllegalStateException("Invalid state: " + stateToString(state));
097            }
098            skipHeader = true;
099            ByteSequence raw = ContentUtil.encode("Content-Type: " + contentType);
100            body.addField(new RawField(raw, 12));
101        }
102    
103        @Override
104        protected int getLineNumber() {
105            if (lineSource == null)
106                return -1;
107            else
108                return lineSource.getLineNumber();
109        }
110        
111        @Override
112        protected LineReaderInputStream getDataStream() {
113            return dataStream;
114        }
115        
116        public EntityStateMachine advance() throws IOException, MimeException {
117            switch (state) {
118            case EntityStates.T_START_MESSAGE:
119                if (skipHeader) {
120                    state = EntityStates.T_END_HEADER;
121                } else {
122                    state = EntityStates.T_START_HEADER;
123                }
124                break;
125            case EntityStates.T_START_BODYPART:
126                state = EntityStates.T_START_HEADER;
127                break;
128            case EntityStates.T_START_HEADER:
129            case EntityStates.T_FIELD:
130                state = parseField() ? EntityStates.T_FIELD : EntityStates.T_END_HEADER;
131                break;
132            case EntityStates.T_END_HEADER:
133                String mimeType = body.getMimeType();
134                if (recursionMode == RecursionMode.M_FLAT) {
135                    state = EntityStates.T_BODY;
136                } else if (MimeUtil.isMultipart(mimeType)) {
137                    state = EntityStates.T_START_MULTIPART;
138                    clearMimeStream();
139                } else if (recursionMode != RecursionMode.M_NO_RECURSE 
140                        && MimeUtil.isMessage(mimeType)) {
141                    state = T_IN_MESSAGE;
142                    return nextMessage();
143                } else {
144                    state = EntityStates.T_BODY;
145                }
146                break;
147            case EntityStates.T_START_MULTIPART:
148                if (dataStream.isUsed()) {
149                    advanceToBoundary();            
150                    state = EntityStates.T_END_MULTIPART;
151                } else {
152                    createMimeStream();
153                    state = EntityStates.T_PREAMBLE;
154                }
155                break;
156            case EntityStates.T_PREAMBLE:
157                advanceToBoundary();            
158                if (mimeStream.isLastPart()) {
159                    clearMimeStream();
160                    state = EntityStates.T_END_MULTIPART;
161                } else {
162                    clearMimeStream();
163                    createMimeStream();
164                    state = T_IN_BODYPART;
165                    return nextMimeEntity();
166                }
167                break;
168            case T_IN_BODYPART:
169                advanceToBoundary();
170                if (mimeStream.eof() && !mimeStream.isLastPart()) {
171                    monitor(Event.MIME_BODY_PREMATURE_END);
172                } else {
173                    if (!mimeStream.isLastPart()) {
174                        clearMimeStream();
175                        createMimeStream();
176                        state = T_IN_BODYPART;
177                        return nextMimeEntity();
178                    }
179                }
180                clearMimeStream();
181                state = EntityStates.T_EPILOGUE;
182                break;
183            case EntityStates.T_EPILOGUE:
184                state = EntityStates.T_END_MULTIPART;
185                break;
186            case EntityStates.T_BODY:
187            case EntityStates.T_END_MULTIPART:
188            case T_IN_MESSAGE:
189                state = endState;
190                break;
191            default:
192                if (state == endState) {
193                    state = EntityStates.T_END_OF_STREAM;
194                    break;
195                }
196                throw new IllegalStateException("Invalid state: " + stateToString(state));
197            }
198            return null;
199        }
200    
201        private void createMimeStream() throws MimeException, IOException {
202            String boundary = body.getBoundary();
203            int bufferSize = 2 * boundary.length();
204            if (bufferSize < 4096) {
205                bufferSize = 4096;
206            }
207            try {
208                if (mimeStream != null) {
209                    mimeStream = new MimeBoundaryInputStream(
210                            new BufferedLineReaderInputStream(
211                                    mimeStream, 
212                                    bufferSize, 
213                                    config.getMaxLineLen()), 
214                            boundary);
215                } else {
216                    inbuffer.ensureCapacity(bufferSize);
217                    mimeStream = new MimeBoundaryInputStream(inbuffer, boundary);
218                }
219            } catch (IllegalArgumentException e) {
220                // thrown when boundary is too long
221                throw new MimeException(e.getMessage(), e);
222            }
223            dataStream = new LineReaderInputStreamAdaptor(
224                    mimeStream,
225                    config.getMaxLineLen()); 
226        }
227        
228        private void clearMimeStream() {
229            mimeStream = null;
230            dataStream = new LineReaderInputStreamAdaptor(
231                    inbuffer,
232                    config.getMaxLineLen()); 
233        }
234        
235        private void advanceToBoundary() throws IOException {
236            if (!dataStream.eof()) {
237                if (tmpbuf == null) {
238                    tmpbuf = new byte[2048];
239                }
240                InputStream instream = getLimitedContentStream();
241                while (instream.read(tmpbuf)!= -1) {
242                }
243            }
244        }
245        
246        private EntityStateMachine nextMessage() {
247            String transferEncoding = body.getTransferEncoding();
248            InputStream instream;
249            if (MimeUtil.isBase64Encoding(transferEncoding)) {
250                log.debug("base64 encoded message/rfc822 detected");
251                instream = new Base64InputStream(dataStream);                    
252            } else if (MimeUtil.isQuotedPrintableEncoded(transferEncoding)) {
253                log.debug("quoted-printable encoded message/rfc822 detected");
254                instream = new QuotedPrintableInputStream(dataStream);                    
255            } else {
256                instream = dataStream;
257            }
258            
259            if (recursionMode == RecursionMode.M_RAW) {
260                RawEntity message = new RawEntity(instream);
261                return message;
262            } else {
263                MimeEntity message = new MimeEntity(
264                        lineSource, 
265                        new BufferedLineReaderInputStream(
266                                instream, 
267                                4 * 1024,
268                                config.getMaxLineLen()),
269                        body, 
270                        EntityStates.T_START_MESSAGE, 
271                        EntityStates.T_END_MESSAGE,
272                        config);
273                message.setRecursionMode(recursionMode);
274                return message;
275            }
276        }
277        
278        private EntityStateMachine nextMimeEntity() {
279            if (recursionMode == RecursionMode.M_RAW) {
280                RawEntity message = new RawEntity(mimeStream);
281                return message;
282            } else {
283                BufferedLineReaderInputStream stream = new BufferedLineReaderInputStream(
284                        mimeStream, 
285                        4 * 1024,
286                        config.getMaxLineLen());
287                MimeEntity mimeentity = new MimeEntity(
288                        lineSource, 
289                        stream,
290                        body, 
291                        EntityStates.T_START_BODYPART, 
292                        EntityStates.T_END_BODYPART,
293                        config);
294                mimeentity.setRecursionMode(recursionMode);
295                return mimeentity;
296            }
297        }
298        
299        private InputStream getLimitedContentStream() {
300            long maxContentLimit = config.getMaxContentLen();
301            if (maxContentLimit >= 0) {
302                return new LimitedInputStream(dataStream, maxContentLimit);
303            } else {
304                return dataStream;
305            }
306        }
307        
308        public InputStream getContentStream() {
309            switch (state) {
310            case EntityStates.T_START_MULTIPART:
311            case EntityStates.T_PREAMBLE:
312            case EntityStates.T_EPILOGUE:
313            case EntityStates.T_BODY:
314                return getLimitedContentStream();
315            default:
316                throw new IllegalStateException("Invalid state: " + stateToString(state));
317            }
318        }
319    
320    }