View Javadoc

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>&quot;text/plain&quot;</code> or
70       *            <code>&quot;application/octet-stream&quot;</code>).
71       * @param parameters
72       *            map containing content-type parameters such as
73       *            <code>&quot;boundary&quot;</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>&quot;7-bit&quot;</code>
102      *            or <code>&quot;quoted-printable&quot;</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>&quot;inline&quot;</code>
133      *            or <code>&quot;attachment&quot;</code>).
134      * @param parameters
135      *            map containing disposition parameters such as
136      *            <code>&quot;filename&quot;</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>&quot;inline&quot;</code>
165      *            or <code>&quot;attachment&quot;</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>&quot;inline&quot;</code>
182      *            or <code>&quot;attachment&quot;</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>&quot;inline&quot;</code>
202      *            or <code>&quot;attachment&quot;</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 }