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 *
9 * *
10 * http://www.apache.org/licenses/LICENSE-2.0 *
11 * *
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 ****************************************************************/
19
20 package org.apache.james.mime4j.field;
21
22 import java.util.Arrays;
23 import java.util.Collections;
24 import java.util.Date;
25 import java.util.HashMap;
26 import java.util.Map;
27 import java.util.TimeZone;
28 import java.util.regex.Pattern;
29
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;
37
38 /**
39 * Factory for concrete {@link Field} instances.
40 */
41 public class Fields {
42
43 private static final Pattern FIELD_NAME_PATTERN = Pattern
44 .compile("[\\x21-\\x39\\x3b-\\x7e]+");
45
46 private Fields() {
47 }
48
49 /**
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.
53 *
54 * @param contentType
55 * raw content type containing a MIME type and optional
56 * parameters.
57 * @return the newly created <i>Content-Type</i> field.
58 */
59 public static ContentTypeField contentType(String contentType) {
60 return parse(ContentTypeField.PARSER, FieldName.CONTENT_TYPE,
61 contentType);
62 }
63
64 /**
65 * Creates a <i>Content-Type</i> field from the specified MIME type and
66 * parameters.
67 *
68 * @param mimeType
69 * a MIME type (such as <code>"text/plain"</code> or
70 * <code>"application/octet-stream"</code>).
71 * @param parameters
72 * map containing content-type parameters such as
73 * <code>"boundary"</code>.
74 * @return the newly created <i>Content-Type</i> field.
75 */
76 public static ContentTypeField contentType(String mimeType,
77 Map<String, String> parameters) {
78 if (!isValidMimeType(mimeType))
79 throw new IllegalArgumentException();
80
81 if (parameters == null || parameters.isEmpty()) {
82 return parse(ContentTypeField.PARSER, FieldName.CONTENT_TYPE,
83 mimeType);
84 } else {
85 StringBuilder sb = new StringBuilder(mimeType);
86 for (Map.Entry<String, String> entry : parameters.entrySet()) {
87 sb.append("; ");
88 sb.append(EncoderUtil.encodeHeaderParameter(entry.getKey(),
89 entry.getValue()));
90 }
91 String contentType = sb.toString();
92 return contentType(contentType);
93 }
94 }
95
96 /**
97 * Creates a <i>Content-Transfer-Encoding</i> field from the specified raw
98 * field value.
99 *
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.
104 */
105 public static ContentTransferEncodingField contentTransferEncoding(
106 String contentTransferEncoding) {
107 return parse(ContentTransferEncodingField.PARSER,
108 FieldName.CONTENT_TRANSFER_ENCODING, contentTransferEncoding);
109 }
110
111 /**
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.
115 *
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.
120 */
121 public static ContentDispositionField contentDisposition(
122 String contentDisposition) {
123 return parse(ContentDispositionField.PARSER,
124 FieldName.CONTENT_DISPOSITION, contentDisposition);
125 }
126
127 /**
128 * Creates a <i>Content-Disposition</i> field from the specified
129 * disposition type and parameters.
130 *
131 * @param dispositionType
132 * a disposition type (usually <code>"inline"</code>
133 * or <code>"attachment"</code>).
134 * @param parameters
135 * map containing disposition parameters such as
136 * <code>"filename"</code>.
137 * @return the newly created <i>Content-Disposition</i> field.
138 */
139 public static ContentDispositionField contentDisposition(
140 String dispositionType, Map<String, String> parameters) {
141 if (!isValidDispositionType(dispositionType))
142 throw new IllegalArgumentException();
143
144 if (parameters == null || parameters.isEmpty()) {
145 return parse(ContentDispositionField.PARSER,
146 FieldName.CONTENT_DISPOSITION, dispositionType);
147 } else {
148 StringBuilder sb = new StringBuilder(dispositionType);
149 for (Map.Entry<String, String> entry : parameters.entrySet()) {
150 sb.append("; ");
151 sb.append(EncoderUtil.encodeHeaderParameter(entry.getKey(),
152 entry.getValue()));
153 }
154 String contentDisposition = sb.toString();
155 return contentDisposition(contentDisposition);
156 }
157 }
158
159 /**
160 * Creates a <i>Content-Disposition</i> field from the specified
161 * disposition type and filename.
162 *
163 * @param dispositionType
164 * a disposition type (usually <code>"inline"</code>
165 * or <code>"attachment"</code>).
166 * @param filename
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.
170 */
171 public static ContentDispositionField contentDisposition(
172 String dispositionType, String filename) {
173 return contentDisposition(dispositionType, filename, -1, null, null,
174 null);
175 }
176
177 /**
178 * Creates a <i>Content-Disposition</i> field from the specified values.
179 *
180 * @param dispositionType
181 * a disposition type (usually <code>"inline"</code>
182 * or <code>"attachment"</code>).
183 * @param filename
184 * filename parameter value or <code>null</code> if the
185 * parameter should not be included.
186 * @param size
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.
190 */
191 public static ContentDispositionField contentDisposition(
192 String dispositionType, String filename, long size) {
193 return contentDisposition(dispositionType, filename, size, null, null,
194 null);
195 }
196
197 /**
198 * Creates a <i>Content-Disposition</i> field from the specified values.
199 *
200 * @param dispositionType
201 * a disposition type (usually <code>"inline"</code>
202 * or <code>"attachment"</code>).
203 * @param filename
204 * filename parameter value or <code>null</code> if the
205 * parameter should not be included.
206 * @param size
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.
215 * @param readDate
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.
219 */
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);
226 }
227 if (size >= 0) {
228 parameters.put(ContentDispositionField.PARAM_SIZE, Long
229 .toString(size));
230 }
231 if (creationDate != null) {
232 parameters.put(ContentDispositionField.PARAM_CREATION_DATE,
233 MimeUtil.formatDate(creationDate, null));
234 }
235 if (modificationDate != null) {
236 parameters.put(ContentDispositionField.PARAM_MODIFICATION_DATE,
237 MimeUtil.formatDate(modificationDate, null));
238 }
239 if (readDate != null) {
240 parameters.put(ContentDispositionField.PARAM_READ_DATE, MimeUtil
241 .formatDate(readDate, null));
242 }
243 return contentDisposition(dispositionType, parameters);
244 }
245
246 /**
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.
249 *
250 * @param date
251 * date value for the header field.
252 * @return the newly created <i>Date</i> field.
253 */
254 public static DateTimeField date(Date date) {
255 return date0(FieldName.DATE, date, null);
256 }
257
258 /**
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.
261 *
262 * @param fieldName
263 * a field name such as <code>Date</code> or
264 * <code>Resent-Date</code>.
265 * @param date
266 * date value for the header field.
267 * @return the newly created date field.
268 */
269 public static DateTimeField date(String fieldName, Date date) {
270 checkValidFieldName(fieldName);
271 return date0(fieldName, date, null);
272 }
273
274 /**
275 * Creates a date field from the specified field name, <code>Date</code>
276 * and <code>TimeZone</code> values.
277 *
278 * @param fieldName
279 * a field name such as <code>Date</code> or
280 * <code>Resent-Date</code>.
281 * @param date
282 * date value for the header field.
283 * @param zone
284 * the time zone to be used for formatting the date.
285 * @return the newly created date field.
286 */
287 public static DateTimeField date(String fieldName, Date date, TimeZone zone) {
288 checkValidFieldName(fieldName);
289 return date0(fieldName, date, zone);
290 }
291
292 /**
293 * Creates a <i>Message-ID</i> field for the specified host name.
294 *
295 * @param hostname
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.
299 */
300 public static Field messageId(String hostname) {
301 String fieldValue = MimeUtil.createUniqueMessageId(hostname);
302 return parse(UnstructuredField.PARSER, FieldName.MESSAGE_ID, fieldValue);
303 }
304
305 /**
306 * Creates a <i>Subject</i> field from the specified string value. The
307 * specified string may contain non-ASCII characters.
308 *
309 * @param subject
310 * the subject string.
311 * @return the newly created <i>Subject</i> field.
312 */
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);
317
318 return parse(UnstructuredField.PARSER, FieldName.SUBJECT, fieldValue);
319 }
320
321 /**
322 * Creates a <i>Sender</i> field for the specified mailbox address.
323 *
324 * @param mailbox
325 * address to be included in the field.
326 * @return the newly created <i>Sender</i> field.
327 */
328 public static MailboxField sender(Mailbox mailbox) {
329 return mailbox0(FieldName.SENDER, mailbox);
330 }
331
332 /**
333 * Creates a <i>From</i> field for the specified mailbox address.
334 *
335 * @param mailbox
336 * address to be included in the field.
337 * @return the newly created <i>From</i> field.
338 */
339 public static MailboxListField from(Mailbox mailbox) {
340 return mailboxList0(FieldName.FROM, Collections.singleton(mailbox));
341 }
342
343 /**
344 * Creates a <i>From</i> field for the specified mailbox addresses.
345 *
346 * @param mailboxes
347 * addresses to be included in the field.
348 * @return the newly created <i>From</i> field.
349 */
350 public static MailboxListField from(Mailbox... mailboxes) {
351 return mailboxList0(FieldName.FROM, Arrays.asList(mailboxes));
352 }
353
354 /**
355 * Creates a <i>From</i> field for the specified mailbox addresses.
356 *
357 * @param mailboxes
358 * addresses to be included in the field.
359 * @return the newly created <i>From</i> field.
360 */
361 public static MailboxListField from(Iterable<Mailbox> mailboxes) {
362 return mailboxList0(FieldName.FROM, mailboxes);
363 }
364
365 /**
366 * Creates a <i>To</i> field for the specified mailbox or group address.
367 *
368 * @param address
369 * mailbox or group address to be included in the field.
370 * @return the newly created <i>To</i> field.
371 */
372 public static AddressListField to(Address address) {
373 return addressList0(FieldName.TO, Collections.singleton(address));
374 }
375
376 /**
377 * Creates a <i>To</i> field for the specified mailbox or group addresses.
378 *
379 * @param addresses
380 * mailbox or group addresses to be included in the field.
381 * @return the newly created <i>To</i> field.
382 */
383 public static AddressListField to(Address... addresses) {
384 return addressList0(FieldName.TO, Arrays.asList(addresses));
385 }
386
387 /**
388 * Creates a <i>To</i> field for the specified mailbox or group addresses.
389 *
390 * @param addresses
391 * mailbox or group addresses to be included in the field.
392 * @return the newly created <i>To</i> field.
393 */
394 public static AddressListField to(Iterable<Address> addresses) {
395 return addressList0(FieldName.TO, addresses);
396 }
397
398 /**
399 * Creates a <i>Cc</i> field for the specified mailbox or group address.
400 *
401 * @param address
402 * mailbox or group address to be included in the field.
403 * @return the newly created <i>Cc</i> field.
404 */
405 public static AddressListField cc(Address address) {
406 return addressList0(FieldName.CC, Collections.singleton(address));
407 }
408
409 /**
410 * Creates a <i>Cc</i> field for the specified mailbox or group addresses.
411 *
412 * @param addresses
413 * mailbox or group addresses to be included in the field.
414 * @return the newly created <i>Cc</i> field.
415 */
416 public static AddressListField cc(Address... addresses) {
417 return addressList0(FieldName.CC, Arrays.asList(addresses));
418 }
419
420 /**
421 * Creates a <i>Cc</i> field for the specified mailbox or group addresses.
422 *
423 * @param addresses
424 * mailbox or group addresses to be included in the field.
425 * @return the newly created <i>Cc</i> field.
426 */
427 public static AddressListField cc(Iterable<Address> addresses) {
428 return addressList0(FieldName.CC, addresses);
429 }
430
431 /**
432 * Creates a <i>Bcc</i> field for the specified mailbox or group address.
433 *
434 * @param address
435 * mailbox or group address to be included in the field.
436 * @return the newly created <i>Bcc</i> field.
437 */
438 public static AddressListField bcc(Address address) {
439 return addressList0(FieldName.BCC, Collections.singleton(address));
440 }
441
442 /**
443 * Creates a <i>Bcc</i> field for the specified mailbox or group addresses.
444 *
445 * @param addresses
446 * mailbox or group addresses to be included in the field.
447 * @return the newly created <i>Bcc</i> field.
448 */
449 public static AddressListField bcc(Address... addresses) {
450 return addressList0(FieldName.BCC, Arrays.asList(addresses));
451 }
452
453 /**
454 * Creates a <i>Bcc</i> field for the specified mailbox or group addresses.
455 *
456 * @param addresses
457 * mailbox or group addresses to be included in the field.
458 * @return the newly created <i>Bcc</i> field.
459 */
460 public static AddressListField bcc(Iterable<Address> addresses) {
461 return addressList0(FieldName.BCC, addresses);
462 }
463
464 /**
465 * Creates a <i>Reply-To</i> field for the specified mailbox or group
466 * address.
467 *
468 * @param address
469 * mailbox or group address to be included in the field.
470 * @return the newly created <i>Reply-To</i> field.
471 */
472 public static AddressListField replyTo(Address address) {
473 return addressList0(FieldName.REPLY_TO, Collections.singleton(address));
474 }
475
476 /**
477 * Creates a <i>Reply-To</i> field for the specified mailbox or group
478 * addresses.
479 *
480 * @param addresses
481 * mailbox or group addresses to be included in the field.
482 * @return the newly created <i>Reply-To</i> field.
483 */
484 public static AddressListField replyTo(Address... addresses) {
485 return addressList0(FieldName.REPLY_TO, Arrays.asList(addresses));
486 }
487
488 /**
489 * Creates a <i>Reply-To</i> field for the specified mailbox or group
490 * addresses.
491 *
492 * @param addresses
493 * mailbox or group addresses to be included in the field.
494 * @return the newly created <i>Reply-To</i> field.
495 */
496 public static AddressListField replyTo(Iterable<Address> addresses) {
497 return addressList0(FieldName.REPLY_TO, addresses);
498 }
499
500 /**
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>.
504 *
505 * @param fieldName
506 * the name of the mailbox field (<code>Sender</code> or
507 * <code>Resent-Sender</code>).
508 * @param mailbox
509 * mailbox address for the field value.
510 * @return the newly created mailbox field.
511 */
512 public static MailboxField mailbox(String fieldName, Mailbox mailbox) {
513 checkValidFieldName(fieldName);
514 return mailbox0(fieldName, mailbox);
515 }
516
517 /**
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>.
521 *
522 * @param fieldName
523 * the name of the mailbox field (<code>From</code> or
524 * <code>Resent-From</code>).
525 * @param mailboxes
526 * mailbox addresses for the field value.
527 * @return the newly created mailbox-list field.
528 */
529 public static MailboxListField mailboxList(String fieldName,
530 Iterable<Mailbox> mailboxes) {
531 checkValidFieldName(fieldName);
532 return mailboxList0(fieldName, mailboxes);
533 }
534
535 /**
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>.
541 *
542 * @param fieldName
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>).
547 * @param addresses
548 * mailbox or group addresses for the field value.
549 * @return the newly created address-list field.
550 */
551 public static AddressListField addressList(String fieldName,
552 Iterable<Address> addresses) {
553 checkValidFieldName(fieldName);
554 return addressList0(fieldName, addresses);
555 }
556
557 private static DateTimeField date0(String fieldName, Date date,
558 TimeZone zone) {
559 final String formattedDate = MimeUtil.formatDate(date, zone);
560 return parse(DateTimeField.PARSER, fieldName, formattedDate);
561 }
562
563 private static MailboxField mailbox0(String fieldName, Mailbox mailbox) {
564 String fieldValue = encodeAddresses(Collections.singleton(mailbox));
565 return parse(MailboxField.PARSER, fieldName, fieldValue);
566 }
567
568 private static MailboxListField mailboxList0(String fieldName,
569 Iterable<Mailbox> mailboxes) {
570 String fieldValue = encodeAddresses(mailboxes);
571 return parse(MailboxListField.PARSER, fieldName, fieldValue);
572 }
573
574 private static AddressListField addressList0(String fieldName,
575 Iterable<Address> addresses) {
576 String fieldValue = encodeAddresses(addresses);
577 return parse(AddressListField.PARSER, fieldName, fieldValue);
578 }
579
580 private static void checkValidFieldName(String fieldName) {
581 if (!FIELD_NAME_PATTERN.matcher(fieldName).matches())
582 throw new IllegalArgumentException("Invalid field name");
583 }
584
585 private static boolean isValidMimeType(String mimeType) {
586 if (mimeType == null)
587 return false;
588
589 int idx = mimeType.indexOf('/');
590 if (idx == -1)
591 return false;
592
593 String type = mimeType.substring(0, idx);
594 String subType = mimeType.substring(idx + 1);
595 return EncoderUtil.isToken(type) && EncoderUtil.isToken(subType);
596 }
597
598 private static boolean isValidDispositionType(String dispositionType) {
599 if (dispositionType == null)
600 return false;
601
602 return EncoderUtil.isToken(dispositionType);
603 }
604
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);
609
610 Field field = parser.parse(fieldName, fieldBody, raw);
611
612 @SuppressWarnings("unchecked")
613 F f = (F) field;
614 return f;
615 }
616
617 private static String encodeAddresses(Iterable<? extends Address> addresses) {
618 StringBuilder sb = new StringBuilder();
619
620 for (Address address : addresses) {
621 if (sb.length() > 0) {
622 sb.append(", ");
623 }
624 sb.append(address.getEncodedString());
625 }
626
627 return sb.toString();
628 }
629
630 }