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 }