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 020 package org.apache.james.mime4j.field; 021 022 import java.util.Arrays; 023 import java.util.Collections; 024 import java.util.Date; 025 import java.util.HashMap; 026 import java.util.Map; 027 import java.util.TimeZone; 028 import java.util.regex.Pattern; 029 030 import org.apache.james.mime4j.codec.EncoderUtil; 031 import org.apache.james.mime4j.field.address.Address; 032 import org.apache.james.mime4j.field.address.Mailbox; 033 import org.apache.james.mime4j.parser.Field; 034 import org.apache.james.mime4j.util.ByteSequence; 035 import org.apache.james.mime4j.util.ContentUtil; 036 import org.apache.james.mime4j.util.MimeUtil; 037 038 /** 039 * Factory for concrete {@link Field} instances. 040 */ 041 public class Fields { 042 043 private static final Pattern FIELD_NAME_PATTERN = Pattern 044 .compile("[\\x21-\\x39\\x3b-\\x7e]+"); 045 046 private Fields() { 047 } 048 049 /** 050 * Creates a <i>Content-Type</i> field from the specified raw field value. 051 * The specified string gets folded into a multiple-line representation if 052 * necessary but is otherwise taken as is. 053 * 054 * @param contentType 055 * raw content type containing a MIME type and optional 056 * parameters. 057 * @return the newly created <i>Content-Type</i> field. 058 */ 059 public static ContentTypeField contentType(String contentType) { 060 return parse(ContentTypeField.PARSER, FieldName.CONTENT_TYPE, 061 contentType); 062 } 063 064 /** 065 * Creates a <i>Content-Type</i> field from the specified MIME type and 066 * parameters. 067 * 068 * @param mimeType 069 * a MIME type (such as <code>"text/plain"</code> or 070 * <code>"application/octet-stream"</code>). 071 * @param parameters 072 * map containing content-type parameters such as 073 * <code>"boundary"</code>. 074 * @return the newly created <i>Content-Type</i> field. 075 */ 076 public static ContentTypeField contentType(String mimeType, 077 Map<String, String> parameters) { 078 if (!isValidMimeType(mimeType)) 079 throw new IllegalArgumentException(); 080 081 if (parameters == null || parameters.isEmpty()) { 082 return parse(ContentTypeField.PARSER, FieldName.CONTENT_TYPE, 083 mimeType); 084 } else { 085 StringBuilder sb = new StringBuilder(mimeType); 086 for (Map.Entry<String, String> entry : parameters.entrySet()) { 087 sb.append("; "); 088 sb.append(EncoderUtil.encodeHeaderParameter(entry.getKey(), 089 entry.getValue())); 090 } 091 String contentType = sb.toString(); 092 return contentType(contentType); 093 } 094 } 095 096 /** 097 * Creates a <i>Content-Transfer-Encoding</i> field from the specified raw 098 * field value. 099 * 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 }