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.descriptor;
021    
022    import java.io.StringReader;
023    import java.util.Collections;
024    import java.util.List;
025    import java.util.Map;
026    
027    import org.apache.james.mime4j.MimeException;
028    import org.apache.james.mime4j.field.datetime.DateTime;
029    import org.apache.james.mime4j.field.datetime.parser.DateTimeParser;
030    import org.apache.james.mime4j.field.datetime.parser.ParseException;
031    import org.apache.james.mime4j.field.language.parser.ContentLanguageParser;
032    import org.apache.james.mime4j.field.mimeversion.parser.MimeVersionParser;
033    import org.apache.james.mime4j.field.structured.parser.StructuredFieldParser;
034    import org.apache.james.mime4j.parser.Field;
035    import org.apache.james.mime4j.util.MimeUtil;
036    
037    /**
038     * Parses and stores values for standard MIME header values.
039     * 
040     */
041    public class MaximalBodyDescriptor extends DefaultBodyDescriptor {
042    
043        private static final int DEFAULT_MINOR_VERSION = 0;
044        private static final int DEFAULT_MAJOR_VERSION = 1;
045        private boolean isMimeVersionSet;
046        private int mimeMinorVersion;
047        private int mimeMajorVersion;
048        private MimeException mimeVersionException;
049        private String contentId;
050        private boolean isContentIdSet;
051        private String contentDescription;
052        private boolean isContentDescriptionSet;
053        private String contentDispositionType;
054        private Map<String, String> contentDispositionParameters;
055        private DateTime contentDispositionModificationDate;
056        private MimeException contentDispositionModificationDateParseException;
057        private DateTime contentDispositionCreationDate;
058        private MimeException contentDispositionCreationDateParseException;
059        private DateTime contentDispositionReadDate;
060        private MimeException contentDispositionReadDateParseException;
061        private long contentDispositionSize;
062        private MimeException contentDispositionSizeParseException;
063        private boolean isContentDispositionSet;
064        private List<String> contentLanguage;
065        private MimeException contentLanguageParseException;
066        private boolean isContentLanguageSet;
067        private MimeException contentLocationParseException;
068        private String contentLocation;
069        private boolean isContentLocationSet;
070        private String contentMD5Raw;
071        private boolean isContentMD5Set;
072        
073        protected MaximalBodyDescriptor() {
074            this(null);
075        }
076    
077        public MaximalBodyDescriptor(BodyDescriptor parent) {
078            super(parent);
079            isMimeVersionSet = false;
080            mimeMajorVersion = DEFAULT_MAJOR_VERSION;
081            mimeMinorVersion = DEFAULT_MINOR_VERSION;
082            this.contentId = null;
083            this.isContentIdSet = false;
084            this.contentDescription = null;
085            this.isContentDescriptionSet = false;
086            this.contentDispositionType = null;
087            this.contentDispositionParameters = Collections.emptyMap();
088            this.contentDispositionModificationDate = null;
089            this.contentDispositionModificationDateParseException = null;
090            this.contentDispositionCreationDate = null;
091            this.contentDispositionCreationDateParseException = null;
092            this.contentDispositionReadDate = null;
093            this.contentDispositionReadDateParseException = null;
094            this.contentDispositionSize = -1;
095            this.contentDispositionSizeParseException = null;
096            this.isContentDispositionSet = false;
097            this.contentLanguage = null;
098            this.contentLanguageParseException = null;
099            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    }