/**************************************************************** * Licensed to the Apache Software Foundation (ASF) under one * * or more contributor license agreements. See the NOTICE file * * distributed with this work for additional information * * regarding copyright ownership. The ASF licenses this file * * to you under the Apache License, Version 2.0 (the * * "License"); you may not use this file except in compliance * * with the License. You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, * * software distributed under the License is distributed on an * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * * KIND, either express or implied. See the License for the * * specific language governing permissions and limitations * * under the License. * ****************************************************************/ package org.apache.james.mime4j.descriptor; import java.io.StringReader; import java.util.Collections; import java.util.List; import java.util.Map; import org.apache.james.mime4j.MimeException; import org.apache.james.mime4j.field.datetime.DateTime; import org.apache.james.mime4j.field.datetime.parser.DateTimeParser; import org.apache.james.mime4j.field.datetime.parser.ParseException; import org.apache.james.mime4j.field.language.parser.ContentLanguageParser; import org.apache.james.mime4j.field.mimeversion.parser.MimeVersionParser; import org.apache.james.mime4j.field.structured.parser.StructuredFieldParser; import org.apache.james.mime4j.parser.Field; import org.apache.james.mime4j.util.MimeUtil; /** * Parses and stores values for standard MIME header values. * */ public class MaximalBodyDescriptor extends DefaultBodyDescriptor { private static final int DEFAULT_MINOR_VERSION = 0; private static final int DEFAULT_MAJOR_VERSION = 1; private boolean isMimeVersionSet; private int mimeMinorVersion; private int mimeMajorVersion; private MimeException mimeVersionException; private String contentId; private boolean isContentIdSet; private String contentDescription; private boolean isContentDescriptionSet; private String contentDispositionType; private Map<String, String> contentDispositionParameters; private DateTime contentDispositionModificationDate; private MimeException contentDispositionModificationDateParseException; private DateTime contentDispositionCreationDate; private MimeException contentDispositionCreationDateParseException; private DateTime contentDispositionReadDate; private MimeException contentDispositionReadDateParseException; private long contentDispositionSize; private MimeException contentDispositionSizeParseException; private boolean isContentDispositionSet; private List<String> contentLanguage; private MimeException contentLanguageParseException; private boolean isContentLanguageSet; private MimeException contentLocationParseException; private String contentLocation; private boolean isContentLocationSet; private String contentMD5Raw; private boolean isContentMD5Set; protected MaximalBodyDescriptor() { this(null); } public MaximalBodyDescriptor(BodyDescriptor parent) { super(parent); isMimeVersionSet = false; mimeMajorVersion = DEFAULT_MAJOR_VERSION; mimeMinorVersion = DEFAULT_MINOR_VERSION; this.contentId = null; this.isContentIdSet = false; this.contentDescription = null; this.isContentDescriptionSet = false; this.contentDispositionType = null; this.contentDispositionParameters = Collections.emptyMap(); this.contentDispositionModificationDate = null; this.contentDispositionModificationDateParseException = null; this.contentDispositionCreationDate = null; this.contentDispositionCreationDateParseException = null; this.contentDispositionReadDate = null; this.contentDispositionReadDateParseException = null; this.contentDispositionSize = -1; this.contentDispositionSizeParseException = null; this.isContentDispositionSet = false; this.contentLanguage = null; this.contentLanguageParseException = null; this.isContentIdSet = false; this.contentLocation = null; this.contentLocationParseException = null; this.isContentLocationSet = false; this.contentMD5Raw = null; this.isContentMD5Set = false; } @Override public void addField(Field field) { String name = field.getName(); String value = field.getBody(); name = name.trim().toLowerCase(); if (MimeUtil.MIME_HEADER_MIME_VERSION.equals(name) && !isMimeVersionSet) { parseMimeVersion(value); } else if (MimeUtil.MIME_HEADER_CONTENT_ID.equals(name) && !isContentIdSet) { parseContentId(value); } else if (MimeUtil.MIME_HEADER_CONTENT_DESCRIPTION.equals(name) && !isContentDescriptionSet) { parseContentDescription(value); } else if (MimeUtil.MIME_HEADER_CONTENT_DISPOSITION.equals(name) && !isContentDispositionSet) { parseContentDisposition(value); } else if (MimeUtil.MIME_HEADER_LANGAUGE.equals(name) && !isContentLanguageSet) { parseLanguage(value); } else if (MimeUtil.MIME_HEADER_LOCATION.equals(name) && !isContentLocationSet) { parseLocation(value); } else if (MimeUtil.MIME_HEADER_MD5.equals(name) && !isContentMD5Set) { parseMD5(value); } else { super.addField(field); } } private void parseMD5(String value) { isContentMD5Set = true; if (value != null) { contentMD5Raw = value.trim(); } } private void parseLocation(final String value) { isContentLocationSet = true; if (value != null) { final StringReader stringReader = new StringReader(value); final StructuredFieldParser parser = new StructuredFieldParser(stringReader); parser.setFoldingPreserved(false); try { contentLocation = parser.parse(); } catch (MimeException e) { contentLocationParseException = e; } } } private void parseLanguage(final String value) { isContentLanguageSet = true; if (value != null) { try { final ContentLanguageParser parser = new ContentLanguageParser(new StringReader(value)); contentLanguage = parser.parse(); } catch (MimeException e) { contentLanguageParseException = e; } } } private void parseContentDisposition(final String value) { isContentDispositionSet = true; contentDispositionParameters = MimeUtil.getHeaderParams(value); contentDispositionType = contentDispositionParameters.get(""); final String contentDispositionModificationDate = contentDispositionParameters.get(MimeUtil.PARAM_MODIFICATION_DATE); if (contentDispositionModificationDate != null) { try { this.contentDispositionModificationDate = parseDate(contentDispositionModificationDate); } catch (ParseException e) { this.contentDispositionModificationDateParseException = e; } } final String contentDispositionCreationDate = contentDispositionParameters.get(MimeUtil.PARAM_CREATION_DATE); if (contentDispositionCreationDate != null) { try { this.contentDispositionCreationDate = parseDate(contentDispositionCreationDate); } catch (ParseException e) { this.contentDispositionCreationDateParseException = e; } } final String contentDispositionReadDate = contentDispositionParameters.get(MimeUtil.PARAM_READ_DATE); if (contentDispositionReadDate != null) { try { this.contentDispositionReadDate = parseDate(contentDispositionReadDate); } catch (ParseException e) { this.contentDispositionReadDateParseException = e; } } final String size = contentDispositionParameters.get(MimeUtil.PARAM_SIZE); if (size != null) { try { contentDispositionSize = Long.parseLong(size); } catch (NumberFormatException e) { this.contentDispositionSizeParseException = (MimeException) new MimeException(e.getMessage(), e).fillInStackTrace(); } } contentDispositionParameters.remove(""); } private DateTime parseDate(final String date) throws ParseException { final StringReader stringReader = new StringReader(date); final DateTimeParser parser = new DateTimeParser(stringReader); DateTime result = parser.date_time(); return result; } private void parseContentDescription(String value) { if (value == null) { contentDescription = ""; } else { contentDescription = value.trim(); } isContentDescriptionSet = true; } private void parseContentId(final String value) { if (value == null) { contentId = ""; } else { contentId = value.trim(); } isContentIdSet = true; } private void parseMimeVersion(String value) { final StringReader reader = new StringReader(value); final MimeVersionParser parser = new MimeVersionParser(reader); try { parser.parse(); final int major = parser.getMajorVersion(); if (major != MimeVersionParser.INITIAL_VERSION_VALUE) { mimeMajorVersion = major; } final int minor = parser.getMinorVersion(); if (minor != MimeVersionParser.INITIAL_VERSION_VALUE) { mimeMinorVersion = minor; } } catch (MimeException e) { this.mimeVersionException = e; } isMimeVersionSet = true; } /** * Gets the MIME major version * as specified by the <code>MIME-Version</code> * header. * Defaults to one. * @return positive integer */ public int getMimeMajorVersion() { return mimeMajorVersion; } /** * Gets the MIME minor version * as specified by the <code>MIME-Version</code> * header. * Defaults to zero. * @return positive integer */ public int getMimeMinorVersion() { return mimeMinorVersion; } /** * When the MIME version header exists but cannot be parsed * this field will be contain the exception. * @return <code>MimeException</code> if the mime header cannot * be parsed, null otherwise */ public MimeException getMimeVersionParseException() { return mimeVersionException; } /** * Gets the value of the <a href='http://www.faqs.org/rfcs/rfc2045'>RFC</a> * <code>Content-Description</code> header. * @return value of the <code>Content-Description</code> when present, * null otherwise */ public String getContentDescription() { return contentDescription; } /** * Gets the value of the <a href='http://www.faqs.org/rfcs/rfc2045'>RFC</a> * <code>Content-ID</code> header. * @return value of the <code>Content-ID</code> when present, * null otherwise */ public String getContentId() { return contentId; } /** * Gets the disposition type of the <code>content-disposition</code> field. * The value is case insensitive and will be converted to lower case. * See <a href='http://www.faqs.org/rfcs/rfc2183.html'>RFC2183</a>. * @return content disposition type, * or null when this has not been set */ public String getContentDispositionType() { return contentDispositionType; } /** * Gets the parameters of the <code>content-disposition</code> field. * See <a href='http://www.faqs.org/rfcs/rfc2183.html'>RFC2183</a>. * @return parameter value strings indexed by parameter name strings, * not null */ public Map<String, String> getContentDispositionParameters() { return contentDispositionParameters; } /** * Gets the <code>filename</code> parameter value of the <code>content-disposition</code> field. * See <a href='http://www.faqs.org/rfcs/rfc2183.html'>RFC2183</a>. * @return filename parameter value, * or null when it is not present */ public String getContentDispositionFilename() { return contentDispositionParameters.get(MimeUtil.PARAM_FILENAME); } /** * Gets the <code>modification-date</code> parameter value of the <code>content-disposition</code> field. * See <a href='http://www.faqs.org/rfcs/rfc2183.html'>RFC2183</a>. * @return modification-date parameter value, * or null when this is not present */ public DateTime getContentDispositionModificationDate() { return contentDispositionModificationDate; } /** * Gets any exception thrown during the parsing of {@link #getContentDispositionModificationDate()} * @return <code>ParseException</code> when the modification-date parse fails, * null otherwise */ public MimeException getContentDispositionModificationDateParseException() { return contentDispositionModificationDateParseException; } /** * Gets the <code>creation-date</code> parameter value of the <code>content-disposition</code> field. * See <a href='http://www.faqs.org/rfcs/rfc2183.html'>RFC2183</a>. * @return creation-date parameter value, * or null when this is not present */ public DateTime getContentDispositionCreationDate() { return contentDispositionCreationDate; } /** * Gets any exception thrown during the parsing of {@link #getContentDispositionCreationDate()} * @return <code>ParseException</code> when the creation-date parse fails, * null otherwise */ public MimeException getContentDispositionCreationDateParseException() { return contentDispositionCreationDateParseException; } /** * Gets the <code>read-date</code> parameter value of the <code>content-disposition</code> field. * See <a href='http://www.faqs.org/rfcs/rfc2183.html'>RFC2183</a>. * @return read-date parameter value, * or null when this is not present */ public DateTime getContentDispositionReadDate() { return contentDispositionReadDate; } /** * Gets any exception thrown during the parsing of {@link #getContentDispositionReadDate()} * @return <code>ParseException</code> when the read-date parse fails, * null otherwise */ public MimeException getContentDispositionReadDateParseException() { return contentDispositionReadDateParseException; } /** * Gets the <code>size</code> parameter value of the <code>content-disposition</code> field. * See <a href='http://www.faqs.org/rfcs/rfc2183.html'>RFC2183</a>. * @return size parameter value, * or -1 if this size has not been set */ public long getContentDispositionSize() { return contentDispositionSize; } /** * Gets any exception thrown during the parsing of {@link #getContentDispositionSize()} * @return <code>ParseException</code> when the read-date parse fails, * null otherwise */ public MimeException getContentDispositionSizeParseException() { return contentDispositionSizeParseException; } /** * Get the <code>content-language</code> header values. * Each applicable language tag will be returned in order. * See <a href='http://tools.ietf.org/html/rfc4646'>RFC4646</a> * <cite>http://tools.ietf.org/html/rfc4646</cite>. * @return list of language tag Strings, * or null if no header exists */ public List<String> getContentLanguage() { return contentLanguage; } /** * Gets any exception thrown during the parsing of {@link #getContentLanguage()} * @return <code>ParseException</code> when the content-language parse fails, * null otherwise */ public MimeException getContentLanguageParseException() { return contentLanguageParseException; } /** * Get the <code>content-location</code> header value. * See <a href='http://tools.ietf.org/html/rfc2557'>RFC2557</a> * @return the URL content-location * or null if no header exists */ public String getContentLocation() { return contentLocation; } /** * Gets any exception thrown during the parsing of {@link #getContentLocation()} * @return <code>ParseException</code> when the content-language parse fails, * null otherwise */ public MimeException getContentLocationParseException() { return contentLocationParseException; } /** * Gets the raw, Base64 encoded value of the * <code>Content-MD5</code> field. * See <a href='http://tools.ietf.org/html/rfc1864'>RFC1864</a>. * @return raw encoded content-md5 * or null if no header exists */ public String getContentMD5Raw() { return contentMD5Raw; } }