View Javadoc

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  
20  package org.apache.james.mime4j.descriptor;
21  
22  import java.io.StringReader;
23  import java.util.Collections;
24  import java.util.List;
25  import java.util.Map;
26  
27  import org.apache.james.mime4j.MimeException;
28  import org.apache.james.mime4j.field.datetime.DateTime;
29  import org.apache.james.mime4j.field.datetime.parser.DateTimeParser;
30  import org.apache.james.mime4j.field.datetime.parser.ParseException;
31  import org.apache.james.mime4j.field.language.parser.ContentLanguageParser;
32  import org.apache.james.mime4j.field.mimeversion.parser.MimeVersionParser;
33  import org.apache.james.mime4j.field.structured.parser.StructuredFieldParser;
34  import org.apache.james.mime4j.parser.Field;
35  import org.apache.james.mime4j.util.MimeUtil;
36  
37  /**
38   * Parses and stores values for standard MIME header values.
39   * 
40   */
41  public class MaximalBodyDescriptor extends DefaultBodyDescriptor {
42  
43      private static final int DEFAULT_MINOR_VERSION = 0;
44      private static final int DEFAULT_MAJOR_VERSION = 1;
45      private boolean isMimeVersionSet;
46      private int mimeMinorVersion;
47      private int mimeMajorVersion;
48      private MimeException mimeVersionException;
49      private String contentId;
50      private boolean isContentIdSet;
51      private String contentDescription;
52      private boolean isContentDescriptionSet;
53      private String contentDispositionType;
54      private Map<String, String> contentDispositionParameters;
55      private DateTime contentDispositionModificationDate;
56      private MimeException contentDispositionModificationDateParseException;
57      private DateTime contentDispositionCreationDate;
58      private MimeException contentDispositionCreationDateParseException;
59      private DateTime contentDispositionReadDate;
60      private MimeException contentDispositionReadDateParseException;
61      private long contentDispositionSize;
62      private MimeException contentDispositionSizeParseException;
63      private boolean isContentDispositionSet;
64      private List<String> contentLanguage;
65      private MimeException contentLanguageParseException;
66      private boolean isContentLanguageSet;
67      private MimeException contentLocationParseException;
68      private String contentLocation;
69      private boolean isContentLocationSet;
70      private String contentMD5Raw;
71      private boolean isContentMD5Set;
72      
73      protected MaximalBodyDescriptor() {
74          this(null);
75      }
76  
77      public MaximalBodyDescriptor(BodyDescriptor parent) {
78          super(parent);
79          isMimeVersionSet = false;
80          mimeMajorVersion = DEFAULT_MAJOR_VERSION;
81          mimeMinorVersion = DEFAULT_MINOR_VERSION;
82          this.contentId = null;
83          this.isContentIdSet = false;
84          this.contentDescription = null;
85          this.isContentDescriptionSet = false;
86          this.contentDispositionType = null;
87          this.contentDispositionParameters = Collections.emptyMap();
88          this.contentDispositionModificationDate = null;
89          this.contentDispositionModificationDateParseException = null;
90          this.contentDispositionCreationDate = null;
91          this.contentDispositionCreationDateParseException = null;
92          this.contentDispositionReadDate = null;
93          this.contentDispositionReadDateParseException = null;
94          this.contentDispositionSize = -1;
95          this.contentDispositionSizeParseException = null;
96          this.isContentDispositionSet = false;
97          this.contentLanguage = null;
98          this.contentLanguageParseException = null;
99          this.isContentIdSet = false;
100         this.contentLocation = null;
101         this.contentLocationParseException = null;
102         this.isContentLocationSet = false;
103         this.contentMD5Raw = null;
104         this.isContentMD5Set = false;
105     }
106     
107     @Override
108     public void addField(Field field) {
109         String name = field.getName();
110         String value = field.getBody();
111         name = name.trim().toLowerCase();
112         if (MimeUtil.MIME_HEADER_MIME_VERSION.equals(name) && !isMimeVersionSet) {
113             parseMimeVersion(value);
114         } else if (MimeUtil.MIME_HEADER_CONTENT_ID.equals(name) && !isContentIdSet) {
115             parseContentId(value);
116         } else if (MimeUtil.MIME_HEADER_CONTENT_DESCRIPTION.equals(name) && !isContentDescriptionSet) {
117             parseContentDescription(value);
118         } else if (MimeUtil.MIME_HEADER_CONTENT_DISPOSITION.equals(name) && !isContentDispositionSet) {
119             parseContentDisposition(value);
120         } else if (MimeUtil.MIME_HEADER_LANGAUGE.equals(name) && !isContentLanguageSet) {
121             parseLanguage(value);
122         } else if (MimeUtil.MIME_HEADER_LOCATION.equals(name) && !isContentLocationSet) {
123             parseLocation(value);
124         } else if (MimeUtil.MIME_HEADER_MD5.equals(name) && !isContentMD5Set) {
125             parseMD5(value);
126         } else {
127             super.addField(field);
128         }
129     }
130     
131     private void parseMD5(String value) {
132         isContentMD5Set = true;
133         if (value != null) {
134             contentMD5Raw = value.trim();
135         }
136     }
137 
138     private void parseLocation(final String value) {
139         isContentLocationSet = true;
140         if (value != null) {
141             final StringReader stringReader = new StringReader(value);
142             final StructuredFieldParser parser = new StructuredFieldParser(stringReader);
143             parser.setFoldingPreserved(false);
144             try {
145                 contentLocation = parser.parse();
146             } catch (MimeException e) { 
147                 contentLocationParseException = e;
148             }
149         }
150     }
151     
152     private void parseLanguage(final String value) {
153         isContentLanguageSet = true;
154         if (value != null) {
155             try {
156                 final ContentLanguageParser parser = new ContentLanguageParser(new StringReader(value));
157                 contentLanguage = parser.parse();
158             } catch (MimeException e) {
159                 contentLanguageParseException = e;
160             }
161         }
162     }
163 
164     private void parseContentDisposition(final String value) {
165         isContentDispositionSet = true;
166         contentDispositionParameters = MimeUtil.getHeaderParams(value);
167         contentDispositionType = contentDispositionParameters.get("");
168         
169         final String contentDispositionModificationDate 
170             = contentDispositionParameters.get(MimeUtil.PARAM_MODIFICATION_DATE);
171         if (contentDispositionModificationDate != null) {
172             try {
173                 this.contentDispositionModificationDate = parseDate(contentDispositionModificationDate);
174             } catch (ParseException e) {
175                 this.contentDispositionModificationDateParseException = e;
176             } 
177         }
178         
179         final String contentDispositionCreationDate 
180             = contentDispositionParameters.get(MimeUtil.PARAM_CREATION_DATE);
181         if (contentDispositionCreationDate != null) {
182             try {
183                 this.contentDispositionCreationDate = parseDate(contentDispositionCreationDate);
184             } catch (ParseException e) {
185                 this.contentDispositionCreationDateParseException = e;
186             }         
187         }
188         
189         final String contentDispositionReadDate 
190             = contentDispositionParameters.get(MimeUtil.PARAM_READ_DATE);
191         if (contentDispositionReadDate != null) {
192             try {
193                 this.contentDispositionReadDate = parseDate(contentDispositionReadDate);
194             } catch (ParseException e) {
195                 this.contentDispositionReadDateParseException = e;
196             }         
197         }
198         
199         final String size = contentDispositionParameters.get(MimeUtil.PARAM_SIZE);
200         if (size != null) {
201             try {
202                 contentDispositionSize = Long.parseLong(size);
203             } catch (NumberFormatException e) {
204                 this.contentDispositionSizeParseException = (MimeException) new MimeException(e.getMessage(), e).fillInStackTrace();
205             }
206         }
207         contentDispositionParameters.remove("");
208     }
209 
210     private DateTime parseDate(final String date) throws ParseException {
211         final StringReader stringReader = new StringReader(date);
212         final DateTimeParser parser = new DateTimeParser(stringReader);
213         DateTime result = parser.date_time();
214         return result;
215     }
216     
217     private void parseContentDescription(String value) {
218         if (value == null) {
219             contentDescription = "";
220         } else {
221             contentDescription = value.trim();
222         }
223         isContentDescriptionSet = true;
224     }
225 
226     private void parseContentId(final String value) {
227         if (value == null) {
228             contentId = "";
229         } else {
230             contentId = value.trim();
231         }
232         isContentIdSet = true;
233     }
234 
235     private void parseMimeVersion(String value) {
236         final StringReader reader = new StringReader(value);
237         final MimeVersionParser parser = new MimeVersionParser(reader);
238         try {
239             parser.parse();
240             final int major = parser.getMajorVersion();
241             if (major != MimeVersionParser.INITIAL_VERSION_VALUE) {
242                 mimeMajorVersion = major;
243             }
244             final int minor = parser.getMinorVersion();
245             if (minor != MimeVersionParser.INITIAL_VERSION_VALUE) {
246                 mimeMinorVersion = minor;
247             }
248         } catch (MimeException e) {
249             this.mimeVersionException = e;
250         }
251         isMimeVersionSet = true;
252     }
253     
254     /**
255      * Gets the MIME major version
256      * as specified by the <code>MIME-Version</code>
257      * header.
258      * Defaults to one.
259      * @return positive integer
260      */
261     public int getMimeMajorVersion() {
262         return mimeMajorVersion;
263     }
264     
265     /**
266      * Gets the MIME minor version
267      * as specified by the <code>MIME-Version</code>
268      * header. 
269      * Defaults to zero.
270      * @return positive integer
271      */
272     public int getMimeMinorVersion() {
273         return mimeMinorVersion;
274     }
275     
276 
277     /**
278      * When the MIME version header exists but cannot be parsed
279      * this field will be contain the exception.
280      * @return <code>MimeException</code> if the mime header cannot
281      * be parsed, null otherwise
282      */
283     public MimeException getMimeVersionParseException() {
284         return mimeVersionException;
285     }
286     
287     /**
288      * Gets the value of the <a href='http://www.faqs.org/rfcs/rfc2045'>RFC</a> 
289      * <code>Content-Description</code> header.
290      * @return value of the <code>Content-Description</code> when present,
291      * null otherwise
292      */
293     public String getContentDescription() {
294         return contentDescription;
295     }
296     
297     /**
298      * Gets the value of the <a href='http://www.faqs.org/rfcs/rfc2045'>RFC</a> 
299      * <code>Content-ID</code> header.
300      * @return value of the <code>Content-ID</code> when present,
301      * null otherwise
302      */
303     public String getContentId() {
304         return contentId;
305     }
306     
307     /**
308      * Gets the disposition type of the <code>content-disposition</code> field.
309      * The value is case insensitive and will be converted to lower case.
310      * See <a href='http://www.faqs.org/rfcs/rfc2183.html'>RFC2183</a>.
311      * @return content disposition type, 
312      * or null when this has not been set
313      */
314     public String getContentDispositionType() {
315         return contentDispositionType;
316     }
317     
318     /**
319      * Gets the parameters of the <code>content-disposition</code> field.
320      * See <a href='http://www.faqs.org/rfcs/rfc2183.html'>RFC2183</a>.
321      * @return parameter value strings indexed by parameter name strings,
322      * not null
323      */
324     public Map<String, String> getContentDispositionParameters() {
325         return contentDispositionParameters;
326     }
327     
328     /**
329      * Gets the <code>filename</code> parameter value of the <code>content-disposition</code> field.
330      * See <a href='http://www.faqs.org/rfcs/rfc2183.html'>RFC2183</a>.
331      * @return filename parameter value, 
332      * or null when it is not present
333      */
334     public String getContentDispositionFilename() {
335         return contentDispositionParameters.get(MimeUtil.PARAM_FILENAME);
336     }
337     
338     /**
339      * Gets the <code>modification-date</code> parameter value of the <code>content-disposition</code> field.
340      * See <a href='http://www.faqs.org/rfcs/rfc2183.html'>RFC2183</a>.
341      * @return modification-date parameter value,
342      * or null when this is not present
343      */
344     public DateTime getContentDispositionModificationDate() {
345         return contentDispositionModificationDate;
346     }
347     
348     /**
349      * Gets any exception thrown during the parsing of {@link #getContentDispositionModificationDate()}
350      * @return <code>ParseException</code> when the modification-date parse fails,
351      * null otherwise
352      */
353     public MimeException getContentDispositionModificationDateParseException() {
354         return contentDispositionModificationDateParseException;
355     }
356     
357     /**
358      * Gets the <code>creation-date</code> parameter value of the <code>content-disposition</code> field.
359      * See <a href='http://www.faqs.org/rfcs/rfc2183.html'>RFC2183</a>.
360      * @return creation-date parameter value,
361      * or null when this is not present
362      */
363     public DateTime getContentDispositionCreationDate() {
364         return contentDispositionCreationDate;
365     }
366     
367     /**
368      * Gets any exception thrown during the parsing of {@link #getContentDispositionCreationDate()}
369      * @return <code>ParseException</code> when the creation-date parse fails,
370      * null otherwise
371      */
372     public MimeException getContentDispositionCreationDateParseException() {
373         return contentDispositionCreationDateParseException;
374     }
375     
376     /**
377      * Gets the <code>read-date</code> parameter value of the <code>content-disposition</code> field.
378      * See <a href='http://www.faqs.org/rfcs/rfc2183.html'>RFC2183</a>.
379      * @return read-date parameter value,
380      * or null when this is not present
381      */
382     public DateTime getContentDispositionReadDate() {
383         return contentDispositionReadDate;
384     }
385     
386     /**
387      * Gets any exception thrown during the parsing of {@link #getContentDispositionReadDate()}
388      * @return <code>ParseException</code> when the read-date parse fails,
389      * null otherwise
390      */
391     public MimeException getContentDispositionReadDateParseException() {
392         return contentDispositionReadDateParseException;
393     }
394     
395     /**
396      * Gets the <code>size</code> parameter value of the <code>content-disposition</code> field.
397      * See <a href='http://www.faqs.org/rfcs/rfc2183.html'>RFC2183</a>.
398      * @return size parameter value,
399      * or -1 if this size has not been set
400      */
401     public long getContentDispositionSize() {
402         return contentDispositionSize;
403     }
404     
405     /**
406      * Gets any exception thrown during the parsing of {@link #getContentDispositionSize()}
407      * @return <code>ParseException</code> when the read-date parse fails,
408      * null otherwise
409      */
410     public MimeException getContentDispositionSizeParseException() {
411         return contentDispositionSizeParseException;
412     }
413     
414     /**
415      * Get the <code>content-language</code> header values.
416      * Each applicable language tag will be returned in order.
417      * See <a href='http://tools.ietf.org/html/rfc4646'>RFC4646</a> 
418      * <cite>http://tools.ietf.org/html/rfc4646</cite>.
419      * @return list of language tag Strings,
420      * or null if no header exists
421      */
422     public List<String> getContentLanguage() {
423         return contentLanguage;
424     }
425 
426     /**
427      * Gets any exception thrown during the parsing of {@link #getContentLanguage()}
428      * @return <code>ParseException</code> when the content-language parse fails,
429      * null otherwise
430      */
431     public MimeException getContentLanguageParseException() {
432         return contentLanguageParseException;
433     }
434     
435 
436     /**
437      * Get the <code>content-location</code> header value.
438      * See <a href='http://tools.ietf.org/html/rfc2557'>RFC2557</a> 
439      * @return the URL content-location
440      * or null if no header exists
441      */
442     public String getContentLocation() {
443         return contentLocation;
444     }
445     
446     /**
447      * Gets any exception thrown during the parsing of {@link #getContentLocation()}
448      * @return <code>ParseException</code> when the content-language parse fails,
449      * null otherwise
450      */
451     public MimeException getContentLocationParseException() {
452         return contentLocationParseException;
453     }
454     
455     /**
456      * Gets the raw, Base64 encoded value of the
457      * <code>Content-MD5</code> field.
458      * See <a href='http://tools.ietf.org/html/rfc1864'>RFC1864</a>.
459      * @return raw encoded content-md5
460      * or null if no header exists
461      */
462     public String getContentMD5Raw() {
463         return contentMD5Raw;
464     }
465     
466     
467 }