source: contrib/MailArchiver/sources/vendor/mime4j/apache-mime4j-0.7-SNAPSHOT-20110327.010440-17/core/src/main/java/org/apache/james/mime4j/stream/AbstractEntity.java @ 6785

Revision 6785, 10.9 KB checked in by rafaelraymundo, 12 years ago (diff)

Ticket #2946 - Liberado codigo do MailArchiver?. Documentação na subpasta DOCS.

Line 
1/****************************************************************
2 * Licensed to the Apache Software Foundation (ASF) under one   *
3 * or more contributor license agreements.  See the NOTICE file *
4 * distributed with this work for additional information        *
5 * regarding copyright ownership.  The ASF licenses this file   *
6 * to you under the Apache License, Version 2.0 (the            *
7 * "License"); you may not use this file except in compliance   *
8 * with the License.  You may obtain a copy of the License at   *
9 *                                                              *
10 *   http://www.apache.org/licenses/LICENSE-2.0                 *
11 *                                                              *
12 * Unless required by applicable law or agreed to in writing,   *
13 * software distributed under the License is distributed on an  *
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
15 * KIND, either express or implied.  See the License for the    *
16 * specific language governing permissions and limitations      *
17 * under the License.                                           *
18 ****************************************************************/
19
20package org.apache.james.mime4j.stream;
21
22import java.io.IOException;
23
24import org.apache.james.mime4j.MimeException;
25import org.apache.james.mime4j.codec.DecodeMonitor;
26import org.apache.james.mime4j.io.LineReaderInputStream;
27import org.apache.james.mime4j.io.MaxHeaderLengthLimitException;
28import org.apache.james.mime4j.io.MaxHeaderLimitException;
29import org.apache.james.mime4j.io.MaxLineLimitException;
30import org.apache.james.mime4j.util.ByteArrayBuffer;
31import org.apache.james.mime4j.util.CharsetUtil;
32
33/**
34 * Abstract MIME entity.
35 */
36public abstract class AbstractEntity implements EntityStateMachine {
37
38    protected final EntityState startState;
39    protected final EntityState endState;
40    protected final MimeEntityConfig config;
41    protected final MutableBodyDescriptor body;
42   
43    protected EntityState state;
44
45    private final ByteArrayBuffer linebuf;
46
47    private int lineCount;
48    private RawField field;
49    private boolean endOfHeader;
50    private int headerCount;
51    protected final DecodeMonitor monitor;
52
53    AbstractEntity(
54            MutableBodyDescriptor body,
55            EntityState startState,
56            EntityState endState,
57            MimeEntityConfig config,
58            DecodeMonitor monitor) {
59        this.state = startState;
60        this.startState = startState;
61        this.endState = endState;
62        this.config = config;
63        this.body = body;
64        this.linebuf = new ByteArrayBuffer(64);
65        this.lineCount = 0;
66        this.endOfHeader = false;
67        this.headerCount = 0;
68        this.monitor = monitor;
69    }
70
71    public EntityState getState() {
72        return state;
73    }
74
75    /**
76     * Returns the current line number or <code>-1</code> if line number
77     * information is not available.
78     */
79    protected abstract int getLineNumber();
80   
81    protected abstract LineReaderInputStream getDataStream();
82   
83    private ByteArrayBuffer fillFieldBuffer() throws IOException, MimeException {
84        if (endOfHeader)
85            throw new IllegalStateException();
86
87        int maxHeaderLen = config.getMaxHeaderLen();
88        LineReaderInputStream instream = getDataStream();
89        ByteArrayBuffer fieldbuf = new ByteArrayBuffer(64);
90
91        try {
92            for (;;) {
93                // If there's still data stuck in the line buffer
94                // copy it to the field buffer
95                int len = linebuf.length();
96                if (maxHeaderLen > 0 && fieldbuf.length() + len >= maxHeaderLen) {
97                    throw new MaxHeaderLengthLimitException("Maximum header length limit exceeded");
98                }
99                if (len > 0) {
100                    fieldbuf.append(linebuf.buffer(), 0, len);
101                }
102                linebuf.clear();
103                if (instream.readLine(linebuf) == -1) {
104                    monitor(Event.HEADERS_PREMATURE_END);
105                    endOfHeader = true;
106                    break;
107                }
108                len = linebuf.length();
109                if (len > 0 && linebuf.byteAt(len - 1) == '\n') {
110                    len--;
111                }
112                if (len > 0 && linebuf.byteAt(len - 1) == '\r') {
113                    len--;
114                }
115                if (len == 0) {
116                    // empty line detected
117                    endOfHeader = true;
118                    break;
119                }
120                lineCount++;
121                if (lineCount > 1) {
122                    int ch = linebuf.byteAt(0);
123                    if (ch != CharsetUtil.SP && ch != CharsetUtil.HT) {
124                        // new header detected
125                        break;
126                    }
127                }
128            }
129        } catch (MaxLineLimitException e) {
130            throw new MimeException(e);
131        }
132
133        return fieldbuf;
134    }
135
136    protected boolean parseField() throws MimeException, IOException {
137        int maxHeaderCount = config.getMaxHeaderCount();
138        // the loop is here to transparently skip invalid headers
139        for (;;) {
140            if (endOfHeader) {
141                return false;
142            }
143            if (maxHeaderCount > 0 && headerCount >= maxHeaderCount) {
144                throw new MaxHeaderLimitException("Maximum header limit exceeded");
145            }
146
147            ByteArrayBuffer fieldbuf = fillFieldBuffer();
148            headerCount++;
149
150            // Strip away line delimiter
151            int origLen = fieldbuf.length();
152            int len = fieldbuf.length();
153            if (len > 0 && fieldbuf.byteAt(len - 1) == '\n') {
154                len--;
155            }
156            if (len > 0 && fieldbuf.byteAt(len - 1) == '\r') {
157                len--;
158            }
159            fieldbuf.setLength(len);
160           
161            try {
162                field = new RawField(fieldbuf);
163                if (field.isObsoleteSyntax()) {
164                        monitor(Event.OBSOLETE_HEADER);
165                }
166                body.addField(field);
167                return true;
168            } catch (MimeException e) {
169                monitor(Event.INVALID_HEADER);
170                if (config.isMalformedHeaderStartsBody()) {
171                        fieldbuf.setLength(origLen);
172                        LineReaderInputStream instream = getDataStream();
173                        if (!instream.unread(fieldbuf)) throw new MimeParseEventException(Event.INVALID_HEADER);
174                        return false;
175                }
176            }
177        }
178    }
179
180    /**
181     * <p>Gets a descriptor for the current entity.
182     * This method is valid if {@link #getState()} returns:</p>
183     * <ul>
184     * <li>{@link EntityState#T_BODY}</li>
185     * <li>{@link EntityState#T_START_MULTIPART}</li>
186     * <li>{@link EntityState#T_EPILOGUE}</li>
187     * <li>{@link EntityState#T_PREAMBLE}</li>
188     * </ul>
189     * @return <code>BodyDescriptor</code>, not nulls
190     */
191    public BodyDescriptor getBodyDescriptor() {
192        switch (getState()) {
193        case T_BODY:
194        case T_START_MULTIPART:
195        case T_PREAMBLE:
196        case T_EPILOGUE:
197        case T_END_OF_STREAM:
198            return body;
199        default:
200            throw new IllegalStateException("Invalid state :" + stateToString(state));
201        }
202    }
203
204    /**
205     * This method is valid, if {@link #getState()} returns {@link EntityState#T_FIELD}.
206     * @return String with the fields raw contents.
207     * @throws IllegalStateException {@link #getState()} returns another
208     *   value than {@link EntityState#T_FIELD}.
209     */
210    public RawField getField() {
211        switch (getState()) {
212        case T_FIELD:
213            return field;
214        default:
215            throw new IllegalStateException("Invalid state :" + stateToString(state));
216        }
217    }
218
219    /**
220     * Monitors the given event.
221     * Subclasses may override to perform actions upon events.
222     * Base implementation logs at warn.
223     * @param event <code>Event</code>, not null
224     * @throws MimeException subclasses may elect to throw this exception upon
225     * invalid content
226     * @throws IOException subclasses may elect to throw this exception
227     */
228    protected void monitor(Event event) throws MimeException, IOException {
229        if (monitor.isListening()) {
230            String message = message(event);
231            if (monitor.warn(message, "ignoring")) {
232                throw new MimeParseEventException(event);
233            }
234        }
235    }
236   
237    /**
238     * Creates an indicative message suitable for display
239     * based on the given event and the current state of the system.
240     * @param event <code>Event</code>, not null
241     * @return message suitable for use as a message in an exception
242     * or for logging
243     */
244    protected String message(Event event) {
245        final String message;
246        if (event == null) {
247            message = "Event is unexpectedly null.";
248        } else {
249            message = event.toString();
250        }
251
252        int lineNumber = getLineNumber();
253        if (lineNumber <= 0)
254            return message;
255        else
256            return "Line " + lineNumber + ": " + message;
257    }
258
259    @Override
260    public String toString() {
261        return getClass().getName() + " [" + stateToString(state)
262        + "][" + body.getMimeType() + "][" + body.getBoundary() + "]";
263    }
264
265    /**
266     * Renders a state as a string suitable for logging.
267     * @param state
268     * @return rendered as string, not null
269     */
270    public static final String stateToString(EntityState state) {
271        final String result;
272        switch (state) {
273            case T_END_OF_STREAM:
274                result = "End of stream";
275                break;
276            case T_START_MESSAGE:
277                result = "Start message";
278                break;
279            case T_END_MESSAGE:
280                result = "End message";
281                break;
282            case T_RAW_ENTITY:
283                result = "Raw entity";
284                break;
285            case T_START_HEADER:
286                result = "Start header";
287                break;
288            case T_FIELD:
289                result = "Field";
290                break;
291            case T_END_HEADER:
292                result = "End header";
293                break;
294            case T_START_MULTIPART:
295                result = "Start multipart";
296                break;
297            case T_END_MULTIPART:
298                result = "End multipart";
299                break;
300            case T_PREAMBLE:
301                result = "Preamble";
302                break;
303            case T_EPILOGUE:
304                result = "Epilogue";
305                break;
306            case T_START_BODYPART:
307                result = "Start bodypart";
308                break;
309            case T_END_BODYPART:
310                result = "End bodypart";
311                break;
312            case T_BODY:
313                result = "Body";
314                break;
315            default:
316                result = "Unknown";
317                break;
318        }
319        return result;
320    }
321   
322}
Note: See TracBrowser for help on using the repository browser.