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 *
10
* http://www.apache.org/licenses/LICENSE-2.0 *
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
****************************************************************/
20
package org.apache.james.mime4j.field;
22
import java.util.Arrays;
23
import java.util.Collections;
24
import java.util.Date;
25
import java.util.HashMap;
27
import java.util.TimeZone;
28
import java.util.regex.Pattern;
30
import org.apache.james.mime4j.codec.EncoderUtil;
31
import org.apache.james.mime4j.field.address.Address;
32
import org.apache.james.mime4j.field.address.Mailbox;
33
import org.apache.james.mime4j.parser.Field;
34
import org.apache.james.mime4j.util.ByteSequence;
35
import org.apache.james.mime4j.util.ContentUtil;
36
import org.apache.james.mime4j.util.MimeUtil;
39
* Factory for concrete {@link Field} instances.
43
private static final Pattern FIELD_NAME_PATTERN = Pattern
44
.compile("[\\x21-\\x39\\x3b-\\x7e]+");
50
* Creates a <i>Content-Type</i> field from the specified raw field value.
51
* The specified string gets folded into a multiple-line representation if
52
* necessary but is otherwise taken as is.
55
* raw content type containing a MIME type and optional
57
* @return the newly created <i>Content-Type</i> field.
59
public static ContentTypeField contentType(String contentType) {
60
return parse(ContentTypeField.PARSER, FieldName.CONTENT_TYPE,
65
* Creates a <i>Content-Type</i> field from the specified MIME type and
69
* a MIME type (such as <code>"text/plain"</code> or
70
* <code>"application/octet-stream"</code>).
72
* map containing content-type parameters such as
73
* <code>"boundary"</code>.
74
* @return the newly created <i>Content-Type</i> field.
76
public static ContentTypeField contentType(String mimeType,
77
Map<String, String> parameters) {
78
if (!isValidMimeType(mimeType))
79
throw new IllegalArgumentException();
81
if (parameters == null || parameters.isEmpty()) {
82
return parse(ContentTypeField.PARSER, FieldName.CONTENT_TYPE,
85
StringBuilder sb = new StringBuilder(mimeType);
86
for (Map.Entry<String, String> entry : parameters.entrySet()) {
88
sb.append(EncoderUtil.encodeHeaderParameter(entry.getKey(),
91
String contentType = sb.toString();
92
return contentType(contentType);
97
* Creates a <i>Content-Transfer-Encoding</i> field from the specified raw
100
* @param contentTransferEncoding
101
* an encoding mechanism such as <code>"7-bit"</code>
102
* or <code>"quoted-printable"</code>.
103
* @return the newly created <i>Content-Transfer-Encoding</i> field.
105
public static ContentTransferEncodingField contentTransferEncoding(
106
String contentTransferEncoding) {
107
return parse(ContentTransferEncodingField.PARSER,
108
FieldName.CONTENT_TRANSFER_ENCODING, contentTransferEncoding);
112
* Creates a <i>Content-Disposition</i> field from the specified raw field
113
* value. The specified string gets folded into a multiple-line
114
* representation if necessary but is otherwise taken as is.
116
* @param contentDisposition
117
* raw content disposition containing a disposition type and
118
* optional parameters.
119
* @return the newly created <i>Content-Disposition</i> field.
121
public static ContentDispositionField contentDisposition(
122
String contentDisposition) {
123
return parse(ContentDispositionField.PARSER,
124
FieldName.CONTENT_DISPOSITION, contentDisposition);
128
* Creates a <i>Content-Disposition</i> field from the specified
129
* disposition type and parameters.
131
* @param dispositionType
132
* a disposition type (usually <code>"inline"</code>
133
* or <code>"attachment"</code>).
135
* map containing disposition parameters such as
136
* <code>"filename"</code>.
137
* @return the newly created <i>Content-Disposition</i> field.
139
public static ContentDispositionField contentDisposition(
140
String dispositionType, Map<String, String> parameters) {
141
if (!isValidDispositionType(dispositionType))
142
throw new IllegalArgumentException();
144
if (parameters == null || parameters.isEmpty()) {
145
return parse(ContentDispositionField.PARSER,
146
FieldName.CONTENT_DISPOSITION, dispositionType);
148
StringBuilder sb = new StringBuilder(dispositionType);
149
for (Map.Entry<String, String> entry : parameters.entrySet()) {
151
sb.append(EncoderUtil.encodeHeaderParameter(entry.getKey(),
154
String contentDisposition = sb.toString();
155
return contentDisposition(contentDisposition);
160
* Creates a <i>Content-Disposition</i> field from the specified
161
* disposition type and filename.
163
* @param dispositionType
164
* a disposition type (usually <code>"inline"</code>
165
* or <code>"attachment"</code>).
167
* filename parameter value or <code>null</code> if the
168
* parameter should not be included.
169
* @return the newly created <i>Content-Disposition</i> field.
171
public static ContentDispositionField contentDisposition(
172
String dispositionType, String filename) {
173
return contentDisposition(dispositionType, filename, -1, null, null,
178
* Creates a <i>Content-Disposition</i> field from the specified values.
180
* @param dispositionType
181
* a disposition type (usually <code>"inline"</code>
182
* or <code>"attachment"</code>).
184
* filename parameter value or <code>null</code> if the
185
* parameter should not be included.
187
* size parameter value or <code>-1</code> if the parameter
188
* should not be included.
189
* @return the newly created <i>Content-Disposition</i> field.
191
public static ContentDispositionField contentDisposition(
192
String dispositionType, String filename, long size) {
193
return contentDisposition(dispositionType, filename, size, null, null,
198
* Creates a <i>Content-Disposition</i> field from the specified values.
200
* @param dispositionType
201
* a disposition type (usually <code>"inline"</code>
202
* or <code>"attachment"</code>).
204
* filename parameter value or <code>null</code> if the
205
* parameter should not be included.
207
* size parameter value or <code>-1</code> if the parameter
208
* should not be included.
209
* @param creationDate
210
* creation-date parameter value or <code>null</code> if the
211
* parameter should not be included.
212
* @param modificationDate
213
* modification-date parameter value or <code>null</code> if
214
* the parameter should not be included.
216
* read-date parameter value or <code>null</code> if the
217
* parameter should not be included.
218
* @return the newly created <i>Content-Disposition</i> field.
220
public static ContentDispositionField contentDisposition(
221
String dispositionType, String filename, long size,
222
Date creationDate, Date modificationDate, Date readDate) {
223
Map<String, String> parameters = new HashMap<String, String>();
224
if (filename != null) {
225
parameters.put(ContentDispositionField.PARAM_FILENAME, filename);
228
parameters.put(ContentDispositionField.PARAM_SIZE, Long
231
if (creationDate != null) {
232
parameters.put(ContentDispositionField.PARAM_CREATION_DATE,
233
MimeUtil.formatDate(creationDate, null));
235
if (modificationDate != null) {
236
parameters.put(ContentDispositionField.PARAM_MODIFICATION_DATE,
237
MimeUtil.formatDate(modificationDate, null));
239
if (readDate != null) {
240
parameters.put(ContentDispositionField.PARAM_READ_DATE, MimeUtil
241
.formatDate(readDate, null));
243
return contentDisposition(dispositionType, parameters);
247
* Creates a <i>Date</i> field from the specified <code>Date</code>
248
* value. The default time zone of the host is used to format the date.
251
* date value for the header field.
252
* @return the newly created <i>Date</i> field.
254
public static DateTimeField date(Date date) {
255
return date0(FieldName.DATE, date, null);
259
* Creates a date field from the specified field name and <code>Date</code>
260
* value. The default time zone of the host is used to format the date.
263
* a field name such as <code>Date</code> or
264
* <code>Resent-Date</code>.
266
* date value for the header field.
267
* @return the newly created date field.
269
public static DateTimeField date(String fieldName, Date date) {
270
checkValidFieldName(fieldName);
271
return date0(fieldName, date, null);
275
* Creates a date field from the specified field name, <code>Date</code>
276
* and <code>TimeZone</code> values.
279
* a field name such as <code>Date</code> or
280
* <code>Resent-Date</code>.
282
* date value for the header field.
284
* the time zone to be used for formatting the date.
285
* @return the newly created date field.
287
public static DateTimeField date(String fieldName, Date date, TimeZone zone) {
288
checkValidFieldName(fieldName);
289
return date0(fieldName, date, zone);
293
* Creates a <i>Message-ID</i> field for the specified host name.
296
* host name to be included in the message ID or
297
* <code>null</code> if no host name should be included.
298
* @return the newly created <i>Message-ID</i> field.
300
public static Field messageId(String hostname) {
301
String fieldValue = MimeUtil.createUniqueMessageId(hostname);
302
return parse(UnstructuredField.PARSER, FieldName.MESSAGE_ID, fieldValue);
306
* Creates a <i>Subject</i> field from the specified string value. The
307
* specified string may contain non-ASCII characters.
310
* the subject string.
311
* @return the newly created <i>Subject</i> field.
313
public static UnstructuredField subject(String subject) {
314
int usedCharacters = FieldName.SUBJECT.length() + 2;
315
String fieldValue = EncoderUtil.encodeIfNecessary(subject,
316
EncoderUtil.Usage.TEXT_TOKEN, usedCharacters);
318
return parse(UnstructuredField.PARSER, FieldName.SUBJECT, fieldValue);
322
* Creates a <i>Sender</i> field for the specified mailbox address.
325
* address to be included in the field.
326
* @return the newly created <i>Sender</i> field.
328
public static MailboxField sender(Mailbox mailbox) {
329
return mailbox0(FieldName.SENDER, mailbox);
333
* Creates a <i>From</i> field for the specified mailbox address.
336
* address to be included in the field.
337
* @return the newly created <i>From</i> field.
339
public static MailboxListField from(Mailbox mailbox) {
340
return mailboxList0(FieldName.FROM, Collections.singleton(mailbox));
344
* Creates a <i>From</i> field for the specified mailbox addresses.
347
* addresses to be included in the field.
348
* @return the newly created <i>From</i> field.
350
public static MailboxListField from(Mailbox... mailboxes) {
351
return mailboxList0(FieldName.FROM, Arrays.asList(mailboxes));
355
* Creates a <i>From</i> field for the specified mailbox addresses.
358
* addresses to be included in the field.
359
* @return the newly created <i>From</i> field.
361
public static MailboxListField from(Iterable<Mailbox> mailboxes) {
362
return mailboxList0(FieldName.FROM, mailboxes);
366
* Creates a <i>To</i> field for the specified mailbox or group address.
369
* mailbox or group address to be included in the field.
370
* @return the newly created <i>To</i> field.
372
public static AddressListField to(Address address) {
373
return addressList0(FieldName.TO, Collections.singleton(address));
377
* Creates a <i>To</i> field for the specified mailbox or group addresses.
380
* mailbox or group addresses to be included in the field.
381
* @return the newly created <i>To</i> field.
383
public static AddressListField to(Address... addresses) {
384
return addressList0(FieldName.TO, Arrays.asList(addresses));
388
* Creates a <i>To</i> field for the specified mailbox or group addresses.
391
* mailbox or group addresses to be included in the field.
392
* @return the newly created <i>To</i> field.
394
public static AddressListField to(Iterable<Address> addresses) {
395
return addressList0(FieldName.TO, addresses);
399
* Creates a <i>Cc</i> field for the specified mailbox or group address.
402
* mailbox or group address to be included in the field.
403
* @return the newly created <i>Cc</i> field.
405
public static AddressListField cc(Address address) {
406
return addressList0(FieldName.CC, Collections.singleton(address));
410
* Creates a <i>Cc</i> field for the specified mailbox or group addresses.
413
* mailbox or group addresses to be included in the field.
414
* @return the newly created <i>Cc</i> field.
416
public static AddressListField cc(Address... addresses) {
417
return addressList0(FieldName.CC, Arrays.asList(addresses));
421
* Creates a <i>Cc</i> field for the specified mailbox or group addresses.
424
* mailbox or group addresses to be included in the field.
425
* @return the newly created <i>Cc</i> field.
427
public static AddressListField cc(Iterable<Address> addresses) {
428
return addressList0(FieldName.CC, addresses);
432
* Creates a <i>Bcc</i> field for the specified mailbox or group address.
435
* mailbox or group address to be included in the field.
436
* @return the newly created <i>Bcc</i> field.
438
public static AddressListField bcc(Address address) {
439
return addressList0(FieldName.BCC, Collections.singleton(address));
443
* Creates a <i>Bcc</i> field for the specified mailbox or group addresses.
446
* mailbox or group addresses to be included in the field.
447
* @return the newly created <i>Bcc</i> field.
449
public static AddressListField bcc(Address... addresses) {
450
return addressList0(FieldName.BCC, Arrays.asList(addresses));
454
* Creates a <i>Bcc</i> field for the specified mailbox or group addresses.
457
* mailbox or group addresses to be included in the field.
458
* @return the newly created <i>Bcc</i> field.
460
public static AddressListField bcc(Iterable<Address> addresses) {
461
return addressList0(FieldName.BCC, addresses);
465
* Creates a <i>Reply-To</i> field for the specified mailbox or group
469
* mailbox or group address to be included in the field.
470
* @return the newly created <i>Reply-To</i> field.
472
public static AddressListField replyTo(Address address) {
473
return addressList0(FieldName.REPLY_TO, Collections.singleton(address));
477
* Creates a <i>Reply-To</i> field for the specified mailbox or group
481
* mailbox or group addresses to be included in the field.
482
* @return the newly created <i>Reply-To</i> field.
484
public static AddressListField replyTo(Address... addresses) {
485
return addressList0(FieldName.REPLY_TO, Arrays.asList(addresses));
489
* Creates a <i>Reply-To</i> field for the specified mailbox or group
493
* mailbox or group addresses to be included in the field.
494
* @return the newly created <i>Reply-To</i> field.
496
public static AddressListField replyTo(Iterable<Address> addresses) {
497
return addressList0(FieldName.REPLY_TO, addresses);
501
* Creates a mailbox field from the specified field name and mailbox
502
* address. Valid field names are <code>Sender</code> and
503
* <code>Resent-Sender</code>.
506
* the name of the mailbox field (<code>Sender</code> or
507
* <code>Resent-Sender</code>).
509
* mailbox address for the field value.
510
* @return the newly created mailbox field.
512
public static MailboxField mailbox(String fieldName, Mailbox mailbox) {
513
checkValidFieldName(fieldName);
514
return mailbox0(fieldName, mailbox);
518
* Creates a mailbox-list field from the specified field name and mailbox
519
* addresses. Valid field names are <code>From</code> and
520
* <code>Resent-From</code>.
523
* the name of the mailbox field (<code>From</code> or
524
* <code>Resent-From</code>).
526
* mailbox addresses for the field value.
527
* @return the newly created mailbox-list field.
529
public static MailboxListField mailboxList(String fieldName,
530
Iterable<Mailbox> mailboxes) {
531
checkValidFieldName(fieldName);
532
return mailboxList0(fieldName, mailboxes);
536
* Creates an address-list field from the specified field name and mailbox
537
* or group addresses. Valid field names are <code>To</code>,
538
* <code>Cc</code>, <code>Bcc</code>, <code>Reply-To</code>,
539
* <code>Resent-To</code>, <code>Resent-Cc</code> and
540
* <code>Resent-Bcc</code>.
543
* the name of the mailbox field (<code>To</code>,
544
* <code>Cc</code>, <code>Bcc</code>, <code>Reply-To</code>,
545
* <code>Resent-To</code>, <code>Resent-Cc</code> or
546
* <code>Resent-Bcc</code>).
548
* mailbox or group addresses for the field value.
549
* @return the newly created address-list field.
551
public static AddressListField addressList(String fieldName,
552
Iterable<Address> addresses) {
553
checkValidFieldName(fieldName);
554
return addressList0(fieldName, addresses);
557
private static DateTimeField date0(String fieldName, Date date,
559
final String formattedDate = MimeUtil.formatDate(date, zone);
560
return parse(DateTimeField.PARSER, fieldName, formattedDate);
563
private static MailboxField mailbox0(String fieldName, Mailbox mailbox) {
564
String fieldValue = encodeAddresses(Collections.singleton(mailbox));
565
return parse(MailboxField.PARSER, fieldName, fieldValue);
568
private static MailboxListField mailboxList0(String fieldName,
569
Iterable<Mailbox> mailboxes) {
570
String fieldValue = encodeAddresses(mailboxes);
571
return parse(MailboxListField.PARSER, fieldName, fieldValue);
574
private static AddressListField addressList0(String fieldName,
575
Iterable<Address> addresses) {
576
String fieldValue = encodeAddresses(addresses);
577
return parse(AddressListField.PARSER, fieldName, fieldValue);
580
private static void checkValidFieldName(String fieldName) {
581
if (!FIELD_NAME_PATTERN.matcher(fieldName).matches())
582
throw new IllegalArgumentException("Invalid field name");
585
private static boolean isValidMimeType(String mimeType) {
586
if (mimeType == null)
589
int idx = mimeType.indexOf('/');
593
String type = mimeType.substring(0, idx);
594
String subType = mimeType.substring(idx + 1);
595
return EncoderUtil.isToken(type) && EncoderUtil.isToken(subType);
598
private static boolean isValidDispositionType(String dispositionType) {
599
if (dispositionType == null)
602
return EncoderUtil.isToken(dispositionType);
605
private static <F extends Field> F parse(FieldParser parser,
606
String fieldName, String fieldBody) {
607
String rawStr = MimeUtil.fold(fieldName + ": " + fieldBody, 0);
608
ByteSequence raw = ContentUtil.encode(rawStr);
610
Field field = parser.parse(fieldName, fieldBody, raw);
612
@SuppressWarnings("unchecked")
617
private static String encodeAddresses(Iterable<? extends Address> addresses) {
618
StringBuilder sb = new StringBuilder();
620
for (Address address : addresses) {
621
if (sb.length() > 0) {
624
sb.append(address.getEncodedString());
627
return sb.toString();