001/**************************************************************** 002 * Licensed to the Apache Software Foundation (ASF) under one * 003 * or more contributor license agreements. See the NOTICE file * 004 * distributed with this work for additional information * 005 * regarding copyright ownership. The ASF licenses this file * 006 * to you under the Apache License, Version 2.0 (the * 007 * "License"); you may not use this file except in compliance * 008 * with the License. You may obtain a copy of the License at * 009 * * 010 * http://www.apache.org/licenses/LICENSE-2.0 * 011 * * 012 * Unless required by applicable law or agreed to in writing, * 013 * software distributed under the License is distributed on an * 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * 015 * KIND, either express or implied. See the License for the * 016 * specific language governing permissions and limitations * 017 * under the License. * 018 ****************************************************************/ 019 020package org.apache.james.mime4j.field; 021 022import java.util.Arrays; 023import java.util.Collections; 024import java.util.Date; 025import java.util.HashMap; 026import java.util.Map; 027import java.util.TimeZone; 028import java.util.regex.Pattern; 029 030import org.apache.james.mime4j.codec.DecodeMonitor; 031import org.apache.james.mime4j.codec.EncoderUtil; 032import org.apache.james.mime4j.dom.FieldParser; 033import org.apache.james.mime4j.dom.address.Address; 034import org.apache.james.mime4j.dom.address.Mailbox; 035import org.apache.james.mime4j.dom.field.AddressListField; 036import org.apache.james.mime4j.dom.field.ContentDispositionField; 037import org.apache.james.mime4j.dom.field.ContentTransferEncodingField; 038import org.apache.james.mime4j.dom.field.ContentTypeField; 039import org.apache.james.mime4j.dom.field.DateTimeField; 040import org.apache.james.mime4j.dom.field.FieldName; 041import org.apache.james.mime4j.dom.field.MailboxField; 042import org.apache.james.mime4j.dom.field.MailboxListField; 043import org.apache.james.mime4j.dom.field.ParsedField; 044import org.apache.james.mime4j.dom.field.UnstructuredField; 045import org.apache.james.mime4j.field.address.AddressFormatter; 046import org.apache.james.mime4j.stream.Field; 047import org.apache.james.mime4j.stream.RawField; 048import org.apache.james.mime4j.util.MimeUtil; 049 050/** 051 * Factory for concrete {@link Field} instances. 052 */ 053public class Fields { 054 055 private static final Pattern FIELD_NAME_PATTERN = Pattern 056 .compile("[\\x21-\\x39\\x3b-\\x7e]+"); 057 058 private Fields() { 059 } 060 061 /** 062 * Creates a <i>Content-Type</i> field from the specified raw field value. 063 * The specified string gets folded into a multiple-line representation if 064 * necessary but is otherwise taken as is. 065 * 066 * @param contentType 067 * raw content type containing a MIME type and optional 068 * parameters. 069 * @return the newly created <i>Content-Type</i> field. 070 */ 071 public static ContentTypeField contentType(String contentType) { 072 return parse(ContentTypeFieldImpl.PARSER, FieldName.CONTENT_TYPE, 073 contentType); 074 } 075 076 /** 077 * Creates a <i>Content-Type</i> field from the specified MIME type and 078 * parameters. 079 * 080 * @param mimeType 081 * a MIME type (such as <code>"text/plain"</code> or 082 * <code>"application/octet-stream"</code>). 083 * @param parameters 084 * map containing content-type parameters such as 085 * <code>"boundary"</code>. 086 * @return the newly created <i>Content-Type</i> field. 087 */ 088 public static ContentTypeField contentType(String mimeType, 089 Map<String, String> parameters) { 090 if (!isValidMimeType(mimeType)) 091 throw new IllegalArgumentException(); 092 093 if (parameters == null || parameters.isEmpty()) { 094 return parse(ContentTypeFieldImpl.PARSER, FieldName.CONTENT_TYPE, 095 mimeType); 096 } else { 097 StringBuilder sb = new StringBuilder(mimeType); 098 for (Map.Entry<String, String> entry : parameters.entrySet()) { 099 sb.append("; "); 100 sb.append(EncoderUtil.encodeHeaderParameter(entry.getKey(), 101 entry.getValue())); 102 } 103 String contentType = sb.toString(); 104 return contentType(contentType); 105 } 106 } 107 108 /** 109 * Creates a <i>Content-Transfer-Encoding</i> field from the specified raw 110 * field value. 111 * 112 * @param contentTransferEncoding 113 * an encoding mechanism such as <code>"7-bit"</code> 114 * or <code>"quoted-printable"</code>. 115 * @return the newly created <i>Content-Transfer-Encoding</i> field. 116 */ 117 public static ContentTransferEncodingField contentTransferEncoding( 118 String contentTransferEncoding) { 119 return parse(ContentTransferEncodingFieldImpl.PARSER, 120 FieldName.CONTENT_TRANSFER_ENCODING, contentTransferEncoding); 121 } 122 123 /** 124 * Creates a <i>Content-Disposition</i> field from the specified raw field 125 * value. The specified string gets folded into a multiple-line 126 * representation if necessary but is otherwise taken as is. 127 * 128 * @param contentDisposition 129 * raw content disposition containing a disposition type and 130 * optional parameters. 131 * @return the newly created <i>Content-Disposition</i> field. 132 */ 133 public static ContentDispositionField contentDisposition( 134 String contentDisposition) { 135 return parse(ContentDispositionFieldImpl.PARSER, 136 FieldName.CONTENT_DISPOSITION, contentDisposition); 137 } 138 139 /** 140 * Creates a <i>Content-Disposition</i> field from the specified 141 * disposition type and parameters. 142 * 143 * @param dispositionType 144 * a disposition type (usually <code>"inline"</code> 145 * or <code>"attachment"</code>). 146 * @param parameters 147 * map containing disposition parameters such as 148 * <code>"filename"</code>. 149 * @return the newly created <i>Content-Disposition</i> field. 150 */ 151 public static ContentDispositionField contentDisposition( 152 String dispositionType, Map<String, String> parameters) { 153 if (!isValidDispositionType(dispositionType)) 154 throw new IllegalArgumentException(); 155 156 if (parameters == null || parameters.isEmpty()) { 157 return parse(ContentDispositionFieldImpl.PARSER, 158 FieldName.CONTENT_DISPOSITION, dispositionType); 159 } else { 160 StringBuilder sb = new StringBuilder(dispositionType); 161 for (Map.Entry<String, String> entry : parameters.entrySet()) { 162 sb.append("; "); 163 sb.append(EncoderUtil.encodeHeaderParameter(entry.getKey(), 164 entry.getValue())); 165 } 166 String contentDisposition = sb.toString(); 167 return contentDisposition(contentDisposition); 168 } 169 } 170 171 /** 172 * Creates a <i>Content-Disposition</i> field from the specified 173 * disposition type and filename. 174 * 175 * @param dispositionType 176 * a disposition type (usually <code>"inline"</code> 177 * or <code>"attachment"</code>). 178 * @param filename 179 * filename parameter value or <code>null</code> if the 180 * parameter should not be included. 181 * @return the newly created <i>Content-Disposition</i> field. 182 */ 183 public static ContentDispositionField contentDisposition( 184 String dispositionType, String filename) { 185 return contentDisposition(dispositionType, filename, -1, null, null, 186 null); 187 } 188 189 /** 190 * Creates a <i>Content-Disposition</i> field from the specified values. 191 * 192 * @param dispositionType 193 * a disposition type (usually <code>"inline"</code> 194 * or <code>"attachment"</code>). 195 * @param filename 196 * filename parameter value or <code>null</code> if the 197 * parameter should not be included. 198 * @param size 199 * size parameter value or <code>-1</code> if the parameter 200 * should not be included. 201 * @return the newly created <i>Content-Disposition</i> field. 202 */ 203 public static ContentDispositionField contentDisposition( 204 String dispositionType, String filename, long size) { 205 return contentDisposition(dispositionType, filename, size, null, null, 206 null); 207 } 208 209 /** 210 * Creates a <i>Content-Disposition</i> field from the specified values. 211 * 212 * @param dispositionType 213 * a disposition type (usually <code>"inline"</code> 214 * or <code>"attachment"</code>). 215 * @param filename 216 * filename parameter value or <code>null</code> if the 217 * parameter should not be included. 218 * @param size 219 * size parameter value or <code>-1</code> if the parameter 220 * should not be included. 221 * @param creationDate 222 * creation-date parameter value or <code>null</code> if the 223 * parameter should not be included. 224 * @param modificationDate 225 * modification-date parameter value or <code>null</code> if 226 * the parameter should not be included. 227 * @param readDate 228 * read-date parameter value or <code>null</code> if the 229 * parameter should not be included. 230 * @return the newly created <i>Content-Disposition</i> field. 231 */ 232 public static ContentDispositionField contentDisposition( 233 String dispositionType, String filename, long size, 234 Date creationDate, Date modificationDate, Date readDate) { 235 Map<String, String> parameters = new HashMap<String, String>(); 236 if (filename != null) { 237 parameters.put(ContentDispositionFieldImpl.PARAM_FILENAME, filename); 238 } 239 if (size >= 0) { 240 parameters.put(ContentDispositionFieldImpl.PARAM_SIZE, Long 241 .toString(size)); 242 } 243 if (creationDate != null) { 244 parameters.put(ContentDispositionFieldImpl.PARAM_CREATION_DATE, 245 MimeUtil.formatDate(creationDate, null)); 246 } 247 if (modificationDate != null) { 248 parameters.put(ContentDispositionFieldImpl.PARAM_MODIFICATION_DATE, 249 MimeUtil.formatDate(modificationDate, null)); 250 } 251 if (readDate != null) { 252 parameters.put(ContentDispositionFieldImpl.PARAM_READ_DATE, MimeUtil 253 .formatDate(readDate, null)); 254 } 255 return contentDisposition(dispositionType, parameters); 256 } 257 258 /** 259 * Creates a <i>Date</i> field from the specified <code>Date</code> 260 * value. The default time zone of the host is used to format the date. 261 * 262 * @param date 263 * date value for the header field. 264 * @return the newly created <i>Date</i> field. 265 */ 266 public static DateTimeField date(Date date) { 267 return date0(FieldName.DATE, date, null); 268 } 269 270 /** 271 * Creates a date field from the specified field name and <code>Date</code> 272 * value. The default time zone of the host is used to format the date. 273 * 274 * @param fieldName 275 * a field name such as <code>Date</code> or 276 * <code>Resent-Date</code>. 277 * @param date 278 * date value for the header field. 279 * @return the newly created date field. 280 */ 281 public static DateTimeField date(String fieldName, Date date) { 282 checkValidFieldName(fieldName); 283 return date0(fieldName, date, null); 284 } 285 286 /** 287 * Creates a date field from the specified field name, <code>Date</code> 288 * and <code>TimeZone</code> values. 289 * 290 * @param fieldName 291 * a field name such as <code>Date</code> or 292 * <code>Resent-Date</code>. 293 * @param date 294 * date value for the header field. 295 * @param zone 296 * the time zone to be used for formatting the date. 297 * @return the newly created date field. 298 */ 299 public static DateTimeField date(String fieldName, Date date, TimeZone zone) { 300 checkValidFieldName(fieldName); 301 return date0(fieldName, date, zone); 302 } 303 304 /** 305 * Creates a <i>Message-ID</i> field for the specified host name. 306 * 307 * @param hostname 308 * host name to be included in the message ID or 309 * <code>null</code> if no host name should be included. 310 * @return the newly created <i>Message-ID</i> field. 311 */ 312 public static UnstructuredField messageId(String hostname) { 313 String fieldValue = MimeUtil.createUniqueMessageId(hostname); 314 return parse(UnstructuredFieldImpl.PARSER, FieldName.MESSAGE_ID, fieldValue); 315 } 316 317 /** 318 * Creates a <i>Subject</i> field from the specified string value. The 319 * specified string may contain non-ASCII characters. 320 * 321 * @param subject 322 * the subject string. 323 * @return the newly created <i>Subject</i> field. 324 */ 325 public static UnstructuredField subject(String subject) { 326 int usedCharacters = FieldName.SUBJECT.length() + 2; 327 String fieldValue = EncoderUtil.encodeIfNecessary(subject, 328 EncoderUtil.Usage.TEXT_TOKEN, usedCharacters); 329 330 return parse(UnstructuredFieldImpl.PARSER, FieldName.SUBJECT, fieldValue); 331 } 332 333 /** 334 * Creates a <i>Sender</i> field for the specified mailbox address. 335 * 336 * @param mailbox 337 * address to be included in the field. 338 * @return the newly created <i>Sender</i> field. 339 */ 340 public static MailboxField sender(Mailbox mailbox) { 341 return mailbox0(FieldName.SENDER, mailbox); 342 } 343 344 /** 345 * Creates a <i>From</i> field for the specified mailbox address. 346 * 347 * @param mailbox 348 * address to be included in the field. 349 * @return the newly created <i>From</i> field. 350 */ 351 public static MailboxListField from(Mailbox mailbox) { 352 return mailboxList0(FieldName.FROM, Collections.singleton(mailbox)); 353 } 354 355 /** 356 * Creates a <i>From</i> field for the specified mailbox addresses. 357 * 358 * @param mailboxes 359 * addresses to be included in the field. 360 * @return the newly created <i>From</i> field. 361 */ 362 public static MailboxListField from(Mailbox... mailboxes) { 363 return mailboxList0(FieldName.FROM, Arrays.asList(mailboxes)); 364 } 365 366 /** 367 * Creates a <i>From</i> field for the specified mailbox addresses. 368 * 369 * @param mailboxes 370 * addresses to be included in the field. 371 * @return the newly created <i>From</i> field. 372 */ 373 public static MailboxListField from(Iterable<Mailbox> mailboxes) { 374 return mailboxList0(FieldName.FROM, mailboxes); 375 } 376 377 /** 378 * Creates a <i>To</i> field for the specified mailbox or group address. 379 * 380 * @param address 381 * mailbox or group address to be included in the field. 382 * @return the newly created <i>To</i> field. 383 */ 384 public static AddressListField to(Address address) { 385 return addressList0(FieldName.TO, Collections.singleton(address)); 386 } 387 388 /** 389 * Creates a <i>To</i> field for the specified mailbox or group addresses. 390 * 391 * @param addresses 392 * mailbox or group addresses to be included in the field. 393 * @return the newly created <i>To</i> field. 394 */ 395 public static AddressListField to(Address... addresses) { 396 return addressList0(FieldName.TO, Arrays.asList(addresses)); 397 } 398 399 /** 400 * Creates a <i>To</i> field for the specified mailbox or group addresses. 401 * 402 * @param addresses 403 * mailbox or group addresses to be included in the field. 404 * @return the newly created <i>To</i> field. 405 */ 406 public static AddressListField to(Iterable<Address> addresses) { 407 return addressList0(FieldName.TO, addresses); 408 } 409 410 /** 411 * Creates a <i>Cc</i> field for the specified mailbox or group address. 412 * 413 * @param address 414 * mailbox or group address to be included in the field. 415 * @return the newly created <i>Cc</i> field. 416 */ 417 public static AddressListField cc(Address address) { 418 return addressList0(FieldName.CC, Collections.singleton(address)); 419 } 420 421 /** 422 * Creates a <i>Cc</i> field for the specified mailbox or group addresses. 423 * 424 * @param addresses 425 * mailbox or group addresses to be included in the field. 426 * @return the newly created <i>Cc</i> field. 427 */ 428 public static AddressListField cc(Address... addresses) { 429 return addressList0(FieldName.CC, Arrays.asList(addresses)); 430 } 431 432 /** 433 * Creates a <i>Cc</i> field for the specified mailbox or group addresses. 434 * 435 * @param addresses 436 * mailbox or group addresses to be included in the field. 437 * @return the newly created <i>Cc</i> field. 438 */ 439 public static AddressListField cc(Iterable<Address> addresses) { 440 return addressList0(FieldName.CC, addresses); 441 } 442 443 /** 444 * Creates a <i>Bcc</i> field for the specified mailbox or group address. 445 * 446 * @param address 447 * mailbox or group address to be included in the field. 448 * @return the newly created <i>Bcc</i> field. 449 */ 450 public static AddressListField bcc(Address address) { 451 return addressList0(FieldName.BCC, Collections.singleton(address)); 452 } 453 454 /** 455 * Creates a <i>Bcc</i> field for the specified mailbox or group addresses. 456 * 457 * @param addresses 458 * mailbox or group addresses to be included in the field. 459 * @return the newly created <i>Bcc</i> field. 460 */ 461 public static AddressListField bcc(Address... addresses) { 462 return addressList0(FieldName.BCC, Arrays.asList(addresses)); 463 } 464 465 /** 466 * Creates a <i>Bcc</i> field for the specified mailbox or group addresses. 467 * 468 * @param addresses 469 * mailbox or group addresses to be included in the field. 470 * @return the newly created <i>Bcc</i> field. 471 */ 472 public static AddressListField bcc(Iterable<Address> addresses) { 473 return addressList0(FieldName.BCC, addresses); 474 } 475 476 /** 477 * Creates a <i>Reply-To</i> field for the specified mailbox or group 478 * address. 479 * 480 * @param address 481 * mailbox or group address to be included in the field. 482 * @return the newly created <i>Reply-To</i> field. 483 */ 484 public static AddressListField replyTo(Address address) { 485 return addressList0(FieldName.REPLY_TO, Collections.singleton(address)); 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(Address... addresses) { 497 return addressList0(FieldName.REPLY_TO, Arrays.asList(addresses)); 498 } 499 500 /** 501 * Creates a <i>Reply-To</i> field for the specified mailbox or group 502 * addresses. 503 * 504 * @param addresses 505 * mailbox or group addresses to be included in the field. 506 * @return the newly created <i>Reply-To</i> field. 507 */ 508 public static AddressListField replyTo(Iterable<Address> addresses) { 509 return addressList0(FieldName.REPLY_TO, addresses); 510 } 511 512 /** 513 * Creates a mailbox field from the specified field name and mailbox 514 * address. Valid field names are <code>Sender</code> and 515 * <code>Resent-Sender</code>. 516 * 517 * @param fieldName 518 * the name of the mailbox field (<code>Sender</code> or 519 * <code>Resent-Sender</code>). 520 * @param mailbox 521 * mailbox address for the field value. 522 * @return the newly created mailbox field. 523 */ 524 public static MailboxField mailbox(String fieldName, Mailbox mailbox) { 525 checkValidFieldName(fieldName); 526 return mailbox0(fieldName, mailbox); 527 } 528 529 /** 530 * Creates a mailbox-list field from the specified field name and mailbox 531 * addresses. Valid field names are <code>From</code> and 532 * <code>Resent-From</code>. 533 * 534 * @param fieldName 535 * the name of the mailbox field (<code>From</code> or 536 * <code>Resent-From</code>). 537 * @param mailboxes 538 * mailbox addresses for the field value. 539 * @return the newly created mailbox-list field. 540 */ 541 public static MailboxListField mailboxList(String fieldName, 542 Iterable<Mailbox> mailboxes) { 543 checkValidFieldName(fieldName); 544 return mailboxList0(fieldName, mailboxes); 545 } 546 547 /** 548 * Creates an address-list field from the specified field name and mailbox 549 * or group addresses. Valid field names are <code>To</code>, 550 * <code>Cc</code>, <code>Bcc</code>, <code>Reply-To</code>, 551 * <code>Resent-To</code>, <code>Resent-Cc</code> and 552 * <code>Resent-Bcc</code>. 553 * 554 * @param fieldName 555 * the name of the mailbox field (<code>To</code>, 556 * <code>Cc</code>, <code>Bcc</code>, <code>Reply-To</code>, 557 * <code>Resent-To</code>, <code>Resent-Cc</code> or 558 * <code>Resent-Bcc</code>). 559 * @param addresses 560 * mailbox or group addresses for the field value. 561 * @return the newly created address-list field. 562 */ 563 public static AddressListField addressList(String fieldName, 564 Iterable<? extends Address> addresses) { 565 checkValidFieldName(fieldName); 566 return addressList0(fieldName, addresses); 567 } 568 569 private static DateTimeField date0(String fieldName, Date date, 570 TimeZone zone) { 571 final String formattedDate = MimeUtil.formatDate(date, zone); 572 return parse(DateTimeFieldImpl.PARSER, fieldName, formattedDate); 573 } 574 575 private static MailboxField mailbox0(String fieldName, Mailbox mailbox) { 576 String fieldValue = encodeAddresses(Collections.singleton(mailbox)); 577 return parse(MailboxFieldImpl.PARSER, fieldName, fieldValue); 578 } 579 580 private static MailboxListField mailboxList0(String fieldName, 581 Iterable<Mailbox> mailboxes) { 582 String fieldValue = encodeAddresses(mailboxes); 583 return parse(MailboxListFieldImpl.PARSER, fieldName, fieldValue); 584 } 585 586 private static AddressListField addressList0(String fieldName, 587 Iterable<? extends Address> addresses) { 588 String fieldValue = encodeAddresses(addresses); 589 return parse(AddressListFieldImpl.PARSER, fieldName, fieldValue); 590 } 591 592 private static void checkValidFieldName(String fieldName) { 593 if (!FIELD_NAME_PATTERN.matcher(fieldName).matches()) 594 throw new IllegalArgumentException("Invalid field name"); 595 } 596 597 private static boolean isValidMimeType(String mimeType) { 598 if (mimeType == null) 599 return false; 600 601 int idx = mimeType.indexOf('/'); 602 if (idx == -1) 603 return false; 604 605 String type = mimeType.substring(0, idx); 606 String subType = mimeType.substring(idx + 1); 607 return EncoderUtil.isToken(type) && EncoderUtil.isToken(subType); 608 } 609 610 private static boolean isValidDispositionType(String dispositionType) { 611 if (dispositionType == null) 612 return false; 613 614 return EncoderUtil.isToken(dispositionType); 615 } 616 617 private static <F extends ParsedField> F parse(FieldParser<F> parser, 618 String fieldName, String fieldBody) { 619 RawField rawField = new RawField(fieldName, fieldBody); 620 return parser.parse(rawField, DecodeMonitor.SILENT); 621 } 622 623 private static String encodeAddresses(Iterable<? extends Address> addresses) { 624 StringBuilder sb = new StringBuilder(); 625 626 for (Address address : addresses) { 627 if (sb.length() > 0) { 628 sb.append(", "); 629 } 630 AddressFormatter.DEFAULT.encode(sb, address); 631 } 632 return sb.toString(); 633 } 634 635}