blob: 72722a1d94b2afaa13747faa8fcff08d93ae36a6 [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.field;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;
import java.util.regex.Pattern;
import org.apache.james.mime4j.codec.DecodeMonitor;
import org.apache.james.mime4j.codec.EncoderUtil;
import org.apache.james.mime4j.dom.FieldParser;
import org.apache.james.mime4j.dom.address.Address;
import org.apache.james.mime4j.dom.address.Mailbox;
import org.apache.james.mime4j.dom.field.AddressListField;
import org.apache.james.mime4j.dom.field.ContentDispositionField;
import org.apache.james.mime4j.dom.field.ContentTransferEncodingField;
import org.apache.james.mime4j.dom.field.ContentTypeField;
import org.apache.james.mime4j.dom.field.DateTimeField;
import org.apache.james.mime4j.dom.field.FieldName;
import org.apache.james.mime4j.dom.field.MailboxField;
import org.apache.james.mime4j.dom.field.MailboxListField;
import org.apache.james.mime4j.dom.field.MimeVersionField;
import org.apache.james.mime4j.dom.field.ParsedField;
import org.apache.james.mime4j.dom.field.UnstructuredField;
import org.apache.james.mime4j.field.address.AddressFormatter;
import org.apache.james.mime4j.stream.Field;
import org.apache.james.mime4j.stream.NameValuePair;
import org.apache.james.mime4j.stream.RawField;
import org.apache.james.mime4j.util.MimeUtil;
/**
* Factory for concrete {@link Field} instances.
*/
public class Fields {
private static final Pattern FIELD_NAME_PATTERN = Pattern
.compile("[\\x21-\\x39\\x3b-\\x7e]+");
private Fields() {
}
public static MimeVersionField version(String ver) {
return parse(MimeVersionFieldLenientImpl.PARSER, FieldName.MIME_VERSION, ver);
}
/**
* Creates a <i>Content-Type</i> field from the specified raw field value.
* The specified string gets folded into a multiple-line representation if
* necessary but is otherwise taken as is.
*
* @param contentType
* raw content type containing a MIME type and optional
* parameters.
* @return the newly created <i>Content-Type</i> field.
*/
public static ContentTypeField contentType(String contentType) {
return parse(ContentTypeFieldImpl.PARSER, FieldName.CONTENT_TYPE,
contentType);
}
/**
* Creates a <i>Content-Type</i> field from the specified MIME type and
* parameters.
*
* @param mimeType
* a MIME type (such as <code>&quot;text/plain&quot;</code> or
* <code>&quot;application/octet-stream&quot;</code>).
* @param parameters
* map containing content-type parameters such as
* <code>&quot;boundary&quot;</code>.
* @return the newly created <i>Content-Type</i> field.
*/
public static ContentTypeField contentType(String mimeType,
Map<String, String> parameters) {
if (!isValidMimeType(mimeType))
throw new IllegalArgumentException();
if (parameters == null || parameters.isEmpty()) {
return parse(ContentTypeFieldImpl.PARSER, FieldName.CONTENT_TYPE,
mimeType);
} else {
StringBuilder sb = new StringBuilder(mimeType);
for (Map.Entry<String, String> entry : parameters.entrySet()) {
sb.append("; ");
sb.append(EncoderUtil.encodeHeaderParameter(entry.getKey(),
entry.getValue()));
}
String contentType = sb.toString();
return contentType(contentType);
}
}
/**
* Creates a <i>Content-Type</i> field from the specified MIME type and
* parameters.
*
* @param mimeType
* a MIME type (such as <code>&quot;text/plain&quot;</code> or
* <code>&quot;application/octet-stream&quot;</code>).
* @param parameters
* list of content-type parameter name/value pairs.
* @return the newly created <i>Content-Type</i> field.
*/
public static ContentTypeField contentType(String mimeType,
Iterable<NameValuePair> parameters) {
if (!isValidMimeType(mimeType)) {
throw new IllegalArgumentException(mimeType + " is not a valid MIME type");
}
if (parameters == null) {
return parse(ContentTypeFieldImpl.PARSER, FieldName.CONTENT_TYPE,
mimeType);
} else {
StringBuilder sb = new StringBuilder(mimeType);
for (NameValuePair param : parameters) {
sb.append("; ");
String name = param.getName();
String value = param.getValue();
sb.append(EncoderUtil.encodeHeaderParameter(name, value != null ? value : ""));
}
String contentType = sb.toString();
return contentType(contentType);
}
}
/**
* Creates a <i>Content-Type</i> field from the specified MIME type and
* parameters.
*
* @param mimeType
* a MIME type (such as <code>&quot;text/plain&quot;</code> or
* <code>&quot;application/octet-stream&quot;</code>).
* @param parameters
* array of content-type parameter name/value pairs.
* @return the newly created <i>Content-Type</i> field.
*/
public static ContentTypeField contentType(String mimeType, NameValuePair... parameters) {
return contentType(mimeType, parameters != null || parameters.length == 0 ? Arrays.asList(parameters) : null);
}
/**
* Creates a <i>Content-Transfer-Encoding</i> field from the specified raw
* field value.
*
* @param contentTransferEncoding
* an encoding mechanism such as <code>&quot;7-bit&quot;</code>
* or <code>&quot;quoted-printable&quot;</code>.
* @return the newly created <i>Content-Transfer-Encoding</i> field.
*/
public static ContentTransferEncodingField contentTransferEncoding(
String contentTransferEncoding) {
return parse(ContentTransferEncodingFieldImpl.PARSER,
FieldName.CONTENT_TRANSFER_ENCODING, contentTransferEncoding);
}
/**
* Creates a <i>Content-Disposition</i> field from the specified raw field
* value. The specified string gets folded into a multiple-line
* representation if necessary but is otherwise taken as is.
*
* @param contentDisposition
* raw content disposition containing a disposition type and
* optional parameters.
* @return the newly created <i>Content-Disposition</i> field.
*/
public static ContentDispositionField contentDisposition(
String contentDisposition) {
return parse(ContentDispositionFieldImpl.PARSER,
FieldName.CONTENT_DISPOSITION, contentDisposition);
}
/**
* Creates a <i>Content-Disposition</i> field from the specified
* disposition type and parameters.
*
* @param dispositionType
* a disposition type (usually <code>&quot;inline&quot;</code>
* or <code>&quot;attachment&quot;</code>).
* @param parameters
* map containing disposition parameters such as
* <code>&quot;filename&quot;</code>.
* @return the newly created <i>Content-Disposition</i> field.
*/
public static ContentDispositionField contentDisposition(
String dispositionType, Map<String, String> parameters) {
if (!isValidDispositionType(dispositionType))
throw new IllegalArgumentException();
if (parameters == null || parameters.isEmpty()) {
return parse(ContentDispositionFieldImpl.PARSER,
FieldName.CONTENT_DISPOSITION, dispositionType);
} else {
StringBuilder sb = new StringBuilder(dispositionType);
for (Map.Entry<String, String> entry : parameters.entrySet()) {
sb.append("; ");
sb.append(EncoderUtil.encodeHeaderParameter(entry.getKey(),
entry.getValue()));
}
String contentDisposition = sb.toString();
return contentDisposition(contentDisposition);
}
}
/**
* Creates a <i>Content-Disposition</i> field from the specified
* disposition type and parameters.
*
* @param dispositionType
* a disposition type (usually <code>&quot;inline&quot;</code>
* or <code>&quot;attachment&quot;</code>).
* @param parameters
* list of disposition parameter name/value pairs.
* @return the newly created <i>Content-Disposition</i> field.
*/
public static ContentDispositionField contentDisposition(
String dispositionType, Iterable<NameValuePair> parameters) {
if (!isValidDispositionType(dispositionType))
throw new IllegalArgumentException();
if (parameters == null) {
return parse(ContentDispositionFieldImpl.PARSER,
FieldName.CONTENT_DISPOSITION, dispositionType);
} else {
StringBuilder sb = new StringBuilder(dispositionType);
for (NameValuePair param : parameters) {
sb.append("; ");
String name = param.getName();
String value = param.getValue();
sb.append(EncoderUtil.encodeHeaderParameter(name, value != null ? value : ""));
}
String contentDisposition = sb.toString();
return contentDisposition(contentDisposition);
}
}
/**
* Creates a <i>Content-Disposition</i> field from the specified
* disposition type and filename.
*
* @param dispositionType
* a disposition type (usually <code>&quot;inline&quot;</code>
* or <code>&quot;attachment&quot;</code>).
* @param filename
* filename parameter value or <code>null</code> if the
* parameter should not be included.
* @return the newly created <i>Content-Disposition</i> field.
*/
public static ContentDispositionField contentDisposition(
String dispositionType, String filename) {
return contentDisposition(dispositionType, filename, -1, null, null,
null);
}
/**
* Creates a <i>Content-Disposition</i> field from the specified values.
*
* @param dispositionType
* a disposition type (usually <code>&quot;inline&quot;</code>
* or <code>&quot;attachment&quot;</code>).
* @param filename
* filename parameter value or <code>null</code> if the
* parameter should not be included.
* @param size
* size parameter value or <code>-1</code> if the parameter
* should not be included.
* @return the newly created <i>Content-Disposition</i> field.
*/
public static ContentDispositionField contentDisposition(
String dispositionType, String filename, long size) {
return contentDisposition(dispositionType, filename, size, null, null,
null);
}
/**
* Creates a <i>Content-Disposition</i> field from the specified values.
*
* @param dispositionType
* a disposition type (usually <code>&quot;inline&quot;</code>
* or <code>&quot;attachment&quot;</code>).
* @param filename
* filename parameter value or <code>null</code> if the
* parameter should not be included.
* @param size
* size parameter value or <code>-1</code> if the parameter
* should not be included.
* @param creationDate
* creation-date parameter value or <code>null</code> if the
* parameter should not be included.
* @param modificationDate
* modification-date parameter value or <code>null</code> if
* the parameter should not be included.
* @param readDate
* read-date parameter value or <code>null</code> if the
* parameter should not be included.
* @return the newly created <i>Content-Disposition</i> field.
*/
public static ContentDispositionField contentDisposition(
String dispositionType, String filename, long size,
Date creationDate, Date modificationDate, Date readDate) {
Map<String, String> parameters = new HashMap<String, String>();
if (filename != null) {
parameters.put(ContentDispositionFieldImpl.PARAM_FILENAME, filename);
}
if (size >= 0) {
parameters.put(ContentDispositionFieldImpl.PARAM_SIZE, Long
.toString(size));
}
if (creationDate != null) {
parameters.put(ContentDispositionFieldImpl.PARAM_CREATION_DATE,
MimeUtil.formatDate(creationDate, null));
}
if (modificationDate != null) {
parameters.put(ContentDispositionFieldImpl.PARAM_MODIFICATION_DATE,
MimeUtil.formatDate(modificationDate, null));
}
if (readDate != null) {
parameters.put(ContentDispositionFieldImpl.PARAM_READ_DATE, MimeUtil
.formatDate(readDate, null));
}
return contentDisposition(dispositionType, parameters);
}
/**
* Creates a <i>Date</i> field from the specified <code>Date</code>
* value. The default time zone of the host is used to format the date.
*
* @param date
* date value for the header field.
* @return the newly created <i>Date</i> field.
*/
public static DateTimeField date(Date date) {
return date0(FieldName.DATE, date, null);
}
/**
* Creates a date field from the specified field name and <code>Date</code>
* value. The default time zone of the host is used to format the date.
*
* @param fieldName
* a field name such as <code>Date</code> or
* <code>Resent-Date</code>.
* @param date
* date value for the header field.
* @return the newly created date field.
*/
public static DateTimeField date(String fieldName, Date date) {
checkValidFieldName(fieldName);
return date0(fieldName, date, null);
}
/**
* Creates a date field from the specified field name, <code>Date</code>
* and <code>TimeZone</code> values.
*
* @param fieldName
* a field name such as <code>Date</code> or
* <code>Resent-Date</code>.
* @param date
* date value for the header field.
* @param zone
* the time zone to be used for formatting the date.
* @return the newly created date field.
*/
public static DateTimeField date(String fieldName, Date date, TimeZone zone) {
checkValidFieldName(fieldName);
return date0(fieldName, date, zone);
}
/**
* Creates a <i>Message-ID</i> field for the specified host name.
*
* @param hostname
* host name to be included in the message ID or
* <code>null</code> if no host name should be included.
* @return the newly created <i>Message-ID</i> field.
*/
public static UnstructuredField generateMessageId(String hostname) {
String fieldValue = MimeUtil.createUniqueMessageId(hostname);
return parse(UnstructuredFieldImpl.PARSER, FieldName.MESSAGE_ID, fieldValue);
}
/**
* Creates a <i>Message-ID</i> field with the given message ID.
*
* @param messageId
* message ID value.
*/
public static UnstructuredField messageId(String messageId) {
return parse(UnstructuredFieldImpl.PARSER, FieldName.MESSAGE_ID, messageId);
}
/**
* Creates a <i>Subject</i> field from the specified string value. The
* specified string may contain non-ASCII characters.
*
* @param subject
* the subject string.
* @return the newly created <i>Subject</i> field.
*/
public static UnstructuredField subject(String subject) {
int usedCharacters = FieldName.SUBJECT.length() + 2;
String fieldValue = EncoderUtil.encodeIfNecessary(subject,
EncoderUtil.Usage.TEXT_TOKEN, usedCharacters);
return parse(UnstructuredFieldImpl.PARSER, FieldName.SUBJECT, fieldValue);
}
/**
* Creates a <i>Sender</i> field for the specified mailbox address.
*
* @param mailbox
* address to be included in the field.
* @return the newly created <i>Sender</i> field.
*/
public static MailboxField sender(Mailbox mailbox) {
return mailbox0(FieldName.SENDER, mailbox);
}
/**
* Creates a <i>From</i> field for the specified mailbox address.
*
* @param mailbox
* address to be included in the field.
* @return the newly created <i>From</i> field.
*/
public static MailboxListField from(Mailbox mailbox) {
return mailboxList0(FieldName.FROM, Collections.singleton(mailbox));
}
/**
* Creates a <i>From</i> field for the specified mailbox addresses.
*
* @param mailboxes
* addresses to be included in the field.
* @return the newly created <i>From</i> field.
*/
public static MailboxListField from(Mailbox... mailboxes) {
return mailboxList0(FieldName.FROM, Arrays.asList(mailboxes));
}
/**
* Creates a <i>From</i> field for the specified mailbox addresses.
*
* @param mailboxes
* addresses to be included in the field.
* @return the newly created <i>From</i> field.
*/
public static MailboxListField from(Iterable<Mailbox> mailboxes) {
return mailboxList0(FieldName.FROM, mailboxes);
}
/**
* Creates a <i>To</i> field for the specified mailbox or group address.
*
* @param address
* mailbox or group address to be included in the field.
* @return the newly created <i>To</i> field.
*/
public static AddressListField to(Address address) {
return addressList0(FieldName.TO, Collections.singleton(address));
}
/**
* Creates a <i>To</i> field for the specified mailbox or group addresses.
*
* @param addresses
* mailbox or group addresses to be included in the field.
* @return the newly created <i>To</i> field.
*/
public static AddressListField to(Address... addresses) {
return addressList0(FieldName.TO, Arrays.asList(addresses));
}
/**
* Creates a <i>To</i> field for the specified mailbox or group addresses.
*
* @param addresses
* mailbox or group addresses to be included in the field.
* @return the newly created <i>To</i> field.
*/
public static AddressListField to(Iterable<Address> addresses) {
return addressList0(FieldName.TO, addresses);
}
/**
* Creates a <i>Cc</i> field for the specified mailbox or group address.
*
* @param address
* mailbox or group address to be included in the field.
* @return the newly created <i>Cc</i> field.
*/
public static AddressListField cc(Address address) {
return addressList0(FieldName.CC, Collections.singleton(address));
}
/**
* Creates a <i>Cc</i> field for the specified mailbox or group addresses.
*
* @param addresses
* mailbox or group addresses to be included in the field.
* @return the newly created <i>Cc</i> field.
*/
public static AddressListField cc(Address... addresses) {
return addressList0(FieldName.CC, Arrays.asList(addresses));
}
/**
* Creates a <i>Cc</i> field for the specified mailbox or group addresses.
*
* @param addresses
* mailbox or group addresses to be included in the field.
* @return the newly created <i>Cc</i> field.
*/
public static AddressListField cc(Iterable<Address> addresses) {
return addressList0(FieldName.CC, addresses);
}
/**
* Creates a <i>Bcc</i> field for the specified mailbox or group address.
*
* @param address
* mailbox or group address to be included in the field.
* @return the newly created <i>Bcc</i> field.
*/
public static AddressListField bcc(Address address) {
return addressList0(FieldName.BCC, Collections.singleton(address));
}
/**
* Creates a <i>Bcc</i> field for the specified mailbox or group addresses.
*
* @param addresses
* mailbox or group addresses to be included in the field.
* @return the newly created <i>Bcc</i> field.
*/
public static AddressListField bcc(Address... addresses) {
return addressList0(FieldName.BCC, Arrays.asList(addresses));
}
/**
* Creates a <i>Bcc</i> field for the specified mailbox or group addresses.
*
* @param addresses
* mailbox or group addresses to be included in the field.
* @return the newly created <i>Bcc</i> field.
*/
public static AddressListField bcc(Iterable<Address> addresses) {
return addressList0(FieldName.BCC, addresses);
}
/**
* Creates a <i>Reply-To</i> field for the specified mailbox or group
* address.
*
* @param address
* mailbox or group address to be included in the field.
* @return the newly created <i>Reply-To</i> field.
*/
public static AddressListField replyTo(Address address) {
return addressList0(FieldName.REPLY_TO, Collections.singleton(address));
}
/**
* Creates a <i>Reply-To</i> field for the specified mailbox or group
* addresses.
*
* @param addresses
* mailbox or group addresses to be included in the field.
* @return the newly created <i>Reply-To</i> field.
*/
public static AddressListField replyTo(Address... addresses) {
return addressList0(FieldName.REPLY_TO, Arrays.asList(addresses));
}
/**
* Creates a <i>Reply-To</i> field for the specified mailbox or group
* addresses.
*
* @param addresses
* mailbox or group addresses to be included in the field.
* @return the newly created <i>Reply-To</i> field.
*/
public static AddressListField replyTo(Iterable<Address> addresses) {
return addressList0(FieldName.REPLY_TO, addresses);
}
/**
* Creates a mailbox field from the specified field name and mailbox
* address. Valid field names are <code>Sender</code> and
* <code>Resent-Sender</code>.
*
* @param fieldName
* the name of the mailbox field (<code>Sender</code> or
* <code>Resent-Sender</code>).
* @param mailbox
* mailbox address for the field value.
* @return the newly created mailbox field.
*/
public static MailboxField mailbox(String fieldName, Mailbox mailbox) {
checkValidFieldName(fieldName);
return mailbox0(fieldName, mailbox);
}
/**
* Creates a mailbox-list field from the specified field name and mailbox
* addresses. Valid field names are <code>From</code> and
* <code>Resent-From</code>.
*
* @param fieldName
* the name of the mailbox field (<code>From</code> or
* <code>Resent-From</code>).
* @param mailboxes
* mailbox addresses for the field value.
* @return the newly created mailbox-list field.
*/
public static MailboxListField mailboxList(String fieldName,
Iterable<Mailbox> mailboxes) {
checkValidFieldName(fieldName);
return mailboxList0(fieldName, mailboxes);
}
/**
* Creates an address-list field from the specified field name and mailbox
* or group addresses. Valid field names are <code>To</code>,
* <code>Cc</code>, <code>Bcc</code>, <code>Reply-To</code>,
* <code>Resent-To</code>, <code>Resent-Cc</code> and
* <code>Resent-Bcc</code>.
*
* @param fieldName
* the name of the mailbox field (<code>To</code>,
* <code>Cc</code>, <code>Bcc</code>, <code>Reply-To</code>,
* <code>Resent-To</code>, <code>Resent-Cc</code> or
* <code>Resent-Bcc</code>).
* @param addresses
* mailbox or group addresses for the field value.
* @return the newly created address-list field.
*/
public static AddressListField addressList(String fieldName,
Iterable<? extends Address> addresses) {
checkValidFieldName(fieldName);
return addressList0(fieldName, addresses);
}
private static DateTimeField date0(String fieldName, Date date,
TimeZone zone) {
final String formattedDate = MimeUtil.formatDate(date, zone);
return parse(DateTimeFieldImpl.PARSER, fieldName, formattedDate);
}
private static MailboxField mailbox0(String fieldName, Mailbox mailbox) {
String fieldValue = encodeAddresses(Collections.singleton(mailbox));
return parse(MailboxFieldImpl.PARSER, fieldName, fieldValue);
}
private static MailboxListField mailboxList0(String fieldName,
Iterable<Mailbox> mailboxes) {
String fieldValue = encodeAddresses(mailboxes);
return parse(MailboxListFieldImpl.PARSER, fieldName, fieldValue);
}
private static AddressListField addressList0(String fieldName,
Iterable<? extends Address> addresses) {
String fieldValue = encodeAddresses(addresses);
return parse(AddressListFieldImpl.PARSER, fieldName, fieldValue);
}
private static void checkValidFieldName(String fieldName) {
if (!FIELD_NAME_PATTERN.matcher(fieldName).matches())
throw new IllegalArgumentException("Invalid field name");
}
private static boolean isValidMimeType(String mimeType) {
if (mimeType == null)
return false;
int idx = mimeType.indexOf('/');
if (idx == -1)
return false;
String type = mimeType.substring(0, idx);
String subType = mimeType.substring(idx + 1);
return EncoderUtil.isToken(type) && EncoderUtil.isToken(subType);
}
private static boolean isValidDispositionType(String dispositionType) {
return dispositionType != null && EncoderUtil.isToken(dispositionType);
}
private static <F extends ParsedField> F parse(FieldParser<F> parser,
String fieldName, String fieldBody) {
RawField rawField = new RawField(fieldName, fieldBody);
return parser.parse(rawField, DecodeMonitor.SILENT);
}
private static String encodeAddresses(Iterable<? extends Address> addresses) {
StringBuilder sb = new StringBuilder();
for (Address address : addresses) {
if (sb.length() > 0) {
sb.append(", ");
}
AddressFormatter.DEFAULT.encode(sb, address);
}
return sb.toString();
}
}