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

Revision 6785, 14.7 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.util.HashMap;
23import java.util.Map;
24
25import org.apache.james.mime4j.MimeException;
26import org.apache.james.mime4j.codec.DecodeMonitor;
27import org.apache.james.mime4j.util.MimeUtil;
28
29/**
30 * Encapsulates the values of the MIME-specific header fields
31 * (which starts with <code>Content-</code>).
32 */
33public class DefaultBodyDescriptor implements MutableBodyDescriptor {
34    private static final String US_ASCII = "us-ascii";
35
36    private static final String SUB_TYPE_EMAIL = "rfc822";
37
38    private static final String MEDIA_TYPE_TEXT = "text";
39
40    private static final String MEDIA_TYPE_MESSAGE = "message";
41
42    private static final String EMAIL_MESSAGE_MIME_TYPE = MEDIA_TYPE_MESSAGE + "/" + SUB_TYPE_EMAIL;
43
44    private static final String DEFAULT_SUB_TYPE = "plain";
45
46    private static final String DEFAULT_MEDIA_TYPE = MEDIA_TYPE_TEXT;
47
48    private static final String DEFAULT_MIME_TYPE = DEFAULT_MEDIA_TYPE + "/" + DEFAULT_SUB_TYPE;
49
50    private final DecodeMonitor monitor;
51   
52    private String mediaType = DEFAULT_MEDIA_TYPE;
53    private String subType = DEFAULT_SUB_TYPE;
54    private String mimeType = DEFAULT_MIME_TYPE;
55    private String boundary = null;
56    private String charset = US_ASCII;
57    private String transferEncoding = "7bit";
58    private Map<String, String> parameters = new HashMap<String, String>();
59    private boolean contentTypeSet;
60    private boolean contentTransferEncSet;
61    private long contentLength = -1;
62   
63    /**
64     * Creates a new root <code>BodyDescriptor</code> instance.
65     */
66    public DefaultBodyDescriptor() {
67        this(null, null);
68    }
69
70    /**
71     * Creates a new <code>BodyDescriptor</code> instance.
72     *
73     * @param parent the descriptor of the parent or <code>null</code> if this
74     *        is the root descriptor.
75     */
76    public DefaultBodyDescriptor(final BodyDescriptor parent, final DecodeMonitor monitor) {
77        if (parent != null && MimeUtil.isSameMimeType("multipart/digest", parent.getMimeType())) {
78            this.mimeType = EMAIL_MESSAGE_MIME_TYPE;
79            this.subType = SUB_TYPE_EMAIL;
80            this.mediaType = MEDIA_TYPE_MESSAGE;
81        } else {
82            this.mimeType = DEFAULT_MIME_TYPE;
83            this.subType = DEFAULT_SUB_TYPE;
84            this.mediaType = DEFAULT_MEDIA_TYPE;
85        }
86        this.monitor = monitor != null ? monitor : DecodeMonitor.SILENT;
87    }
88   
89    protected DecodeMonitor getDecodeMonitor() {
90        return monitor;
91    }
92   
93    public MutableBodyDescriptor newChild() {
94                return new DefaultBodyDescriptor(this, getDecodeMonitor());
95    }
96   
97    /**
98     * Should be called for each <code>Content-</code> header field of
99     * a MIME message or part.
100     *
101     * @param field the MIME field.
102     */
103    public void addField(RawField field) throws MimeException {
104        String name = field.getName();
105        String value = field.getBody();
106
107        name = name.trim().toLowerCase();
108       
109        if (name.equals("content-transfer-encoding") && !contentTransferEncSet) {
110            contentTransferEncSet = true;
111           
112            value = value.trim().toLowerCase();
113            if (value.length() > 0) {
114                transferEncoding = value;
115            }
116           
117        } else if (name.equals("content-length") && contentLength == -1) {
118            try {
119                contentLength = Long.parseLong(value.trim());
120            } catch (NumberFormatException e) {
121                if (monitor.warn("Invalid content length: " + value,
122                        "ignoring Content-Length header")) {
123                    throw new MimeException("Invalid Content-Length header: " + value);
124                }
125            }
126        } else if (name.equals("content-type") && !contentTypeSet) {
127            parseContentType(value);
128        }
129    }
130
131    private void parseContentType(String value) throws MimeException {
132        contentTypeSet = true;
133       
134        Map<String, String> params = DefaultBodyDescriptor.getHeaderParams(value, getDecodeMonitor());
135       
136        String main = params.get("");
137        String type = null;
138        String subtype = null;
139        if (main != null) {
140            main = main.toLowerCase().trim();
141            int index = main.indexOf('/');
142            boolean valid = false;
143            if (index != -1) {
144                type = main.substring(0, index).trim();
145                subtype = main.substring(index + 1).trim();
146                if (type.length() > 0 && subtype.length() > 0) {
147                    main = type + "/" + subtype;
148                    valid = true;
149                }
150            }
151           
152            if (!valid) {
153                main = null;
154                type = null;
155                subtype = null;
156            }
157        }
158        String b = params.get("boundary");
159       
160        if (main != null
161                && ((main.startsWith("multipart/") && b != null)
162                        || !main.startsWith("multipart/"))) {
163            mimeType = main;
164            this.subType = subtype;
165            this.mediaType = type;
166        }
167       
168        if (MimeUtil.isMultipart(mimeType)) {
169            boundary = b;
170        }
171       
172        String c = params.get("charset");
173        charset = null;
174        if (c != null) {
175            c = c.trim();
176            if (c.length() > 0) {
177                charset = c.toLowerCase();
178            }
179        }
180        if (charset == null && MEDIA_TYPE_TEXT.equals(mediaType)) {
181            charset = US_ASCII;
182        }
183       
184        /*
185         * Add all other parameters to parameters.
186         */
187        parameters.putAll(params);
188        parameters.remove("");
189        parameters.remove("boundary");
190        parameters.remove("charset");
191    }
192
193    /**
194     * Return the MimeType
195     *
196     * @return mimeType
197     */
198    public String getMimeType() {
199        return mimeType;
200    }
201   
202    /**
203     * Return the boundary
204     *
205     * @return boundary
206     */
207    public String getBoundary() {
208        return boundary;
209    }
210   
211    /**
212     * Return the charset
213     *
214     * @return charset
215     */
216    public String getCharset() {
217        return charset;
218    }
219   
220    /**
221     * Return all parameters for the BodyDescriptor
222     *
223     * @return parameters
224     */
225    public Map<String, String> getContentTypeParameters() {
226        return parameters;
227    }
228   
229    /**
230     * Return the TransferEncoding
231     *
232     * @return transferEncoding
233     */
234    public String getTransferEncoding() {
235        return transferEncoding;
236    }
237   
238    @Override
239    public String toString() {
240        return mimeType;
241    }
242
243    public long getContentLength() {
244        return contentLength;
245    }
246
247    public String getMediaType() {
248        return mediaType;
249    }
250
251    public String getSubType() {
252        return subType;
253    }
254
255    /**
256     * <p>Parses a complex field value into a map of key/value pairs. You may
257     * use this, for example, to parse a definition like
258     * <pre>
259     *   text/plain; charset=UTF-8; boundary=foobar
260     * </pre>
261     * The above example would return a map with the keys "", "charset",
262     * and "boundary", and the values "text/plain", "UTF-8", and "foobar".
263     * </p><p>
264     * Header value will be unfolded and excess white space trimmed.
265     * </p>
266     * @param pValue The field value to parse.
267     * @return The result map; use the key "" to retrieve the first value.
268     */
269    public static Map<String, String> getHeaderParams(
270            String pValue, DecodeMonitor monitor) throws MimeException {
271        pValue = pValue.trim();
272       
273        Map<String, String> result = new HashMap<String, String>();
274   
275        // split main value and parameters
276        String main;
277        String rest;
278        if (pValue.indexOf(";") == -1) {
279            main = pValue;
280            rest = null;
281        } else {
282            main = pValue.substring(0, pValue.indexOf(";"));
283            rest = pValue.substring(main.length() + 1);
284        }
285   
286        result.put("", main);
287        if (rest != null) {
288            char[] chars = rest.toCharArray();
289            StringBuilder paramName = new StringBuilder(64);
290            StringBuilder paramValue = new StringBuilder(64);
291   
292            final byte READY_FOR_NAME = 0;
293            final byte IN_NAME = 1;
294            final byte READY_FOR_VALUE = 2;
295            final byte IN_VALUE = 3;
296            final byte IN_QUOTED_VALUE = 4;
297            final byte VALUE_DONE = 5;
298            final byte ERROR = 99;
299   
300            byte state = READY_FOR_NAME;
301            boolean escaped = false;
302            for (char c : chars) {
303                switch (state) {
304                    case ERROR:
305                        if (c == ';')
306                            state = READY_FOR_NAME;
307                        break;
308   
309                    case READY_FOR_NAME:
310                        if (c == '=') {
311                            if (monitor.warn("Expected header param name, got '='", "ignoring")) {
312                                throw new MimeException("Expected header param name, got '='");
313                            }
314                            state = ERROR;
315                            break;
316                        }
317   
318                        paramName.setLength(0);
319                        paramValue.setLength(0);
320   
321                        state = IN_NAME;
322                        // fall-through
323   
324                    case IN_NAME:
325                        if (c == '=') {
326                            if (paramName.length() == 0)
327                                state = ERROR;
328                            else
329                                state = READY_FOR_VALUE;
330                            break;
331                        }
332   
333                        // not '='... just add to name
334                        paramName.append(c);
335                        break;
336   
337                    case READY_FOR_VALUE:
338                        boolean fallThrough = false;
339                        switch (c) {
340                            case ' ':
341                            case '\t':
342                                break;  // ignore spaces, especially before '"'
343   
344                            case '"':
345                                state = IN_QUOTED_VALUE;
346                                break;
347   
348                            default:
349                                state = IN_VALUE;
350                                fallThrough = true;
351                                break;
352                        }
353                        if (!fallThrough)
354                            break;
355   
356                        // fall-through
357   
358                    case IN_VALUE:
359                        fallThrough = false;
360                        switch (c) {
361                            case ';':
362                            case ' ':
363                            case '\t':
364                                result.put(
365                                   paramName.toString().trim().toLowerCase(),
366                                   paramValue.toString().trim());
367                                state = VALUE_DONE;
368                                fallThrough = true;
369                                break;
370                            default:
371                                paramValue.append(c);
372                                break;
373                        }
374                        if (!fallThrough)
375                            break;
376   
377                    case VALUE_DONE:
378                        switch (c) {
379                            case ';':
380                                state = READY_FOR_NAME;
381                                break;
382   
383                            case ' ':
384                            case '\t':
385                                break;
386   
387                            default:
388                                state = ERROR;
389                                break;
390                        }
391                        break;
392                       
393                    case IN_QUOTED_VALUE:
394                        switch (c) {
395                            case '"':
396                                if (!escaped) {
397                                    // don't trim quoted strings; the spaces could be intentional.
398                                    result.put(
399                                            paramName.toString().trim().toLowerCase(),
400                                            paramValue.toString());
401                                    state = VALUE_DONE;
402                                } else {
403                                    escaped = false;
404                                    paramValue.append(c);                                   
405                                }
406                                break;
407                               
408                            case '\\':
409                                if (escaped) {
410                                    paramValue.append('\\');
411                                }
412                                escaped = !escaped;
413                                break;
414   
415                            default:
416                                if (escaped) {
417                                    paramValue.append('\\');
418                                }
419                                escaped = false;
420                                paramValue.append(c);
421                                break;
422                        }
423                        break;
424   
425                }
426            }
427   
428            // done looping.  check if anything is left over.
429            if (state == IN_VALUE) {
430                result.put(
431                        paramName.toString().trim().toLowerCase(),
432                        paramValue.toString().trim());
433            }
434        }
435   
436        return result;
437    }
438}
Note: See TracBrowser for help on using the repository browser.