blob: 15cae3b8a922cbf0f1661cead14dfe7866124a83 [file] [log] [blame]
/****************************************************************
* 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.parser;
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.dom.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.stream.BodyDescriptor;
import org.apache.james.mime4j.stream.DefaultBodyDescriptor;
import org.apache.james.mime4j.stream.MutableBodyDescriptor;
import org.apache.james.mime4j.stream.RawField;
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;
}
public MutableBodyDescriptor newChild() {
return new MaximalBodyDescriptor(this);
}
@Override
public void addField(RawField 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);
try {
// From RFC2017 3.1
/*
* Extraction of the URL string from the URL-parameter is even simpler:
* The enclosing quotes and any linear whitespace are removed and the
* remaining material is the URL string.
* Read more: http://www.faqs.org/rfcs/rfc2017.html#ixzz0aufO9nRL
*/
contentLocation = parser.parse().replaceAll("\\s", "");
} 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 = DefaultBodyDescriptor.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;
}
}