diff --git a/README.md b/README.md index 305b409a6..c270ca48d 100644 --- a/README.md +++ b/README.md @@ -16,15 +16,21 @@ Simple Java Mail is available in Maven Central: org.codemonkey.simplejavamail simple-java-mail - 2.5.1 + 3.0.0 ``` ### Latest Progress ### +v3.0.0 + + * [#22](https://github.com/bbottema/simple-java-mail/issues/22): Added conversion to and from MimeMessage. You can now consume and produce MimeMessage objects with simple-java-mail + * [#28](https://github.com/bbottema/simple-java-mail/issues/28): Re-added improved email validation facility + * [#29](https://github.com/bbottema/simple-java-mail/issues/29): The package has been restructured for future maintenance, breaking backwards compatibility + v2.5.1 - * [#25](https://github.com/bbottema/simple-java-mail/issues/25): Added finally clausule that will always close socket properly in case of an exception + * [#25](https://github.com/bbottema/simple-java-mail/issues/25): Added finally clause that will always close socket properly in case of an exception v2.5 diff --git a/RELEASE.txt b/RELEASE.txt index 4803d733e..72203ab8f 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -3,11 +3,18 @@ https://github.com/bbottema/simple-java-mail/ org.codemonkey.simplejavamail simple-java-mail - 2.5.1 + 3.0.0 RELEASE NOTES Java Simple Mail +v3.0.0 + +- #22: Added conversion to and from MimeMessage. You can now consume and produce MimeMessage objects with simple-java-mail +- #28: Re-added improved email validation facility +- #29: The package has been restructured for future maintenance, breaking backwards compatibility + + v2.5.1 - #25: Added finally clausule that will always close socket properly in case of an exception @@ -18,7 +25,7 @@ v2.5 v2.4 -- #21: Builder API uses CC and BCC recepient types incorrectly +- #21: Builder API uses CC and BCC recipient types incorrectly v2.3 diff --git a/pom.xml b/pom.xml index b65c3fc38..d0c8aac91 100644 --- a/pom.xml +++ b/pom.xml @@ -68,6 +68,15 @@ + + org.apache.maven.plugins + maven-compiler-plugin + 3.5.1 + + 1.6 + 1.6 + + maven-release-plugin 2.5.2 diff --git a/src/main/java/MailTest.java b/src/main/java/MailTest.java index 384fc43f0..ce6fbd0a0 100644 --- a/src/main/java/MailTest.java +++ b/src/main/java/MailTest.java @@ -1,41 +1,51 @@ -import javax.mail.Message.RecipientType; - -import org.codemonkey.simplejavamail.Email; import org.codemonkey.simplejavamail.Mailer; import org.codemonkey.simplejavamail.TransportStrategy; +import org.codemonkey.simplejavamail.email.Email; + +import javax.mail.Message.RecipientType; +import javax.mail.MessagingException; +import javax.mail.Session; +import javax.mail.internet.MimeMessage; +import javax.mail.util.ByteArrayDataSource; +import java.io.IOException; +import java.util.Base64; +import java.util.Properties; /** * Demonstration program for the Simple Java Mail framework. - *

- * IMPORTANT:
- * This testclass was designed to run from the commandline (or by Ant) and expects some system properties to be present. See - * Readme.txt for instructions. Alternatively, you can assign the host, username and password a hard value and ignore the system - * properties altogether. - * + * * @author Benny Bottema */ public class MailTest { - /** - * Just run as Java application, but remember to set the JVM arguments first. - * - * @param args should be empty, this demo uses JVM arguments only (-Dhost=value etc.). - */ - public static void main(final String[] args) { - final Email email = new Email(); - email.setFromAddress("lollypop", "lol.pop@somemail.com"); - email.addRecipient("C.Cane", "candycane@candyshop.org", RecipientType.TO); - email.setText("We should meet up!"); - email.setTextHTML("We should meet up!"); - email.setSubject("hey"); - sendMail(email); - } + public static void main(final String[] args) throws IOException, MessagingException { + final Email emailNormal = new Email(); + emailNormal.setFromAddress("lollypop", "lol.pop@somemail.com"); + // don't forget to add your own address here -> + emailNormal.addRecipient("C.Cane", "candycane@candyshop.org", RecipientType.TO); + emailNormal.setText("We should meet up!"); + emailNormal.setTextHTML("We should meet up!"); + emailNormal.setSubject("hey"); + + // add two text files in different ways and a black thumbs up embedded image -> + emailNormal.addAttachment("dresscode.txt", new ByteArrayDataSource("Black Tie Optional", "text/plain")); + emailNormal.addAttachment("location.txt", "On the moon!".getBytes(), "text/plain"); + String base64String = "iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAABeElEQVRYw2NgoAAYGxu3GxkZ7TY1NZVloDcAWq4MxH+B+D8Qv3FwcOCgtwM6oJaDMTAUXOhmuYqKCjvQ0pdoDrCnmwNMTEwakC0H4u8GBgYC9Ap6DSD+iewAoIPm0ctyLqBlp9F8/x+YE4zpYT8T0LL16JYD8U26+B7oyz4sloPwenpYno3DchCeROsUbwa05A8eB3wB4kqgIxOAuArIng7EW4H4EhC/B+JXQLwDaI4ryZaDSjeg5mt4LCcFXyIn1fdSyXJQVt1OtMWGhoai0OD8T0W8GohZifE1PxD/o7LlsPLiFNAKRrwOABWptLAcqc6QGDAHQEOAYaAc8BNotsJAOgAUAosG1AFA/AtUoY3YEFhKMAvS2AE7iC1+WaG1H6gY3gzE36hUFJ8mqzbU1dUVBBqQBzTgIDQRkWo5qCZdpaenJ0Zx1aytrc0DDB0foIG1oAYKqC0IZK8D4n1AfA6IzwPxXpCFoGoZVEUDaRGGUTAKRgEeAAA2eGJC+ETCiAAAAABJRU5ErkJggg=="; + emailNormal.addEmbeddedImage("winksmiley", Base64.getDecoder().decode(base64String), "image/png"); + + // let's try producing and then consuming a MimeMessage -> + final MimeMessage mimeMessage = Mailer.produceMimeMessage(emailNormal, Session.getDefaultInstance(new Properties())); + final Email emailFromMimeMessage = new Email(mimeMessage); + + sendMail(emailNormal); + sendMail(emailFromMimeMessage); // should produce the exact same result as emailNormal! + } - private static void sendMail(final Email email) { - final String host = System.getProperty("host") != null ? System.getProperty("host") : ""; - final int port = System.getProperty("port") != null ? Integer.parseInt(System.getProperty("port")) : 25; - final String username = System.getProperty("username") != null ? System.getProperty("username") : ""; - final String password = System.getProperty("password") != null ? System.getProperty("password") : ""; - new Mailer(host, port, username, password, TransportStrategy.SMTP_SSL).sendMail(email); - } + private static void sendMail(final Email email) { + final String host = System.getProperty("host") != null ? System.getProperty("host") : ""; + final int port = System.getProperty("port") != null ? Integer.parseInt(System.getProperty("port")) : 25; + final String username = System.getProperty("username") != null ? System.getProperty("username") : ""; + final String password = System.getProperty("password") != null ? System.getProperty("password") : ""; + new Mailer(host, port, username, password, TransportStrategy.SMTP_SSL).sendMail(email); + } } \ No newline at end of file diff --git a/src/main/java/org/codemonkey/simplejavamail/MailException.java b/src/main/java/org/codemonkey/simplejavamail/MailException.java index 82afcde20..607c1bd86 100644 --- a/src/main/java/org/codemonkey/simplejavamail/MailException.java +++ b/src/main/java/org/codemonkey/simplejavamail/MailException.java @@ -21,13 +21,12 @@ public final class MailException extends RuntimeException { protected static final String MISSING_RECIPIENT = "Email is not valid: missing recipients"; protected static final String MISSING_SUBJECT = "Email is not valid: missing subject"; protected static final String MISSING_CONTENT = "Email is not valid: missing content body"; - protected static final String PARSE_MIMEMESSAGE_ERROR = "Error parsing MimeMessage: %s"; protected MailException(final String message) { super(message); } - protected MailException(final String message, final Exception cause) { + public MailException(final String message, final Exception cause) { super(message, cause); } } \ No newline at end of file diff --git a/src/main/java/org/codemonkey/simplejavamail/Mailer.java b/src/main/java/org/codemonkey/simplejavamail/Mailer.java index 34cd9788f..1d3d8610a 100644 --- a/src/main/java/org/codemonkey/simplejavamail/Mailer.java +++ b/src/main/java/org/codemonkey/simplejavamail/Mailer.java @@ -22,6 +22,11 @@ import javax.mail.internet.MimeMultipart; import javax.mail.internet.MimeUtility; +import org.codemonkey.simplejavamail.email.AttachmentResource; +import org.codemonkey.simplejavamail.email.Email; +import org.codemonkey.simplejavamail.email.Recipient; +import org.codemonkey.simplejavamail.util.EmailAddressValidationCriteria; +import org.codemonkey.simplejavamail.util.EmailValidationUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -68,7 +73,7 @@ */ public class Mailer { - private static final Logger logger = LoggerFactory.getLogger(Mailer.class); + private static final Logger LOGGER = LoggerFactory.getLogger(Mailer.class); /** * Encoding used for setting body text, email address, headers, reply-to fields etc. ({@value #CHARACTER_ENCODING}). @@ -159,7 +164,7 @@ public Mailer(final String host, final Integer port, final String username, fina */ protected Session createMailSession(final String host, final Integer port, final String username, final String password) { if (transportStrategy == null) { - logger.warn("Transport Strategy not set, using plain SMTP strategy instead!"); + LOGGER.warn("Transport Strategy not set, using plain SMTP strategy instead!"); transportStrategy = TransportStrategy.SMTP_PLAIN; } Properties props = transportStrategy.generateProperties(); @@ -167,7 +172,7 @@ protected Session createMailSession(final String host, final Integer port, final if (port != null) { props.put(transportStrategy.propertyNamePort(), String.valueOf(port)); } else { - // let JavaMail's Transport objects determine deault port base don the used protocol + // let JavaMail's Transport objects determine default port base don the used protocol } if (username != null) { @@ -205,8 +210,8 @@ public Mailer(final String host, final Integer port, final String username, fina * let us know why you are needing this on https://github.com/bbottema/simple-java-mail/issues. */ public Session getSession() { - logger.warn("Providing access to Session instance for emergency fall-back scenario. Please let us know why you need it."); - logger.warn("\t>https://github.com/bbottema/simple-java-mail/issues"); + LOGGER.warn("Providing access to Session instance for emergency fall-back scenario. Please let us know why you need it."); + LOGGER.warn("\t>https://github.com/bbottema/simple-java-mail/issues"); return session; } @@ -263,10 +268,10 @@ public final void sendMail(final Email email) transport.close(); } } catch (final UnsupportedEncodingException e) { - logger.error(e.getMessage(), e); + LOGGER.error(e.getMessage(), e); throw new MailException(String.format(MailException.INVALID_ENCODING, e.getMessage())); } catch (final MessagingException e) { - logger.error(e.getMessage(), e); + LOGGER.error(e.getMessage(), e); throw new MailException(String.format(MailException.GENERIC_ERROR, e.getMessage()), e); } } @@ -286,7 +291,7 @@ private void logSession(Session session, TransportStrategy transportStrategy) { } else { specifics = properties.toString(); } - logger.debug(String.format("starting mail session (%s)", specifics)); + LOGGER.debug(String.format("starting mail session (%s)", specifics)); } /** @@ -537,7 +542,7 @@ private static class MimeEmailMessageWrapper { multipartRelated.addBodyPart(contentAlternativeMessages); contentAlternativeMessages.setContent(multipartAlternativeMessages); } catch (final MessagingException e) { - logger.error(e.getMessage(), e); + LOGGER.error(e.getMessage(), e); throw new RuntimeException(e.getMessage(), e); } } diff --git a/src/main/java/org/codemonkey/simplejavamail/AttachmentResource.java b/src/main/java/org/codemonkey/simplejavamail/email/AttachmentResource.java similarity index 90% rename from src/main/java/org/codemonkey/simplejavamail/AttachmentResource.java rename to src/main/java/org/codemonkey/simplejavamail/email/AttachmentResource.java index 25626e2c8..1c4b4b939 100644 --- a/src/main/java/org/codemonkey/simplejavamail/AttachmentResource.java +++ b/src/main/java/org/codemonkey/simplejavamail/email/AttachmentResource.java @@ -1,50 +1,50 @@ -package org.codemonkey.simplejavamail; - -import javax.activation.DataSource; - -/** - * A named immutable email attachment information object. The name can be a simple name, a filename or a named embedded - * image (eg. <cid:footer>). Contains a {@link DataSource} that is compatible with the javax.mail API. - * - * @author Benny Bottema - * @see DataSource - */ -final class AttachmentResource { - - /** - * @see #AttachmentResource(String, DataSource) - */ - private final String name; - - /** - * @see #AttachmentResource(String, DataSource) - */ - private final DataSource dataSource; - - /** - * Constructor; initializes the attachment resource with a name and data. - * - * @param name The name of the attachment which can be a simple name, a filename or a named embedded image (eg. - * <cid:footer>) - * @param dataSource The attachment data. - * @see DataSource - */ - public AttachmentResource(final String name, final DataSource dataSource) { - this.name = name; - this.dataSource = dataSource; - } - - /** - * Bean getter for {@link #dataSource}. - */ - public DataSource getDataSource() { - return dataSource; - } - - /** - * Bean getter for {@link #name}. - */ - public String getName() { - return name; - } +package org.codemonkey.simplejavamail.email; + +import javax.activation.DataSource; + +/** + * A named immutable email attachment information object. The name can be a simple name, a filename or a named embedded + * image (eg. <cid:footer>). Contains a {@link DataSource} that is compatible with the javax.mail API. + * + * @author Benny Bottema + * @see DataSource + */ +public class AttachmentResource { + + /** + * @see #AttachmentResource(String, DataSource) + */ + private final String name; + + /** + * @see #AttachmentResource(String, DataSource) + */ + private final DataSource dataSource; + + /** + * Constructor; initializes the attachment resource with a name and data. + * + * @param name The name of the attachment which can be a simple name, a filename or a named embedded image (eg. + * <cid:footer>) + * @param dataSource The attachment data. + * @see DataSource + */ + public AttachmentResource(final String name, final DataSource dataSource) { + this.name = name; + this.dataSource = dataSource; + } + + /** + * Bean getter for {@link #dataSource}. + */ + public DataSource getDataSource() { + return dataSource; + } + + /** + * Bean getter for {@link #name}. + */ + public String getName() { + return name; + } } \ No newline at end of file diff --git a/src/main/java/org/codemonkey/simplejavamail/Email.java b/src/main/java/org/codemonkey/simplejavamail/email/Email.java similarity index 96% rename from src/main/java/org/codemonkey/simplejavamail/Email.java rename to src/main/java/org/codemonkey/simplejavamail/email/Email.java index cc212f820..79867e446 100644 --- a/src/main/java/org/codemonkey/simplejavamail/Email.java +++ b/src/main/java/org/codemonkey/simplejavamail/email/Email.java @@ -1,4 +1,7 @@ -package org.codemonkey.simplejavamail; +package org.codemonkey.simplejavamail.email; + +import org.codemonkey.simplejavamail.MailException; +import org.codemonkey.simplejavamail.util.MimeMessageParser; import java.io.IOException; import java.util.ArrayList; @@ -8,7 +11,6 @@ import java.util.Map; import javax.activation.DataSource; -import javax.mail.Address; import javax.mail.Message.RecipientType; import javax.mail.MessagingException; import javax.mail.internet.InternetAddress; @@ -305,8 +307,6 @@ public static class Builder { */ private final Map headers; - private Email email; - public Builder() { recipients = new ArrayList(); embeddedImages = new ArrayList(); @@ -529,19 +529,27 @@ private Email(Builder builder) { subject = builder.subject; } - /** + /* * Email from MimeMessage * * @author Benny Bottema */ + + private static final String PARSE_MIMEMESSAGE_ERROR = "Error parsing MimeMessage: %s"; + + /** + * Constructor for {@link javax.mail.internet.MimeMessage}. + * + * @param mimeMessage The MimeMessage from which to create the email. + */ public Email(MimeMessage mimeMessage) { this(); try { fillEmailFromMimeMessage(new MimeMessageParser(mimeMessage).parse()); } catch (MessagingException e) { - throw new MailException(String.format(MailException.PARSE_MIMEMESSAGE_ERROR, e.getMessage()), e); + throw new MailException(String.format(PARSE_MIMEMESSAGE_ERROR, e.getMessage()), e); } catch (IOException e) { - throw new MailException(String.format(MailException.PARSE_MIMEMESSAGE_ERROR, e.getMessage()), e); + throw new MailException(String.format(PARSE_MIMEMESSAGE_ERROR, e.getMessage()), e); } } diff --git a/src/main/java/org/codemonkey/simplejavamail/Recipient.java b/src/main/java/org/codemonkey/simplejavamail/email/Recipient.java similarity index 83% rename from src/main/java/org/codemonkey/simplejavamail/Recipient.java rename to src/main/java/org/codemonkey/simplejavamail/email/Recipient.java index 6775770b4..12b207534 100644 --- a/src/main/java/org/codemonkey/simplejavamail/Recipient.java +++ b/src/main/java/org/codemonkey/simplejavamail/email/Recipient.java @@ -1,60 +1,60 @@ -package org.codemonkey.simplejavamail; - -import javax.mail.Message.RecipientType; - -/** - * An immutable recipient object, with a name, emailadres and recipient type (eg {@link RecipientType#BCC}). - * - * @author Benny Bottema - */ -public final class Recipient { - - private final String name; - - private final String address; - - private final RecipientType type; - - /** - * Constructor; initializes this recipient object. - * - * @param name The name of the recipient. - * @param address The email address of the recipient. - * @param type The recipient type (eg. {@link RecipientType#TO}). - * @see RecipientType - */ - public Recipient(final String name, final String address, final RecipientType type) { - this.name = name; - this.address = address; - this.type = type; - } - - @Override public String toString() { - return "Recipient{" + - "name='" + name + '\'' + - ", address='" + address + '\'' + - ", type=" + type + - '}'; - } - - /** - * Bean getter for {@link #name}; - */ - public String getName() { - return name; - } - - /** - * Bean getter for {@link #address}; - */ - public String getAddress() { - return address; - } - - /** - * Bean getter for {@link #type}; - */ - public RecipientType getType() { - return type; - } +package org.codemonkey.simplejavamail.email; + +import javax.mail.Message.RecipientType; + +/** + * An immutable recipient object, with a name, emailaddress and recipient type (eg {@link RecipientType#BCC}). + * + * @author Benny Bottema + */ +public final class Recipient { + + private final String name; + + private final String address; + + private final RecipientType type; + + /** + * Constructor; initializes this recipient object. + * + * @param name The name of the recipient. + * @param address The email address of the recipient. + * @param type The recipient type (eg. {@link RecipientType#TO}). + * @see RecipientType + */ + public Recipient(final String name, final String address, final RecipientType type) { + this.name = name; + this.address = address; + this.type = type; + } + + @Override public String toString() { + return "Recipient{" + + "name='" + name + '\'' + + ", address='" + address + '\'' + + ", type=" + type + + '}'; + } + + /** + * Bean getter for {@link #name}; + */ + public String getName() { + return name; + } + + /** + * Bean getter for {@link #address}; + */ + public String getAddress() { + return address; + } + + /** + * Bean getter for {@link #type}; + */ + public RecipientType getType() { + return type; + } } \ No newline at end of file diff --git a/src/main/java/org/codemonkey/simplejavamail/EmailAddressValidationCriteria.java b/src/main/java/org/codemonkey/simplejavamail/util/EmailAddressValidationCriteria.java similarity index 96% rename from src/main/java/org/codemonkey/simplejavamail/EmailAddressValidationCriteria.java rename to src/main/java/org/codemonkey/simplejavamail/util/EmailAddressValidationCriteria.java index ff3dca1c5..db6425572 100644 --- a/src/main/java/org/codemonkey/simplejavamail/EmailAddressValidationCriteria.java +++ b/src/main/java/org/codemonkey/simplejavamail/util/EmailAddressValidationCriteria.java @@ -1,64 +1,64 @@ -package org.codemonkey.simplejavamail; - -/** - * Defines a set of restriction flags for email address validation. To remain completely true to RFC 2822, all flags should be set to - * true. - * - * @author Benny Bottema - * @see #EmailAddressValidationCriteria(boolean, boolean) - */ -public class EmailAddressValidationCriteria { - - private final boolean allowDomainLiterals; - private final boolean allowQuotedIdentifiers; - - /** - * Criteria which is most RFC 2822 compliant and allows all compiant address forms, including the more exotic ones. - * - * @see #EmailAddressValidationCriteria(boolean, boolean) - */ - public static final EmailAddressValidationCriteria RFC_COMPLIANT = new EmailAddressValidationCriteria(true, true); - - /** - * @param allowDomainLiterals

    - *
  • This flag states whether domain literals are allowed in the email address, e.g.: - *

    - * someone@[192.168.1.100] or
    - * john.doe@[23:33:A2:22:16:1F] or
    - * me@[my computer] - *

    - *

    - * The RFC says these are valid email addresses, but most people don't like allowing them. If you don't want to allow them, - * and only want to allow valid domain names (RFC 1035, x.y.z.com, etc), - * change this constant to false. - *

    - * Its default value is true to remain RFC 2822 compliant, but you should set it depending on what you need for your - * application.

  • - *
- * @param allowQuotedIdentifiers
    - *
  • This flag states whether quoted identifiers are allowed (using quotes and angle brackets around the raw address) are - * allowed, e.g.: - *

    - * "John Smith" <john.smith@somewhere.com> - *

    - * The RFC says this is a valid mailbox. If you don't want to allow this, because for example, you only want users to enter - * in a raw address (john.smith@somewhere.com - no quotes or angle brackets), then change this constant to - * false. - *

    - * Its default value is true to remain RFC 2822 compliant, but you should set it depending on what you need for your - * application.

  • - *
- */ - public EmailAddressValidationCriteria(boolean allowDomainLiterals, boolean allowQuotedIdentifiers) { - this.allowDomainLiterals = allowDomainLiterals; - this.allowQuotedIdentifiers = allowQuotedIdentifiers; - } - - public final boolean isAllowDomainLiterals() { - return allowDomainLiterals; - } - - public final boolean isAllowQuotedIdentifiers() { - return allowQuotedIdentifiers; - } +package org.codemonkey.simplejavamail.util; + +/** + * Defines a set of restriction flags for email address validation. To remain completely true to RFC 2822, all flags should be set to + * true. + * + * @author Benny Bottema + * @see #EmailAddressValidationCriteria(boolean, boolean) + */ +public class EmailAddressValidationCriteria { + + private final boolean allowDomainLiterals; + private final boolean allowQuotedIdentifiers; + + /** + * Criteria which is most RFC 2822 compliant and allows all compiant address forms, including the more exotic ones. + * + * @see #EmailAddressValidationCriteria(boolean, boolean) + */ + public static final EmailAddressValidationCriteria RFC_COMPLIANT = new EmailAddressValidationCriteria(true, true); + + /** + * @param allowDomainLiterals
    + *
  • This flag states whether domain literals are allowed in the email address, e.g.: + *

    + * someone@[192.168.1.100] or
    + * john.doe@[23:33:A2:22:16:1F] or
    + * me@[my computer] + *

    + *

    + * The RFC says these are valid email addresses, but most people don't like allowing them. If you don't want to allow them, + * and only want to allow valid domain names (RFC 1035, x.y.z.com, etc), + * change this constant to false. + *

    + * Its default value is true to remain RFC 2822 compliant, but you should set it depending on what you need for your + * application.

  • + *
+ * @param allowQuotedIdentifiers
    + *
  • This flag states whether quoted identifiers are allowed (using quotes and angle brackets around the raw address) are + * allowed, e.g.: + *

    + * "John Smith" <john.smith@somewhere.com> + *

    + * The RFC says this is a valid mailbox. If you don't want to allow this, because for example, you only want users to enter + * in a raw address (john.smith@somewhere.com - no quotes or angle brackets), then change this constant to + * false. + *

    + * Its default value is true to remain RFC 2822 compliant, but you should set it depending on what you need for your + * application.

  • + *
+ */ + public EmailAddressValidationCriteria(boolean allowDomainLiterals, boolean allowQuotedIdentifiers) { + this.allowDomainLiterals = allowDomainLiterals; + this.allowQuotedIdentifiers = allowQuotedIdentifiers; + } + + public final boolean isAllowDomainLiterals() { + return allowDomainLiterals; + } + + public final boolean isAllowQuotedIdentifiers() { + return allowQuotedIdentifiers; + } } \ No newline at end of file diff --git a/src/main/java/org/codemonkey/simplejavamail/EmailValidationUtil.java b/src/main/java/org/codemonkey/simplejavamail/util/EmailValidationUtil.java similarity index 97% rename from src/main/java/org/codemonkey/simplejavamail/EmailValidationUtil.java rename to src/main/java/org/codemonkey/simplejavamail/util/EmailValidationUtil.java index 026494d3f..cc368b543 100644 --- a/src/main/java/org/codemonkey/simplejavamail/EmailValidationUtil.java +++ b/src/main/java/org/codemonkey/simplejavamail/util/EmailValidationUtil.java @@ -1,107 +1,107 @@ -/* - * Copyright 2008 Les Hazlewood Licensed under the Apache License, Version 2.0 (the "License"); you may not use this - * file except in compliance with the License. You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific language governing permissions and limitations under the - * License. - */ -package org.codemonkey.simplejavamail; - -import java.util.regex.Pattern; - -/** - * Validates an email address according to RFC 2822, using regular expressions. - *

- * From the original author:
- *

If you use this code, please keep the author information in tact and reference my site at leshazlewood.com. Thanks!
- *

- * Code sanitized by Benny Bottema (kept validation 100% in tact). - * - * @author Les Hazlewood, Benny Bottema - * @see EmailAddressValidationCriteria - */ -public final class EmailValidationUtil { - - /** - * Private constructor; this is a utility class with static methods only, not designed for extension. - */ - private EmailValidationUtil() { - // - } - - /** - * Validates an e-mail with default validation flags that remains true to RFC 2822. This means allowing both domain - * literals and quoted identifiers. - * - * @param email A complete email address. - * @return Whether the e-mail address is compliant with RFC 2822. - * @see EmailAddressValidationCriteria#RFC_COMPLIANT - */ - public static boolean isValid(final String email) { - return isValid(email, EmailAddressValidationCriteria.RFC_COMPLIANT); - } - - /** - * Validates an e-mail with given validation flags. - * - * @param email A complete email address. - * @param emailAddressValidationCriteria A set of flags that restrict or relax RFC 2822 compliance. - * @return Whether the e-mail address is compliant with RFC 2822, configured using the passed in {@link EmailAddressValidationCriteria}. - * @see EmailAddressValidationCriteria#RFC_COMPLIANT - */ - public static boolean isValid(final String email, final EmailAddressValidationCriteria emailAddressValidationCriteria) { - return buildValidEmailPattern(emailAddressValidationCriteria).matcher(email).matches(); - } - - protected static Pattern buildValidEmailPattern(EmailAddressValidationCriteria parameterObject) { - // RFC 2822 2.2.2 Structured Header Field Bodies - final String wsp = "[ \\t]"; // space or tab - final String fwsp = wsp + "*"; - // RFC 2822 3.2.1 Primitive tokens - final String dquote = "\\\""; - // ASCII Control characters excluding white space: - final String noWsCtl = "\\x01-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F"; - // all ASCII characters except CR and LF: - final String asciiText = "[\\x01-\\x09\\x0B\\x0C\\x0E-\\x7F]"; - // RFC 2822 3.2.2 Quoted characters: - // single backslash followed by a text char - final String quotedPair = "(\\\\" + asciiText + ")"; - // RFC 2822 3.2.4 Atom: - final String atext = "[a-zA-Z0-9\\!\\#\\$\\%\\&\\'\\*\\+\\-\\/\\=\\?\\^\\_\\`\\{\\|\\}\\~]"; - final String atom = fwsp + atext + "+" + fwsp; - final String dotAtomText = atext + "+" + "(" + "\\." + atext + "+)*"; - final String dotAtom = fwsp + "(" + dotAtomText + ")" + fwsp; - // RFC 2822 3.2.5 Quoted strings: - // noWsCtl and the rest of ASCII except the doublequote and backslash characters: - final String qtext = "[" + noWsCtl + "\\x21\\x23-\\x5B\\x5D-\\x7E]"; - final String qcontent = "(" + qtext + "|" + quotedPair + ")"; - final String quotedString = dquote + "(" + fwsp + qcontent + ")*" + fwsp + dquote; - // RFC 2822 3.2.6 Miscellaneous tokens - final String word = "((" + atom + ")|(" + quotedString + "))"; - final String phrase = word + "+"; // one or more words. - // RFC 1035 tokens for domain names: - final String letter = "[a-zA-Z]"; - final String letDig = "[a-zA-Z0-9]"; - final String letDigHyp = "[a-zA-Z0-9-]"; - final String rfcLabel = letDig + "(" + letDigHyp + "{0,61}" + letDig + ")?"; - final String rfc1035DomainName = rfcLabel + "(\\." + rfcLabel + ")*\\." + letter + "{2,6}"; - // RFC 2822 3.4 Address specification - // domain text - non white space controls and the rest of ASCII chars not including [, ], or \: - final String dtext = "[" + noWsCtl + "\\x21-\\x5A\\x5E-\\x7E]"; - final String dcontent = dtext + "|" + quotedPair; - final String domainLiteral = "\\[" + "(" + fwsp + dcontent + "+)*" + fwsp + "\\]"; - final String rfc2822Domain = "(" + dotAtom + "|" + domainLiteral + ")"; - final String domain = parameterObject.isAllowDomainLiterals() ? rfc2822Domain : rfc1035DomainName; - final String localPart = "((" + dotAtom + ")|(" + quotedString + "))"; - final String addrSpec = localPart + "@" + domain; - final String angleAddr = "<" + addrSpec + ">"; - final String nameAddr = "(" + phrase + ")?" + fwsp + angleAddr; - final String mailbox = nameAddr + "|" + addrSpec; - // now compile a pattern for efficient re-use: - // if we're allowing quoted identifiers or not: - final String patternString = parameterObject.isAllowQuotedIdentifiers() ? mailbox : addrSpec; - return Pattern.compile(patternString); - } +/* + * Copyright 2008 Les Hazlewood Licensed under the Apache License, Version 2.0 (the "License"); you may not use this + * file except in compliance with the License. You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and limitations under the + * License. + */ +package org.codemonkey.simplejavamail.util; + +import java.util.regex.Pattern; + +/** + * Validates an email address according to RFC 2822, using regular expressions. + *

+ * From the original author:
+ *

If you use this code, please keep the author information in tact and reference my site at leshazlewood.com. Thanks!
+ *

+ * Code sanitized by Benny Bottema (kept validation 100% in tact). + * + * @author Les Hazlewood, Benny Bottema + * @see EmailAddressValidationCriteria + */ +public final class EmailValidationUtil { + + /** + * Private constructor; this is a utility class with static methods only, not designed for extension. + */ + private EmailValidationUtil() { + // + } + + /** + * Validates an e-mail with default validation flags that remains true to RFC 2822. This means allowing both domain + * literals and quoted identifiers. + * + * @param email A complete email address. + * @return Whether the e-mail address is compliant with RFC 2822. + * @see EmailAddressValidationCriteria#RFC_COMPLIANT + */ + public static boolean isValid(final String email) { + return isValid(email, EmailAddressValidationCriteria.RFC_COMPLIANT); + } + + /** + * Validates an e-mail with given validation flags. + * + * @param email A complete email address. + * @param emailAddressValidationCriteria A set of flags that restrict or relax RFC 2822 compliance. + * @return Whether the e-mail address is compliant with RFC 2822, configured using the passed in {@link EmailAddressValidationCriteria}. + * @see EmailAddressValidationCriteria#RFC_COMPLIANT + */ + public static boolean isValid(final String email, final EmailAddressValidationCriteria emailAddressValidationCriteria) { + return buildValidEmailPattern(emailAddressValidationCriteria).matcher(email).matches(); + } + + protected static Pattern buildValidEmailPattern(EmailAddressValidationCriteria parameterObject) { + // RFC 2822 2.2.2 Structured Header Field Bodies + final String wsp = "[ \\t]"; // space or tab + final String fwsp = wsp + "*"; + // RFC 2822 3.2.1 Primitive tokens + final String dquote = "\\\""; + // ASCII Control characters excluding white space: + final String noWsCtl = "\\x01-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F"; + // all ASCII characters except CR and LF: + final String asciiText = "[\\x01-\\x09\\x0B\\x0C\\x0E-\\x7F]"; + // RFC 2822 3.2.2 Quoted characters: + // single backslash followed by a text char + final String quotedPair = "(\\\\" + asciiText + ")"; + // RFC 2822 3.2.4 Atom: + final String atext = "[a-zA-Z0-9\\!\\#\\$\\%\\&\\'\\*\\+\\-\\/\\=\\?\\^\\_\\`\\{\\|\\}\\~]"; + final String atom = fwsp + atext + "+" + fwsp; + final String dotAtomText = atext + "+" + "(" + "\\." + atext + "+)*"; + final String dotAtom = fwsp + "(" + dotAtomText + ")" + fwsp; + // RFC 2822 3.2.5 Quoted strings: + // noWsCtl and the rest of ASCII except the doublequote and backslash characters: + final String qtext = "[" + noWsCtl + "\\x21\\x23-\\x5B\\x5D-\\x7E]"; + final String qcontent = "(" + qtext + "|" + quotedPair + ")"; + final String quotedString = dquote + "(" + fwsp + qcontent + ")*" + fwsp + dquote; + // RFC 2822 3.2.6 Miscellaneous tokens + final String word = "((" + atom + ")|(" + quotedString + "))"; + final String phrase = word + "+"; // one or more words. + // RFC 1035 tokens for domain names: + final String letter = "[a-zA-Z]"; + final String letDig = "[a-zA-Z0-9]"; + final String letDigHyp = "[a-zA-Z0-9-]"; + final String rfcLabel = letDig + "(" + letDigHyp + "{0,61}" + letDig + ")?"; + final String rfc1035DomainName = rfcLabel + "(\\." + rfcLabel + ")*\\." + letter + "{2,6}"; + // RFC 2822 3.4 Address specification + // domain text - non white space controls and the rest of ASCII chars not including [, ], or \: + final String dtext = "[" + noWsCtl + "\\x21-\\x5A\\x5E-\\x7E]"; + final String dcontent = dtext + "|" + quotedPair; + final String domainLiteral = "\\[" + "(" + fwsp + dcontent + "+)*" + fwsp + "\\]"; + final String rfc2822Domain = "(" + dotAtom + "|" + domainLiteral + ")"; + final String domain = parameterObject.isAllowDomainLiterals() ? rfc2822Domain : rfc1035DomainName; + final String localPart = "((" + dotAtom + ")|(" + quotedString + "))"; + final String addrSpec = localPart + "@" + domain; + final String angleAddr = "<" + addrSpec + ">"; + final String nameAddr = "(" + phrase + ")?" + fwsp + angleAddr; + final String mailbox = nameAddr + "|" + addrSpec; + // now compile a pattern for efficient re-use: + // if we're allowing quoted identifiers or not: + final String patternString = parameterObject.isAllowQuotedIdentifiers() ? mailbox : addrSpec; + return Pattern.compile(patternString); + } } \ No newline at end of file diff --git a/src/main/java/org/codemonkey/simplejavamail/MimeMessageParser.java b/src/main/java/org/codemonkey/simplejavamail/util/MimeMessageParser.java similarity index 77% rename from src/main/java/org/codemonkey/simplejavamail/MimeMessageParser.java rename to src/main/java/org/codemonkey/simplejavamail/util/MimeMessageParser.java index 9a8503f69..a677bdc3a 100644 --- a/src/main/java/org/codemonkey/simplejavamail/MimeMessageParser.java +++ b/src/main/java/org/codemonkey/simplejavamail/util/MimeMessageParser.java @@ -15,55 +15,32 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.codemonkey.simplejavamail; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +package org.codemonkey.simplejavamail.util; import javax.activation.DataHandler; import javax.activation.DataSource; -import javax.mail.Address; -import javax.mail.Header; -import javax.mail.Message; -import javax.mail.MessagingException; -import javax.mail.Multipart; -import javax.mail.Part; -import javax.mail.internet.ContentType; -import javax.mail.internet.InternetAddress; -import javax.mail.internet.InternetHeaders; -import javax.mail.internet.MimeBodyPart; -import javax.mail.internet.MimeMessage; -import javax.mail.internet.MimePart; -import javax.mail.internet.MimeUtility; -import javax.mail.internet.ParseException; +import javax.mail.*; +import javax.mail.internet.*; import javax.mail.util.ByteArrayDataSource; +import java.io.*; +import java.util.*; /** * heavily modified version based on org.apache.commons.mail.util.MimeMessageParser.html * Parses a MimeMessage and stores the individual parts such a plain text, * HTML text and attachments. * - * @version $Id: MimeMessageParser.java 1632031 2014-10-15 13:51:18Z ggregory $ - * @since 1.3 + * @version current: MimeMessageParser.java 2016-02-25 Benny Bottema */ -class MimeMessageParser { +public class MimeMessageParser { private static final List DEFAULT_HEADERS = new ArrayList(); static { // taken from: protected javax.mail.internet.InternetHeaders constructor + /* + * When extracting information to create an Email, we're not interested in the following headers: + */ DEFAULT_HEADERS.add("Return-Path"); DEFAULT_HEADERS.add("Received"); DEFAULT_HEADERS.add("Resent-Date"); @@ -94,7 +71,7 @@ class MimeMessageParser { DEFAULT_HEADERS.add(":"); DEFAULT_HEADERS.add("Content-Length"); DEFAULT_HEADERS.add("Status"); - // extra headers that may arrive from nested attachments, that should be ignored + // extra headers that should be ignored, which may originate from nested attachments DEFAULT_HEADERS.add("Content-Disposition"); DEFAULT_HEADERS.add("size"); DEFAULT_HEADERS.add("filename"); @@ -115,8 +92,6 @@ class MimeMessageParser { private String htmlContent; - private boolean isMultiPart; - /** * Constructs an instance with the MimeMessage to be extracted. * @@ -124,7 +99,6 @@ class MimeMessageParser { */ public MimeMessageParser(final MimeMessage message) { this.mimeMessage = message; - this.isMultiPart = false; } /** @@ -133,7 +107,7 @@ public MimeMessageParser(final MimeMessage message) { * @return this instance */ public MimeMessageParser parse() throws MessagingException, IOException { - this.parse(null, mimeMessage); + this.parse(mimeMessage); return this; } @@ -201,12 +175,11 @@ public String getSubject() throws MessagingException { /** * Extracts the content of a MimeMessage recursively. * - * @param parent the parent multi-part * @param part the current MimePart * @throws MessagingException parsing the MimeMessage failed * @throws IOException parsing the MimeMessage failed */ - protected void parse(final Multipart parent, final MimePart part) + protected void parse(final MimePart part) throws MessagingException, IOException { extractCustomUserHeaders(part); @@ -219,16 +192,15 @@ protected void parse(final Multipart parent, final MimePart part) htmlContent = (String) part.getContent(); } else { if (isMimeType(part, "multipart/*")) { - this.isMultiPart = true; final Multipart mp = (Multipart) part.getContent(); final int count = mp.getCount(); // iterate over all MimeBodyPart for (int i = 0; i < count; i++) { - parse(mp, (MimeBodyPart) mp.getBodyPart(i)); + parse((MimeBodyPart) mp.getBodyPart(i)); } } else { - final DataSource ds = createDataSource(parent, part); + final DataSource ds = createDataSource(part); if (Part.ATTACHMENT.equalsIgnoreCase(part.getDisposition())) { this.attachmentList.put(part.getFileName(), ds); } else if (Part.INLINE.equalsIgnoreCase(part.getDisposition())) { @@ -258,19 +230,6 @@ private boolean isCustomUserHeader(Header header) { return !DEFAULT_HEADERS.contains(header.getName()); } - /** - * Strips the content id of any whitespace and angle brackets. - * - * @param contentId the string to strip - * @return a stripped version of the content id - */ - private String stripContentId(final String contentId) { - if (contentId == null) { - return null; - } - return contentId.trim().replaceAll("[\\<\\>]", ""); - } - /** * Checks whether the MimePart contains an object of the given mime type. * @@ -296,13 +255,12 @@ private boolean isMimeType(final MimePart part, final String mimeType) /** * Parses the MimePart to create a DataSource. * - * @param parent the parent multi-part * @param part the current part to be processed * @return the DataSource * @throws MessagingException creating the DataSource failed * @throws IOException creating the DataSource failed */ - protected DataSource createDataSource(final Multipart parent, final MimePart part) + protected DataSource createDataSource(final MimePart part) throws MessagingException, IOException { final DataHandler dataHandler = part.getDataHandler(); final DataSource dataSource = dataHandler.getDataSource(); @@ -315,87 +273,6 @@ protected DataSource createDataSource(final Multipart parent, final MimePart par return result; } - /** - * @return Returns the mimeMessage. - */ - public MimeMessage getMimeMessage() { - return mimeMessage; - } - - /** - * @return Returns the isMultiPart. - */ - public boolean isMultipart() { - return isMultiPart; - } - - /** - * @return Returns the plainContent if any - */ - public String getPlainContent() { - return plainContent; - } - - /** - * @return Returns the attachmentList. - */ - public Map getAttachmentList() { - return attachmentList; - } - - /** - * Returns a collection of all content-ids in the parsed message. - *

- * The content-ids are stripped of any angle brackets, i.e. "part1" instead - * of "<part1>". - * - * @return the collection of content ids. - * @since 1.3.4 - */ - public Collection getContentIds() { - return Collections.unmodifiableSet(cidMap.keySet()); - } - - /** - * @return Returns the htmlContent if any - */ - public String getHtmlContent() { - return htmlContent; - } - - /** - * @return true if a plain content is available - */ - public boolean hasPlainContent() { - return this.plainContent != null; - } - - /** - * @return true if HTML content is available - */ - public boolean hasHtmlContent() { - return this.htmlContent != null; - } - - /** - * Find an attachment using its name. - * - * @param name the name of the attachment - * @return the corresponding datasource or null if nothing was found - */ - public DataSource findAttachmentByName(final String name) { - DataSource dataSource; - - for (int i = 0; i < getAttachmentList().size(); i++) { - dataSource = getAttachmentList().get(i); - if (name.equalsIgnoreCase(dataSource.getName())) { - return dataSource; - } - } - - return null; - } - /** * Determines the name of the data source if it is not already set. * @@ -463,11 +340,38 @@ private String getBaseMimeType(final String fullMimeType) { return fullMimeType; } + /** + * @return {@link #cidMap} + */ public Map getCidMap() { return cidMap; } + /** + * @return {@link #headers} + */ public Map getHeaders() { return headers; } + + /** + * @return {@link #plainContent} + */ + public String getPlainContent() { + return plainContent; + } + + /** + * @return {@link #attachmentList} + */ + public Map getAttachmentList() { + return attachmentList; + } + + /** + * @return {@link #htmlContent} + */ + public String getHtmlContent() { + return htmlContent; + } } \ No newline at end of file