Apache Commons logo Commons Email

CPD Results

The following document contains the results of PMD's CPD 7.2.0.

Duplications

File Project Line
org/apache/commons/mail2/jakarta/Email.java Apache Commons Email for Jakarta 60
org/apache/commons/mail2/javax/Email.java Apache Commons Email for Javax 59
public abstract class Email {

    /**
     * Empty array.
     */
    private static final InternetAddress[] EMPTY_INTERNET_ADDRESS_ARRAY = {};

    /**
     * The email message to send.
     */
    private MimeMessage message;

    /**
     * The charset to use for this message.
     */
    private String charset;

    /**
     * The Address of the sending party, mandatory.
     */
    private InternetAddress fromAddress;

    /**
     * The Subject.
     */
    private String subject;

    /**
     * An attachment.
     */
    private MimeMultipart emailBody;

    /**
     * The content.
     */
    private Object content;

    /**
     * The content type.
     */
    private String contentType;

    /**
     * Set session debugging on or off.
     */
    private boolean debug;

    /**
     * Sent date.
     */
    private Date sentDate;

    /**
     * Instance of an {@code Authenticator} object that will be used when authentication is requested from the mail server.
     */
    private Authenticator authenticator;

    /**
     * The hostname of the mail server with which to connect. If null will try to get property from system.properties. If still null, quit.
     */
    private String hostName;

    /**
     * The port number of the mail server to connect to. Defaults to the standard port ( 25 ).
     */
    private String smtpPort = "25";

    /**
     * The port number of the SSL enabled SMTP server; defaults to the standard port, 465.
     */
    private String sslSmtpPort = "465";

    /**
     * List of "to" email addresses.
     */
    private List<InternetAddress> toList = new ArrayList<>();

    /**
     * List of "cc" email addresses.
     */
    private List<InternetAddress> ccList = new ArrayList<>();

    /**
     * List of "bcc" email addresses.
     */
    private List<InternetAddress> bccList = new ArrayList<>();

    /**
     * List of "replyTo" email addresses.
     */
    private List<InternetAddress> replyList = new ArrayList<>();

    /**
     * Address to which undeliverable mail should be sent. Because this is handled by JavaMail as a String property in the mail session, this property is of
     * type {@code String} rather than {@code InternetAddress}.
     */
    private String bounceAddress;

    /**
     * Used to specify the mail headers. Example:
     *
     * X-Mailer: Sendmail, X-Priority: 1( highest ) or 2( high ) 3( normal ) 4( low ) and 5( lowest ) Disposition-Notification-To: user@domain.net
     */
    private final Map<String, String> headers = new HashMap<>();

    /**
     * Whether to use POP3 before SMTP, and if so the settings.
     */
    private boolean popBeforeSmtp;

    /**
     * The host name of the POP3 server.
     */
    private String popHost;

    /**
     * The user name to log into the POP3 server.
     */
    private String popUsername;

    /**
     * The password to log into the POP3 server.
     */
    private String popPassword;

    /**
     * Does server require TLS encryption for authentication?
     */
    private boolean tls;

    /**
     * Does the current transport use SSL/TLS encryption upon connection?
     */
    private boolean ssl;

    /**
     * Socket I/O timeout value in milliseconds.
     */
    private int socketTimeout = Math.toIntExact(EmailConstants.SOCKET_TIMEOUT.toMillis());

    /**
     * Socket connection timeout value in milliseconds.
     */
    private int socketConnectionTimeout = Math.toIntExact(EmailConstants.SOCKET_TIMEOUT.toMillis());

    /**
     * If true, enables the use of the STARTTLS command (if supported by the server) to switch the connection to a TLS-protected connection before issuing any
     * login commands. Note that an appropriate trust store must configured so that the client will trust the server's certificate. Defaults to false.
     */
    private boolean startTlsEnabled;

    /**
     * If true, requires the use of the STARTTLS command. If the server doesn't support the STARTTLS command, or the command fails, the connect method will
     * fail. Defaults to false.
     */
    private boolean startTlsRequired;

    /**
     * Does the current transport use SSL/TLS encryption upon connection?
     */
    private boolean sslOnConnect;

    /**
     * If set to true, check the server identity as specified by RFC 2595. These additional checks based on the content of the server's certificate are intended
     * to prevent man-in-the-middle attacks. Defaults to false.
     */
    private boolean sslCheckServerIdentity;

    /**
     * If set to true, and a message has some valid and some invalid addresses, send the message anyway, reporting the partial failure with a
     * SendFailedException. If set to false (the default), the message is not sent to any of the recipients if there is an invalid recipient address. Defaults
     * to false.
     */
    private boolean sendPartial;

    /**
     * The Session to mail with.
     */
    private Session session;

    /**
     * Constructs a new instance.
     */
    public Email() {
        // empty
    }

    /**
     * Adds a blind BCC recipient to the email. The email address will also be used as the personal name. The name will be encoded by the charset of
     * {@link #setCharset(String)}. If it is not set, it will be encoded using the Java platform's default charset (UTF-16) if it contains non-ASCII characters;
     * otherwise, it is used as is.
     *
     * @param email A String.
     * @return An Email.
     * @throws EmailException Indicates an invalid email address
     * @since 1.0
     */
    public Email addBcc(final String email) throws EmailException {
        return addBcc(email, null);
    }

    /**
     * Adds an array of blind BCC recipients to the email. The email addresses will also be used as the personal name. The names will be encoded by the charset
     * of {@link #setCharset(String)}. If it is not set, it will be encoded using the Java platform's default charset (UTF-16) if it contains non-ASCII
     * characters; otherwise, it is used as is.
     *
     * @param emails A String array.
     * @return An Email.
     * @throws EmailException Indicates an invalid email address
     * @since 1.3
     */
    public Email addBcc(final String... emails) throws EmailException {
        EmailException.checkNonEmpty(emails, () -> "BCC list invalid.");
        for (final String email : emails) {
            addBcc(email, null);
        }
        return this;
    }

    /**
     * Adds a blind BCC recipient to the email using the specified address and the specified personal name. The name will be encoded by the charset of
     * {@link #setCharset(String)}. If it is not set, it will be encoded using the Java platform's default charset (UTF-16) if it contains non-ASCII characters;
     * otherwise, it is used as is.
     *
     * @param email A String.
     * @param name  A String.
     * @return An Email.
     * @throws EmailException Indicates an invalid email address
     * @since 1.0
     */
    public Email addBcc(final String email, final String name) throws EmailException {
        return addBcc(email, name, charset);
    }

    /**
     * Adds a blind BCC recipient to the email using the specified address, personal name, and charset encoding for the name.
     *
     * @param email   A String.
     * @param name    A String.
     * @param charset The charset to encode the name with.
     * @return An Email.
     * @throws EmailException Indicates an invalid email address
     * @since 1.1
     */
    public Email addBcc(final String email, final String name, final String charset) throws EmailException {
        bccList.add(createInternetAddress(email, name, charset));
        return this;
    }

    /**
     * Adds a recipient CC to the email. The email address will also be used as the personal name. The name will be encoded by the charset of
     * {@link #setCharset(String)}. If it is not set, it will be encoded using the Java platform's default charset (UTF-16) if it contains non-ASCII characters;
     * otherwise, it is used as is.
     *
     * @param email A String.
     * @return An Email.
     * @throws EmailException Indicates an invalid email address.
     * @since 1.0
     */
    public Email addCc(final String email) throws EmailException {
        return addCc(email, null);
    }

    /**
     * Adds an array of CC recipients to the email. The email addresses will also be used as the personal name. The names will be encoded by the charset of
     * {@link #setCharset(String)}. If it is not set, it will be encoded using the Java platform's default charset (UTF-16) if it contains non-ASCII characters;
     * otherwise, it is used as is.
     *
     * @param emails A String array.
     * @return An Email.
     * @throws EmailException Indicates an invalid email address.
     * @since 1.3
     */
    public Email addCc(final String... emails) throws EmailException {
        EmailException.checkNonEmpty(emails, () -> "CC list invalid.");
        for (final String email : emails) {
            addCc(email, null);
        }
        return this;
    }

    /**
     * Adds a recipient CC to the email using the specified address and the specified personal name. The name will be encoded by the charset of
     * {@link #setCharset(String)}. If it is not set, it will be encoded using the Java platform's default charset (UTF-16) if it contains non-ASCII characters;
     * otherwise, it is used as is.
     *
     * @param email A String.
     * @param name  A String.
     * @return An Email.
     * @throws EmailException Indicates an invalid email address.
     * @since 1.0
     */
    public Email addCc(final String email, final String name) throws EmailException {
        return addCc(email, name, charset);
    }

    /**
     * Adds a recipient CC to the email using the specified address, personal name, and charset encoding for the name.
     *
     * @param email   A String.
     * @param name    A String.
     * @param charset The charset to encode the name with.
     * @return An Email.
     * @throws EmailException Indicates an invalid email address or charset.
     * @since 1.1
     */
    public Email addCc(final String email, final String name, final String charset) throws EmailException {
        ccList.add(createInternetAddress(email, name, charset));
        return this;
    }

    /**
     * Adds a header ( name, value ) to the headers Map.
     *
     * @param name  A String with the name.
     * @param value A String with the value.
     * @since 1.0
     * @throws IllegalArgumentException if either {@code name} or {@code value} is null or empty
     */
    public void addHeader(final String name, final String value) {
        if (EmailUtils.isEmpty(name)) {
            throw new IllegalArgumentException("name can not be null or empty");
        }
        if (EmailUtils.isEmpty(value)) {
            throw new IllegalArgumentException("value can not be null or empty");
        }
        headers.put(name, value);
    }

    /**
     * Adds a reply to address to the email. The email address will also be used as the personal name. The name will be encoded by the charset of
     * {@link #setCharset(String)}. If it is not set, it will be encoded using the Java platform's default charset (UTF-16) if it contains non-ASCII characters;
     * otherwise, it is used as is.
     *
     * @param email A String.
     * @return An Email.
     * @throws EmailException Indicates an invalid email address
     * @since 1.0
     */
    public Email addReplyTo(final String email) throws EmailException {
        return addReplyTo(email, null);
    }

    /**
     * Adds a reply to address to the email using the specified address and the specified personal name. The name will be encoded by the charset of
     * {@link #setCharset(String)}. If it is not set, it will be encoded using the Java platform's default charset (UTF-16) if it contains non-ASCII characters;
     * otherwise, it is used as is.
     *
     * @param email A String.
     * @param name  A String.
     * @return An Email.
     * @throws EmailException Indicates an invalid email address
     * @since 1.0
     */
    public Email addReplyTo(final String email, final String name) throws EmailException {
        return addReplyTo(email, name, charset);
    }

    /**
     * Adds a reply to address to the email using the specified address, personal name, and charset encoding for the name.
     *
     * @param email   A String.
     * @param name    A String.
     * @param charset The charset to encode the name with.
     * @return An Email.
     * @throws EmailException Indicates an invalid email address or charset.
     * @since 1.1
     */
    public Email addReplyTo(final String email, final String name, final String charset) throws EmailException {
        replyList.add(createInternetAddress(email, name, charset));
        return this;
    }

    /**
     * Adds a recipient TO to the email. The email address will also be used as the personal name. The name will be encoded by the charset of
     * {@link #setCharset(String)}. If it is not set, it will be encoded using the Java platform's default charset (UTF-16) if it contains non-ASCII characters;
     * otherwise, it is used as is.
     *
     * @param email A String.
     * @return An Email.
     * @throws EmailException Indicates an invalid email address.
     * @since 1.0
     */
    public Email addTo(final String email) throws EmailException {
        return addTo(email, null);
    }

    /**
     * Adds a list of TO recipients to the email. The email addresses will also be used as the personal names. The names will be encoded by the charset of
     * {@link #setCharset(String)}. If it is not set, it will be encoded using the Java platform's default charset (UTF-16) if it contains non-ASCII characters;
     * otherwise, it is used as is.
     *
     * @param emails A String array.
     * @return An Email.
     * @throws EmailException Indicates an invalid email address.
     * @since 1.3
     */
    public Email addTo(final String... emails) throws EmailException {
        EmailException.checkNonEmpty(emails, () -> "To list invalid.");
        for (final String email : emails) {
            addTo(email, null);
        }
        return this;
    }

    /**
     * Adds a recipient TO to the email using the specified address and the specified personal name. The name will be encoded by the charset of
     * {@link #setCharset(String)}. If it is not set, it will be encoded using the Java platform's default charset (UTF-16) if it contains non-ASCII characters;
     * otherwise, it is used as is.
     *
     * @param email A String.
     * @param name  A String.
     * @return An Email.
     * @throws EmailException Indicates an invalid email address.
     * @since 1.0
     */
    public Email addTo(final String email, final String name) throws EmailException {
        return addTo(email, name, charset);
    }

    /**
     * Adds a recipient TO to the email using the specified address, personal name, and charset encoding for the name.
     *
     * @param email   A String.
     * @param name    A String.
     * @param charset The charset to encode the name with.
     * @return An Email.
     * @throws EmailException Indicates an invalid email address or charset.
     * @since 1.1
     */
    public Email addTo(final String email, final String name, final String charset) throws EmailException {
        toList.add(createInternetAddress(email, name, charset));
        return this;
    }

    /**
     * Builds the MimeMessage. Please note that a user rarely calls this method directly and only if he/she is interested in the sending the underlying
     * MimeMessage without commons-email.
     *
     * @throws IllegalStateException if the MimeMessage was already built
     * @throws EmailException        if there was an error.
     * @since 1.0
     */
    public void buildMimeMessage() throws EmailException {
        if (message != null) {
            // [EMAIL-95] we assume that an email is not reused therefore invoking
            // buildMimeMessage() more than once is illegal.
            throw new IllegalStateException("The MimeMessage is already built.");
        }

        try {
            message = createMimeMessage(getMailSession());

            if (EmailUtils.isNotEmpty(subject)) {
                if (EmailUtils.isNotEmpty(charset)) {
                    message.setSubject(subject, charset);
                } else {
                    message.setSubject(subject);
                }
            }

            // update content type (and encoding)
            updateContentType(contentType);

            if (content != null) {
                if (EmailConstants.TEXT_PLAIN.equalsIgnoreCase(contentType) && content instanceof String) {
                    // EMAIL-104: call explicitly setText to use default mime charset
                    // (property "mail.mime.charset") in case none has been set
                    message.setText(content.toString(), charset);
                } else {
                    message.setContent(content, contentType);
                }
            } else if (emailBody != null) {
                if (contentType == null) {
                    message.setContent(emailBody);
                } else {
                    message.setContent(emailBody, contentType);
                }
            } else {
                message.setText("");
            }

            if (fromAddress != null) {
                message.setFrom(fromAddress);
            } else if (session.getProperty(EmailConstants.MAIL_SMTP_FROM) == null && session.getProperty(EmailConstants.MAIL_FROM) == null) {
                throw new EmailException("From address required");
            }

            if (toList.size() + ccList.size() + bccList.size() == 0) {
                throw new EmailException("At least one receiver address required");
            }

            if (!EmailUtils.isEmpty(toList)) {
                message.setRecipients(Message.RecipientType.TO, toInternetAddressArray(toList));
            }

            if (!EmailUtils.isEmpty(ccList)) {
                message.setRecipients(Message.RecipientType.CC, toInternetAddressArray(ccList));
            }

            if (!EmailUtils.isEmpty(bccList)) {
                message.setRecipients(Message.RecipientType.BCC, toInternetAddressArray(bccList));
            }

            if (!EmailUtils.isEmpty(replyList)) {
                message.setReplyTo(toInternetAddressArray(replyList));
            }

            if (!EmailUtils.isEmpty(headers)) {
                for (final Map.Entry<String, String> entry : headers.entrySet()) {
                    final String foldedValue = createFoldedHeaderValue(entry.getKey(), entry.getValue());
                    message.addHeader(entry.getKey(), foldedValue);
                }
            }

            if (message.getSentDate() == null) {
                message.setSentDate(getSentDate());
            }

            if (popBeforeSmtp) {
                // TODO Why is this not a Store leak? When to close?
                final Store store = session.getStore("pop3");
                store.connect(popHost, popUsername, popPassword);
            }
        } catch (final MessagingException e) {
            throw new EmailException(e);
        }
    }

    /**
     * When a mail session is already initialized setting the session properties has no effect. In order to flag the problem throw an IllegalStateException.
     *
     * @throws IllegalStateException when the mail session is already initialized
     */
    private void checkSessionAlreadyInitialized() {
        if (session != null) {
            throw new IllegalStateException("The mail session is already initialized");
        }
    }

    /**
     * Creates a folded header value containing 76 character chunks.
     *
     * @param name  the name of the header
     * @param value the value of the header
     * @return the folded header value
     * @throws IllegalArgumentException if either the name or value is null or empty
     */
    private String createFoldedHeaderValue(final String name, final String value) {
        if (EmailUtils.isEmpty(name)) {
            throw new IllegalArgumentException("name can not be null or empty");
        }
        if (EmailUtils.isEmpty(value)) {
            throw new IllegalArgumentException("value can not be null or empty");
        }
        try {
            return MimeUtility.fold(name.length() + 2, MimeUtility.encodeText(value, charset, null));
        } catch (final UnsupportedEncodingException e) {
            return value;
        }
    }

    /**
     * Creates an InternetAddress.
     *
     * @param email       An email address.
     * @param name        A name.
     * @param charsetName The name of the charset to encode the name with.
     * @return An internet address.
     * @throws EmailException Thrown when the supplied address, name or charset were invalid.
     */
    private InternetAddress createInternetAddress(final String email, final String name, final String charsetName) throws EmailException {
        try {
            final InternetAddress address = new InternetAddress(new IDNEmailAddressConverter().toASCII(email));
            // check name input
            if (EmailUtils.isNotEmpty(name)) {
                // check charset input.
                if (EmailUtils.isEmpty(charsetName)) {
                    address.setPersonal(name);
                } else {
                    // canonicalize the charset name and make sure
                    // the current platform supports it.
                    final Charset set = Charset.forName(charsetName);
                    address.setPersonal(name, set.name());
                }
            }
            // run sanity check on new InternetAddress object; if this fails
            // it will throw AddressException.
            address.validate();
            return address;
        } catch (final AddressException | UnsupportedEncodingException e) {
            throw new EmailException(e);
        }
    }

    /**
     * Creates a customized MimeMessage which can be implemented by a derived class, e.g. to set the message id.
     *
     * @param aSession mail session to be used
     * @return the newly created message
     */
    protected MimeMessage createMimeMessage(final Session aSession) {
        return new MimeMessage(aSession);
    }

    /**
     * Gets the authenticator.
     *
     * @return the authenticator.
     * @since 1.6.0
     */
    public Authenticator getAuthenticator() {
        return authenticator;
    }

    /**
     * Gets the list of "Bcc" addresses.
     *
     * @return List addresses
     */
    public List<InternetAddress> getBccAddresses() {
        return bccList;
    }

    /**
     * Gets the "bounce address" of this email.
     *
     * @return the bounce address as string
     * @since 1.4
     */
    public String getBounceAddress() {
        return bounceAddress;
    }

    /**
     * Gets the list of "CC" addresses.
     *
     * @return List addresses
     */
    public List<InternetAddress> getCcAddresses() {
        return ccList;
    }

    /**
     * Gets the Charset.
     *
     * @return the Charset.
     * @since 1.6.0
     */
    public String getCharsetName() {
        return charset;
    }

    /**
     * Gets the content.
     *
     * @return the content.
     * @since 1.6.0
     */
    public Object getContent() {
        return content;
    }

    /**
     * Gets the content type.
     *
     * @return the content type.
     * @since 1.6.0
     */
    public String getContentType() {
        return contentType;
    }

    /**
     * Gets the email body.
     *
     * @return the email body.
     * @since 1.6.0
     */
    public MimeMultipart getEmailBody() {
        return emailBody;
    }

    /**
     * Gets the sender of the email.
     *
     * @return from address
     */
    public InternetAddress getFromAddress() {
        return fromAddress;
    }

    /**
     * Gets the specified header.
     *
     * @param header A string with the header.
     * @return The value of the header, or null if no such header.
     * @since 1.5
     */
    public String getHeader(final String header) {
        return headers.get(header);
    }

    /**
     * Gets all headers on an Email.
     *
     * @return a Map of all headers.
     * @since 1.5
     */
    public Map<String, String> getHeaders() {
        return headers;
    }

    /**
     * Gets the host name of the SMTP server,
     *
     * @return host name
     */
    public String getHostName() {
        if (session != null) {
            return session.getProperty(EmailConstants.MAIL_HOST);
        }
        if (EmailUtils.isNotEmpty(hostName)) {
            return hostName;
        }
        return null;
    }

    /**
     * Gets the mail session used when sending this Email, creating the Session if necessary. When a mail session is already initialized setting the session
     * related properties will cause an IllegalStateException.
     *
     * @return A Session.
     * @throws EmailException if the host name was not set
     * @since 1.0
     */
    public Session getMailSession() throws EmailException {
        if (session == null) {
            final Properties properties = new Properties(System.getProperties());
            properties.setProperty(EmailConstants.MAIL_TRANSPORT_PROTOCOL, EmailConstants.SMTP);

            if (EmailUtils.isEmpty(hostName)) {
                hostName = properties.getProperty(EmailConstants.MAIL_HOST);
            }

            EmailException.checkNonEmpty(hostName, () -> "Cannot find valid hostname for mail session");

            properties.setProperty(EmailConstants.MAIL_PORT, smtpPort);
            properties.setProperty(EmailConstants.MAIL_HOST, hostName);
            properties.setProperty(EmailConstants.MAIL_DEBUG, String.valueOf(debug));

            properties.setProperty(EmailConstants.MAIL_TRANSPORT_STARTTLS_ENABLE, Boolean.toString(isStartTLSEnabled()));
            properties.setProperty(EmailConstants.MAIL_TRANSPORT_STARTTLS_REQUIRED, Boolean.toString(isStartTLSRequired()));

            properties.setProperty(EmailConstants.MAIL_SMTP_SEND_PARTIAL, Boolean.toString(isSendPartial()));
            properties.setProperty(EmailConstants.MAIL_SMTPS_SEND_PARTIAL, Boolean.toString(isSendPartial()));

            if (authenticator != null) {
                properties.setProperty(EmailConstants.MAIL_SMTP_AUTH, "true");
            }

            if (isSSLOnConnect()) {
                properties.setProperty(EmailConstants.MAIL_PORT, sslSmtpPort);
                properties.setProperty(EmailConstants.MAIL_SMTP_SOCKET_FACTORY_PORT, sslSmtpPort);
                properties.setProperty(EmailConstants.MAIL_SMTP_SOCKET_FACTORY_CLASS, "javax.net.ssl.SSLSocketFactory");
                properties.setProperty(EmailConstants.MAIL_SMTP_SOCKET_FACTORY_FALLBACK, "false");
            }

            if ((isSSLOnConnect() || isStartTLSEnabled()) && isSSLCheckServerIdentity()) {
                properties.setProperty(EmailConstants.MAIL_SMTP_SSL_CHECKSERVERIDENTITY, "true");
            }

            if (bounceAddress != null) {
                properties.setProperty(EmailConstants.MAIL_SMTP_FROM, bounceAddress);
            }

            if (socketTimeout > 0) {
                properties.setProperty(EmailConstants.MAIL_SMTP_TIMEOUT, Integer.toString(socketTimeout));
            }

            if (socketConnectionTimeout > 0) {
                properties.setProperty(EmailConstants.MAIL_SMTP_CONNECTIONTIMEOUT, Integer.toString(socketConnectionTimeout));
            }

            // changed this (back) to getInstance due to security exceptions
            // caused when testing using Maven
            session = Session.getInstance(properties, authenticator);
        }
        return session;
    }

    /**
     * Gets the message.
     *
     * @return the message.
     * @since 1.6.0
     */
    public MimeMessage getMessage() {
        return message;
    }

    /**
     * Gets the internal MimeMessage. Please note that the MimeMessage is built by the buildMimeMessage() method.
     *
     * @return the MimeMessage
     */
    public MimeMessage getMimeMessage() {
        return message;
    }

    /**
     * Gets the POP3 host.
     *
     * @return the POP3 host.
     * @since 1.6.0
     */
    public String getPopHost() {
        return popHost;
    }

    /**
     * Gets the POP3 password.
     *
     * @return the POP3 password.
     * @since 1.6.0
     */
    public String getPopPassword() {
        return popPassword;
    }

    /**
     * Gets the POP3 user name.
     *
     * @return the POP3 user name.
     * @since 1.6.0
     */
    public String getPopUserName() {
        return popUsername;
    }

    /**
     * Gets the list of "Reply-To" addresses.
     *
     * @return List addresses
     */
    public List<InternetAddress> getReplyToAddresses() {
        return replyList;
    }

    /**
     * Gets the sent date for the email.
     *
     * @return date to be used as the sent date for the email
     * @since 1.0
     */
    public Date getSentDate() {
        if (sentDate == null) {
            return new Date();
        }
        return new Date(sentDate.getTime());
    }

    /**
     * Gets the listening port of the SMTP server.
     *
     * @return SMTP port
     */
    public String getSmtpPort() {
        if (session != null) {
            return session.getProperty(EmailConstants.MAIL_PORT);
        }
        if (EmailUtils.isNotEmpty(smtpPort)) {
            return smtpPort;
        }
        return null;
    }

    /**
     * Gets the socket connection timeout value in milliseconds.
     *
     * @return the timeout in milliseconds.
     * @since 1.2
     */
    public int getSocketConnectionTimeout() {
        return socketConnectionTimeout;
    }

    /**
     * Gets the socket I/O timeout value in milliseconds.
     *
     * @return the socket I/O timeout
     * @since 1.2
     */
    public int getSocketTimeout() {
        return socketTimeout;
    }

    /**
     * Gets the current SSL port used by the SMTP transport.
     *
     * @return the current SSL port used by the SMTP transport
     */
    public String getSslSmtpPort() {
        if (session != null) {
            return session.getProperty(EmailConstants.MAIL_SMTP_SOCKET_FACTORY_PORT);
        }
        if (EmailUtils.isNotEmpty(sslSmtpPort)) {
            return sslSmtpPort;
        }
        return null;
    }

    /**
     * Gets the subject of the email.
     *
     * @return email subject
     */
    public String getSubject() {
        return subject;
    }

    /**
     * Gets the list of "To" addresses.
     *
     * @return List addresses
     */
    public List<InternetAddress> getToAddresses() {
        return toList;
    }

    /**
     * Tests whether debug is on.
     *
     * @return whether debug is on.
     * @since 1.6.0
     */
    public boolean isDebug() {
        return debug;
    }

    /**
     * Tests whether to use POP3 before SMTP, and if so the settings.
     *
     * @return whether to use POP3 before SMTP, and if so the settings.
     * @since 1.6.0
     */
    public boolean isPopBeforeSmtp() {
        return popBeforeSmtp;
    }

    /**
     * Tests whether partial sending of email is enabled.
     *
     * @return true if sending partial email is enabled.
     * @since 1.3.2
     */
    public boolean isSendPartial() {
        return sendPartial;
    }

    /**
     * Tests whether the server identity checked as specified by RFC 2595
     *
     * @return true if the server identity is checked.
     * @since 1.3
     */
    public boolean isSSLCheckServerIdentity() {
        return sslCheckServerIdentity;
    }

    /**
     * Tests whether SSL/TLS encryption for the transport is currently enabled (SMTPS/POPS).
     *
     * @return true if SSL enabled for the transport.
     * @since 1.3
     */
    public boolean isSSLOnConnect() {
        return sslOnConnect || ssl;
    }

    /**
     * Tests whether the client is configured to try to enable STARTTLS.
     *
     * @return true if using STARTTLS for authentication, false otherwise.
     * @since 1.3
     */
    public boolean isStartTLSEnabled() {
        return startTlsEnabled || tls;
    }

    /**
     * Tests whether the client is configured to require STARTTLS.
     *
     * @return true if using STARTTLS for authentication, false otherwise.
     * @since 1.3
     */
    public boolean isStartTLSRequired() {
        return startTlsRequired;
    }

    /**
     * Sends the email. Internally we build a MimeMessage which is afterwards sent to the SMTP server.
     *
     * @return the message id of the underlying MimeMessage
     * @throws IllegalStateException if the MimeMessage was already built, that is, {@link #buildMimeMessage()} was already called
     * @throws EmailException        the sending failed
     */
    public String send() throws EmailException {
        buildMimeMessage();
        return sendMimeMessage();
    }

    /**
     * Sends the previously created MimeMessage to the SMTP server.
     *
     * @return the message id of the underlying MimeMessage
     * @throws IllegalArgumentException if the MimeMessage has not been created
     * @throws EmailException           the sending failed
     */
    public String sendMimeMessage() throws EmailException {
        Objects.requireNonNull(message, "MimeMessage has not been created yet");
        try {
            Transport.send(message);
            return message.getMessageID();
        } catch (final Throwable t) {
            throw new EmailException("Sending the email to the following server failed : " + this.getHostName() + ":" + getSmtpPort(), t);
        }
    }

    /**
     * Sets the userName and password if authentication is needed. If this method is not used, no authentication will be performed.
     * <p>
     * This method will create a new instance of {@code DefaultAuthenticator} using the supplied parameters.
     * </p>
     *
     * @param userName User name for the SMTP server
     * @param password password for the SMTP server
     * @see DefaultAuthenticator
     * @see #setAuthenticator
     * @since 1.0
     */
    public void setAuthentication(final String userName, final String password) {
        this.setAuthenticator(new DefaultAuthenticator(userName, password));
    }

    /**
     * Sets the {@code Authenticator} to be used when authentication is requested from the mail server.
     * <p>
     * This method should be used when your outgoing mail server requires authentication. Your mail server must also support RFC2554.
     * </p>
     *
     * @param authenticator the {@code Authenticator} object.
     * @see Authenticator
     * @since 1.0
     */
    public void setAuthenticator(final Authenticator authenticator) {
        this.authenticator = authenticator;
    }

    /**
     * Sets a list of "BCC" addresses. All elements in the specified {@code Collection} are expected to be of type {@code java.mail.internet.InternetAddress}.
     *
     * @param collection collection of {@code InternetAddress} objects
     * @return An Email.
     * @throws EmailException Indicates an invalid email address
     * @see jakarta.mail.internet.InternetAddress
     * @since 1.0
     */
    public Email setBcc(final Collection<InternetAddress> collection) throws EmailException {
        EmailException.checkNonEmpty(collection, () -> "BCC list invalid");
        bccList = new ArrayList<>(collection);
        return this;
    }

    /**
     * Sets the "bounce address" - the address to which undeliverable messages will be returned. If this value is never set, then the message will be sent to
     * the address specified with the System property "mail.smtp.from", or if that value is not set, then to the "from" address.
     *
     * @param email A String.
     * @return An Email.
     * @throws IllegalStateException if the mail session is already initialized
     * @since 1.0
     */
    public Email setBounceAddress(final String email) {
        checkSessionAlreadyInitialized();
        if (!EmailUtils.isEmpty(email)) {
            try {
                bounceAddress = createInternetAddress(email, null, charset).getAddress();
            } catch (final EmailException e) {
                // Can't throw 'EmailException' to keep backward-compatibility
                throw new IllegalArgumentException("Failed to set the bounce address : " + email, e);
            }
        } else {
            bounceAddress = email;
        }

        return this;
    }

    /**
     * Sets a list of "CC" addresses. All elements in the specified {@code Collection} are expected to be of type {@code java.mail.internet.InternetAddress}.
     *
     * @param collection collection of {@code InternetAddress} objects.
     * @return An Email.
     * @throws EmailException Indicates an invalid email address.
     * @see jakarta.mail.internet.InternetAddress
     * @since 1.0
     */
    public Email setCc(final Collection<InternetAddress> collection) throws EmailException {
        EmailException.checkNonEmpty(collection, () -> "CC list invalid");
        ccList = new ArrayList<>(collection);
        return this;
    }

    /**
     * Sets the charset of the message. Please note that you should set the charset before adding the message content.
     *
     * @param charset A String.
     * @throws java.nio.charset.IllegalCharsetNameException if the charset name is invalid
     * @throws java.nio.charset.UnsupportedCharsetException if no support for the named charset exists in the current JVM
     * @since 1.0
     */
    public void setCharset(final String charset) {
        final Charset set = Charset.forName(charset);
        this.charset = set.name();
    }

    /**
     * Sets the emailBody to a MimeMultiPart
     *
     * @param mimeMultipart aMimeMultipart
     * @since 1.0
     */
    public void setContent(final MimeMultipart mimeMultipart) {
        this.emailBody = mimeMultipart;
    }

    /**
     * Sets the content.
     *
     * @param content the content.
     * @return {@code this} instance.
     * @since 1.6.0
     */
    public Email setContent(final Object content) {
        this.content = content;
        return this;
    }

    /**
     * Sets the content and contentType.
     *
     * @param content     content.
     * @param contentType content type.
     * @since 1.0
     */
    public void setContent(final Object content, final String contentType) {
        this.content = content;
        updateContentType(contentType);
    }

    /**
     * Sets the content type.
     *
     * @param contentType the content type.
     * @return {@code this} instance.
     * @since 1.6.0
     */
    public Email setContentType(final String contentType) {
        this.contentType = contentType;
        return this;
    }

    /**
     * Sets the display of debug information.
     *
     * @param debug A boolean.
     * @since 1.0
     */
    public void setDebug(final boolean debug) {
        this.debug = debug;
    }

    /**
     * Sets the FROM field of the email to use the specified address. The email address will also be used as the personal name. The name will be encoded by the
     * charset of {@link #setCharset(String)}. If it is not set, it will be encoded using the Java platform's default charset (UTF-16) if it contains non-ASCII
     * characters; otherwise, it is used as is.
     *
     * @param email A String.
     * @return An Email.
     * @throws EmailException Indicates an invalid email address.
     * @since 1.0
     */
    public Email setFrom(final String email) throws EmailException {
        return setFrom(email, null);
    }

    /**
     * Sets the FROM field of the email to use the specified address and the specified personal name. The name will be encoded by the charset of
     * {@link #setCharset(String)}. If it is not set, it will be encoded using the Java platform's default charset (UTF-16) if it contains non-ASCII characters;
     * otherwise, it is used as is.
     *
     * @param email A String.
     * @param name  A String.
     * @return An Email.
     * @throws EmailException Indicates an invalid email address.
     * @since 1.0
     */
    public Email setFrom(final String email, final String name) throws EmailException {
        return setFrom(email, name, charset);
    }

    /**
     * Sets the FROM field of the email to use the specified address, personal name, and charset encoding for the name.
     *
     * @param email   A String.
     * @param name    A String.
     * @param charset The charset to encode the name with.
     * @return An Email.
     * @throws EmailException Indicates an invalid email address or charset.
     * @since 1.1
     */
    public Email setFrom(final String email, final String name, final String charset) throws EmailException {
        fromAddress = createInternetAddress(email, name, charset);
        return this;
    }

    /**
     * Sets the From address.
     *
     * @param fromAddress the From address.
     * @return {@code this} instance.
     * @since 1.6.0
     */
    public Email setFromAddress(final InternetAddress fromAddress) {
        this.fromAddress = fromAddress;
        return this;

    }

    /**
     * Sets the mail headers. Example:
     *
     * X-Mailer: Sendmail, X-Priority: 1( highest ) or 2( high ) 3( normal ) 4( low ) and 5( lowest ) Disposition-Notification-To: user@domain.net
     *
     * @param map A Map.
     * @throws IllegalArgumentException if either of the provided header / value is null or empty
     * @since 1.0
     */
    public void setHeaders(final Map<String, String> map) {
        headers.clear();
        for (final Map.Entry<String, String> entry : map.entrySet()) {
            addHeader(entry.getKey(), entry.getValue());
        }
    }

    /**
     * Sets the hostname of the outgoing mail server.
     *
     * @param hostName aHostName
     * @throws IllegalStateException if the mail session is already initialized
     * @since 1.0
     */
    public void setHostName(final String hostName) {
        checkSessionAlreadyInitialized();
        this.hostName = hostName;
    }

    /**
     * Sets a mail Session object to use. Please note that passing a user name and password (in the case of mail authentication) will create a new mail session
     * with a DefaultAuthenticator. This is a convenience but might come unexpected.
     *
     * If mail authentication is used but NO user name and password is supplied the implementation assumes that you have set a authenticator and will use the
     * existing mail session (as expected).
     *
     * @param session mail session to be used
     * @throws NullPointerException if {@code aSession} is {@code null}
     * @since 1.0
     */
    public void setMailSession(final Session session) {
        Objects.requireNonNull(session, "no mail session supplied");

        final Properties sessionProperties = session.getProperties();
        final String auth = sessionProperties.getProperty(EmailConstants.MAIL_SMTP_AUTH);

        if (Boolean.parseBoolean(auth)) {
            final String userName = sessionProperties.getProperty(EmailConstants.MAIL_SMTP_USER);
            final String password = sessionProperties.getProperty(EmailConstants.MAIL_SMTP_PASSWORD);

            if (EmailUtils.isNotEmpty(userName) && EmailUtils.isNotEmpty(password)) {
                // only create a new mail session with an authenticator if
                // authentication is required and no user name is given
                authenticator = new DefaultAuthenticator(userName, password);
                this.session = Session.getInstance(sessionProperties, authenticator);
            } else {
                // assume that the given mail session contains a working authenticator
                this.session = session;
            }
        } else {
            this.session = session;
        }
    }

    /**
     * Sets a mail Session object from a JNDI directory.
     *
     * @param jndiName name of JNDI resource (jakarta.mail.Session type), resource if searched in java:comp/env if name does not start with "java:"
     * @throws IllegalArgumentException if the JNDI name is null or empty
     * @throws NamingException          if the resource cannot be retrieved from JNDI directory
     * @since 1.1
     */
    public void setMailSessionFromJNDI(final String jndiName) throws NamingException {
        if (EmailUtils.isEmpty(jndiName)) {
            throw new IllegalArgumentException("JNDI name missing");
        }
        Context ctx = null;
        if (jndiName.startsWith("java:")) {
            ctx = new InitialContext();
        } else {
            ctx = (Context) new InitialContext().lookup("java:comp/env");

        }
        setMailSession((Session) ctx.lookup(jndiName));
    }

    /**
     * Sets the MIME message.
     *
     * @param message the MIME message.
     */
    public void setMessage(final MimeMessage message) {
        this.message = message;
    }

    /**
     * Sets the content of the mail. It should be overridden by the subclasses.
     *
     * @param msg A String.
     * @return An Email.
     * @throws EmailException generic exception.
     * @since 1.0
     */
    public abstract Email setMsg(String msg) throws EmailException;

    /**
     * Sets whether to use POP3 before SMTP, and if so the settings.
     *
     * @param popBeforeSmtp whether to use POP3 before SMTP, and if so the settings.
     * @return {@code this} instance.
     * @since 1.6.0
     */
    public Email setPopBeforeSmtp(final boolean popBeforeSmtp) {
        this.popBeforeSmtp = popBeforeSmtp;
        return this;

    }

    /**
     * Sets details regarding "POP3 before SMTP" authentication.
     *
     * @param popBeforeSmtp Whether or not to log into POP3 server before sending mail.
     * @param popHost       The POP3 host to use.
     * @param popUserName   The POP3 user name.
     * @param popPassword   The POP3 password.
     * @since 1.0
     */
    public void setPopBeforeSmtp(final boolean popBeforeSmtp, final String popHost, final String popUserName, final String popPassword) {
        this.popBeforeSmtp = popBeforeSmtp;
        this.popHost = popHost;
        this.popUsername = popUserName;
        this.popPassword = popPassword;
    }

    /**
     * Sets the POP3 host.
     *
     * @param popHost The POP3 host.
     * @return {@code this} instance.
     * @since 1.6.0
     */
    public Email setPopHost(final String popHost) {
        this.popHost = popHost;
        return this;

    }

    /**
     * Sets the POP3 password.
     *
     * @param popPassword the POP3 password.
     * @return {@code this} instance.
     * @since 1.6.0
     */
    public Email setPopPassword(final String popPassword) {
        this.popPassword = popPassword;
        return this;

    }

    /**
     * Sets the POP3 user name.
     *
     * @param popUserName the POP3 user name.
     * @return {@code this} instance.
     * @since 1.6.0
     */
    public Email setPopUsername(final String popUserName) {
        this.popUsername = popUserName;
        return this;

    }

    /**
     * Sets a list of reply to addresses. All elements in the specified {@code Collection} are expected to be of type
     * {@code java.mail.internet.InternetAddress}.
     *
     * @param collection collection of {@code InternetAddress} objects
     * @return An Email.
     * @throws EmailException Indicates an invalid email address
     * @see jakarta.mail.internet.InternetAddress
     * @since 1.1
     */
    public Email setReplyTo(final Collection<InternetAddress> collection) throws EmailException {
        EmailException.checkNonEmpty(collection, () -> "Reply to list invalid");
        replyList = new ArrayList<>(collection);
        return this;
    }

    /**
     * Sets whether the email is partially send in case of invalid addresses.
     * <p>
     * In case the mail server rejects an address as invalid, the call to {@link #send()} may throw a {@link jakarta.mail.SendFailedException}, even if partial
     * send mode is enabled (emails to valid addresses will be transmitted). In case the email server does not reject invalid addresses immediately, but return
     * a bounce message, no exception will be thrown by the {@link #send()} method.
     * </p>
     *
     * @param sendPartial whether to enable partial send mode
     * @return An Email.
     * @throws IllegalStateException if the mail session is already initialized
     * @since 1.3.2
     */
    public Email setSendPartial(final boolean sendPartial) {
        checkSessionAlreadyInitialized();
        this.sendPartial = sendPartial;
        return this;
    }

    /**
     * Sets the sent date for the email. The sent date will default to the current date if not explicitly set.
     *
     * @param date Date to use as the sent date on the email
     * @since 1.0
     */
    public void setSentDate(final Date date) {
        if (date != null) {
            // create a separate instance to keep findbugs happy
            sentDate = new Date(date.getTime());
        }
    }

    /**
     * Sets the non-SSL port number of the outgoing mail server.
     *
     * @param portNumber aPortNumber
     * @throws IllegalArgumentException if the port number is &lt; 1
     * @throws IllegalStateException    if the mail session is already initialized
     * @since 1.0
     * @see #setSslSmtpPort(String)
     */
    public void setSmtpPort(final int portNumber) {
        checkSessionAlreadyInitialized();
        if (portNumber < 1) {
            throw new IllegalArgumentException("Cannot connect to a port number that is less than 1 ( " + portNumber + " )");
        }
        this.smtpPort = Integer.toString(portNumber);
    }

    /**
     * Sets the socket connection timeout value in milliseconds. Default is a 60 second timeout.
     *
     * @param socketConnectionTimeout the connection timeout
     * @throws IllegalStateException if the mail session is already initialized
     * @since 1.6.0
     */
    public void setSocketConnectionTimeout(final Duration socketConnectionTimeout) {
        checkSessionAlreadyInitialized();
        this.socketConnectionTimeout = Math.toIntExact(socketConnectionTimeout.toMillis());
    }

    /**
     * Sets the socket I/O timeout value in milliseconds. Default is 60 second timeout.
     *
     * @param socketTimeout the socket I/O timeout
     * @throws IllegalStateException if the mail session is already initialized
     * @since 1.6.0
     */
    public void setSocketTimeout(final Duration socketTimeout) {
        checkSessionAlreadyInitialized();
        this.socketTimeout = Math.toIntExact(socketTimeout.toMillis());
    }

    /**
     * Sets whether the server identity is checked as specified by RFC 2595
     *
     * @param sslCheckServerIdentity whether to enable server identity check
     * @return An Email.
     * @throws IllegalStateException if the mail session is already initialized
     * @since 1.3
     */
    public Email setSSLCheckServerIdentity(final boolean sslCheckServerIdentity) {
        checkSessionAlreadyInitialized();
        this.sslCheckServerIdentity = sslCheckServerIdentity;
        return this;
    }

    /**
     * Sets whether SSL/TLS encryption should be enabled for the SMTP transport upon connection (SMTPS/POPS). Takes precedence over
     * {@link #setStartTLSRequired(boolean)}
     * <p>
     * Defaults to {@link #sslSmtpPort}; can be overridden by using {@link #setSslSmtpPort(String)}
     * </p>
     *
     * @param ssl whether to enable the SSL transport
     * @return An Email.
     * @throws IllegalStateException if the mail session is already initialized
     * @since 1.3
     */
    public Email setSSLOnConnect(final boolean ssl) {
        checkSessionAlreadyInitialized();
        this.sslOnConnect = ssl;
        this.ssl = ssl;
        return this;
    }

    /**
     * Sets the SSL port to use for the SMTP transport. Defaults to the standard port, 465.
     *
     * @param sslSmtpPort the SSL port to use for the SMTP transport
     * @throws IllegalStateException if the mail session is already initialized
     * @see #setSmtpPort(int)
     */
    public void setSslSmtpPort(final String sslSmtpPort) {
        checkSessionAlreadyInitialized();
        this.sslSmtpPort = sslSmtpPort;
    }

    /**
     * Sets or disable the STARTTLS encryption.
     *
     * @param startTlsEnabled true if STARTTLS requested, false otherwise
     * @return An Email.
     * @throws IllegalStateException if the mail session is already initialized
     * @since 1.3
     */
    public Email setStartTLSEnabled(final boolean startTlsEnabled) {
        checkSessionAlreadyInitialized();
        this.startTlsEnabled = startTlsEnabled;
        this.tls = startTlsEnabled;
        return this;
    }

    /**
     * Sets or disable the required STARTTLS encryption.
     * <p>
     * Defaults to {@link #smtpPort}; can be overridden by using {@link #setSmtpPort(int)}
     * </p>
     *
     * @param startTlsRequired true if STARTTLS requested, false otherwise
     * @return An Email.
     * @throws IllegalStateException if the mail session is already initialized
     * @since 1.3
     */
    public Email setStartTLSRequired(final boolean startTlsRequired) {
        checkSessionAlreadyInitialized();
        this.startTlsRequired = startTlsRequired;
        return this;
    }

    /**
     * Sets the email subject. Replaces end-of-line characters with spaces.
     *
     * @param aSubject A String.
     * @return An Email.
     * @since 1.0
     */
    public Email setSubject(final String aSubject) {
        this.subject = EmailUtils.replaceEndOfLineCharactersWithSpaces(aSubject);
        return this;
    }

    /**
     * Sets a list of "TO" addresses. All elements in the specified {@code Collection} are expected to be of type {@code java.mail.internet.InternetAddress}.
     *
     * @param collection collection of {@code InternetAddress} objects.
     * @return An Email.
     * @throws EmailException Indicates an invalid email address.
     * @see jakarta.mail.internet.InternetAddress
     * @since 1.0
     */
    public Email setTo(final Collection<InternetAddress> collection) throws EmailException {
        EmailException.checkNonEmpty(collection, () -> "To list invalid");
        this.toList = new ArrayList<>(collection);
        return this;
    }

    /**
     * Converts to copy List of known InternetAddress objects into an array.
     *
     * @param list A List.
     * @return An InternetAddress[].
     * @since 1.0
     */
    protected InternetAddress[] toInternetAddressArray(final List<InternetAddress> list) {
        return list.toArray(EMPTY_INTERNET_ADDRESS_ARRAY);
    }

    /**
     * Updates the contentType.
     *
     * @param contentType aContentType
     * @since 1.2
     */
    public void updateContentType(final String contentType) {
        if (EmailUtils.isEmpty(contentType)) {
            this.contentType = null;
        } else {
            // set the content type
            this.contentType = contentType;
            // set the charset if the input was properly formed
            final String strMarker = "; charset=";
            int charsetPos = EmailUtils.toLower(contentType).indexOf(strMarker);
            if (charsetPos != -1) {
                // find the next space (after the marker)
                charsetPos += strMarker.length();
                final int intCharsetEnd = EmailUtils.toLower(contentType).indexOf(" ", charsetPos);
                if (intCharsetEnd != -1) {
                    this.charset = contentType.substring(charsetPos, intCharsetEnd);
                } else {
                    this.charset = contentType.substring(charsetPos);
                }
            } else if (this.contentType.startsWith("text/") && EmailUtils.isNotEmpty(this.charset)) {
                // use the default charset, if one exists, for messages
                // whose content-type is some form of text.
                final StringBuilder contentTypeBuf = new StringBuilder(this.contentType);
                contentTypeBuf.append(strMarker);
                contentTypeBuf.append(this.charset);
                this.contentType = contentTypeBuf.toString();
            }
        }
    }
}
File Project Line
org/apache/commons/mail2/jakarta/HtmlEmail.java Apache Commons Email for Jakarta 82
org/apache/commons/mail2/javax/HtmlEmail.java Apache Commons Email for Javax 82
public class HtmlEmail extends MultiPartEmail {

    /**
     * Private bean class that encapsulates data about URL contents that are embedded in the final email.
     *
     * @since 1.1
     */
    private static final class InlineImage {

        /** Content id. */
        private final String cid;

        /** {@code DataSource} for the content. */
        private final DataSource dataSource;

        /** The {@code MimeBodyPart} that contains the encoded data. */
        private final MimeBodyPart mimeBodyPart;

        /**
         * Creates an InlineImage object to represent the specified content ID and {@code MimeBodyPart}.
         *
         * @param cid          the generated content ID, not null.
         * @param dataSource   the {@code DataSource} that represents the content, not null.
         * @param mimeBodyPart the {@code MimeBodyPart} that contains the encoded data, not null.
         */
        private InlineImage(final String cid, final DataSource dataSource, final MimeBodyPart mimeBodyPart) {
            this.cid = Objects.requireNonNull(cid, "cid");
            this.dataSource = Objects.requireNonNull(dataSource, "dataSource");
            this.mimeBodyPart = Objects.requireNonNull(mimeBodyPart, "mimeBodyPart");
        }

        @Override
        public boolean equals(final Object obj) {
            if (this == obj) {
                return true;
            }
            if (!(obj instanceof InlineImage)) {
                return false;
            }
            final InlineImage other = (InlineImage) obj;
            return Objects.equals(cid, other.cid);
        }

        /**
         * Returns the unique content ID of this InlineImage.
         *
         * @return the unique content ID of this InlineImage
         */
        private String getCid() {
            return cid;
        }

        /**
         * Returns the {@code DataSource} that represents the encoded content.
         *
         * @return the {@code DataSource} representing the encoded content
         */
        private DataSource getDataSource() {
            return dataSource;
        }

        /**
         * Returns the {@code MimeBodyPart} that contains the encoded InlineImage data.
         *
         * @return the {@code MimeBodyPart} containing the encoded InlineImage data
         */
        private MimeBodyPart getMimeBodyPart() {
            return mimeBodyPart;
        }

        @Override
        public int hashCode() {
            return Objects.hash(cid);
        }
    }

    /** Definition of the length of generated CID's. */
    public static final int CID_LENGTH = 10;

    /** Prefix for default HTML mail. */
    private static final String HTML_MESSAGE_START = "<html><body><pre>";

    /** Suffix for default HTML mail. */
    private static final String HTML_MESSAGE_END = "</pre></body></html>";

    /**
     * Text part of the message. This will be used as alternative text if the email client does not support HTML messages.
     */
    private String text;

    /**
     * HTML part of the message.
     */
    private String html;

    /**
     * Embedded images Map&lt;String, InlineImage&gt; where the key is the user-defined image name.
     */
    private final Map<String, InlineImage> inlineEmbeds = new HashMap<>();

    /**
     * Constructs a new instance.
     */
    public HtmlEmail() {
        // empty
    }

    /**
     * @throws EmailException     EmailException
     * @throws MessagingException MessagingException
     */
    private void build() throws MessagingException, EmailException {
        final MimeMultipart rootContainer = getContainer();
        MimeMultipart bodyEmbedsContainer = rootContainer;
        MimeMultipart bodyContainer = rootContainer;
        MimeBodyPart msgHtml = null;
        MimeBodyPart msgText = null;

        rootContainer.setSubType("mixed");

        // determine how to form multiparts of email

        if (EmailUtils.isNotEmpty(html) && !EmailUtils.isEmpty(inlineEmbeds)) {
            // If HTML body and embeds are used, create a related container and add it to the root container
            bodyEmbedsContainer = new MimeMultipart("related");
            bodyContainer = bodyEmbedsContainer;
            addPart(bodyEmbedsContainer, 0);

            // If TEXT body was specified, create a alternative container and add it to the embeds container
            if (EmailUtils.isNotEmpty(text)) {
                bodyContainer = new MimeMultipart("alternative");
                final BodyPart bodyPart = createBodyPart();
                try {
                    bodyPart.setContent(bodyContainer);
                    bodyEmbedsContainer.addBodyPart(bodyPart, 0);
                } catch (final MessagingException e) {
                    throw new EmailException(e);
                }
            }
        } else if (EmailUtils.isNotEmpty(text) && EmailUtils.isNotEmpty(html)) {
            // EMAIL-142: if we have both an HTML and TEXT body, but no attachments or
            // inline images, the root container should have mimetype
            // "multipart/alternative".
            // reference: https://tools.ietf.org/html/rfc2046#section-5.1.4
            if (!EmailUtils.isEmpty(inlineEmbeds) || isBoolHasAttachments()) {
                // If both HTML and TEXT bodies are provided, create an alternative
                // container and add it to the root container
                bodyContainer = new MimeMultipart("alternative");
                this.addPart(bodyContainer, 0);
            } else {
                // no attachments or embedded images present, change the mimetype
                // of the root container (= body container)
                rootContainer.setSubType("alternative");
            }
        }

        if (EmailUtils.isNotEmpty(html)) {
            msgHtml = new MimeBodyPart();
            bodyContainer.addBodyPart(msgHtml, 0);

            // EMAIL-104: call explicitly setText to use default mime charset
            // (property "mail.mime.charset") in case none has been set
            msgHtml.setText(html, getCharsetName(), EmailConstants.TEXT_SUBTYPE_HTML);

            // EMAIL-147: work-around for buggy JavaMail implementations;
            // in case setText(...) does not set the correct content type,
            // use the setContent() method instead.
            final String contentType = msgHtml.getContentType();
            if (contentType == null || !contentType.equals(EmailConstants.TEXT_HTML)) {
                // apply default charset if one has been set
                if (EmailUtils.isNotEmpty(getCharsetName())) {
                    msgHtml.setContent(html, EmailConstants.TEXT_HTML + "; charset=" + getCharsetName());
                } else {
                    // unfortunately, MimeUtility.getDefaultMIMECharset() is package private
                    // and thus can not be used to set the default system charset in case
                    // no charset has been provided by the user
                    msgHtml.setContent(html, EmailConstants.TEXT_HTML);
                }
            }

            for (final InlineImage image : inlineEmbeds.values()) {
                bodyEmbedsContainer.addBodyPart(image.getMimeBodyPart());
            }
        }

        if (EmailUtils.isNotEmpty(text)) {
            msgText = new MimeBodyPart();
            bodyContainer.addBodyPart(msgText, 0);

            // EMAIL-104: call explicitly setText to use default mime charset
            // (property "mail.mime.charset") in case none has been set
            msgText.setText(text, getCharsetName());
        }
    }

    /**
     * Builds the MimeMessage. Please note that a user rarely calls this method directly and only if he/she is interested in the sending the underlying
     * MimeMessage without commons-email.
     *
     * @throws EmailException if there was an error.
     * @since 1.0
     */
    @Override
    public void buildMimeMessage() throws EmailException {
        try {
            build();
        } catch (final MessagingException e) {
            throw new EmailException(e);
        }
        super.buildMimeMessage();
    }

    /**
     * Embeds the specified {@code DataSource} in the HTML using a randomly generated Content-ID. Returns the generated Content-ID string.
     *
     * @param dataSource the {@code DataSource} to embed
     * @param name       the name that will be set in the file name header field
     * @return the generated Content-ID for this {@code DataSource}
     * @throws EmailException if the embedding fails or if {@code name} is null or empty
     * @see #embed(DataSource, String, String)
     * @since 1.1
     */
    public String embed(final DataSource dataSource, final String name) throws EmailException {
        // check if the DataSource has already been attached;
        // if so, return the cached CID value.
        final InlineImage inlineImage = inlineEmbeds.get(name);
        if (inlineImage != null) {
            // make sure the supplied URL points to the same thing
            // as the one already associated with this name.
            if (dataSource.equals(inlineImage.getDataSource())) {
                return inlineImage.getCid();
            }
            throw new EmailException("embedded DataSource '" + name + "' is already bound to name " + inlineImage.getDataSource().toString()
                    + "; existing names cannot be rebound");
        }

        final String cid = EmailUtils.toLower(EmailUtils.randomAlphabetic(CID_LENGTH));
        return embed(dataSource, name, cid);
    }

    /**
     * Embeds the specified {@code DataSource} in the HTML using the specified Content-ID. Returns the specified Content-ID string.
     *
     * @param dataSource the {@code DataSource} to embed
     * @param name       the name that will be set in the file name header field
     * @param cid        the Content-ID to use for this {@code DataSource}
     * @return the URL encoded Content-ID for this {@code DataSource}
     * @throws EmailException if the embedding fails or if {@code name} is null or empty
     * @since 1.1
     */
    public String embed(final DataSource dataSource, final String name, final String cid) throws EmailException {
        EmailException.checkNonEmpty(name, () -> "Name cannot be null or empty");
        final MimeBodyPart mbp = new MimeBodyPart();
        try {
            // URL encode the cid according to RFC 2392
            final String encodedCid = EmailUtils.encodeUrl(cid);
            mbp.setDataHandler(new DataHandler(dataSource));
            mbp.setFileName(name);
            mbp.setDisposition(EmailAttachment.INLINE);
            mbp.setContentID("<" + encodedCid + ">");
            this.inlineEmbeds.put(name, new InlineImage(encodedCid, dataSource, mbp));
            return encodedCid;
        } catch (final MessagingException e) {
            throw new EmailException(e);
        }
    }

    /**
     * Embeds a file in the HTML. This implementation delegates to {@link #embed(File, String)}.
     *
     * @param file The {@code File} object to embed
     * @return A String with the Content-ID of the file.
     * @throws EmailException when the supplied {@code File} cannot be used; also see {@link jakarta.mail.internet.MimeBodyPart} for definitions
     *
     * @see #embed(File, String)
     * @since 1.1
     */
    public String embed(final File file) throws EmailException {
        return embed(file, EmailUtils.toLower(EmailUtils.randomAlphabetic(CID_LENGTH)));
    }

    /**
     * Embeds a file in the HTML.
     *
     * <p>
     * This method embeds a file located by an URL into the mail body. It allows, for instance, to add inline images to the email. Inline files may be
     * referenced with a {@code cid:xxxxxx} URL, where xxxxxx is the Content-ID returned by the embed function. Files are bound to their names, which is the
     * value returned by {@link java.io.File#getName()}. If the same file is embedded multiple times, the same CID is guaranteed to be returned.
     *
     * <p>
     * While functionally the same as passing {@code FileDataSource} to {@link #embed(DataSource, String, String)}, this method attempts to validate the file
     * before embedding it in the message and will throw {@code EmailException} if the validation fails. In this case, the {@code HtmlEmail} object will not be
     * changed.
     *
     * @param file The {@code File} to embed
     * @param cid  the Content-ID to use for the embedded {@code File}
     * @return A String with the Content-ID of the file.
     * @throws EmailException when the supplied {@code File} cannot be used or if the file has already been embedded; also see
     *                        {@link jakarta.mail.internet.MimeBodyPart} for definitions
     * @since 1.1
     */
    public String embed(final File file, final String cid) throws EmailException {
        EmailException.checkNonEmpty(file.getName(), () -> "File name cannot be null or empty");

        // verify that the File can provide a canonical path
        String filePath = null;
        try {
            filePath = file.getCanonicalPath();
        } catch (final IOException e) {
            throw new EmailException("couldn't get canonical path for " + file.getName(), e);
        }

        // check if a FileDataSource for this name has already been attached;
        // if so, return the cached CID value.
        final InlineImage inlineImage = inlineEmbeds.get(file.getName());
        if (inlineImage != null) {
            final FileDataSource fileDataSource = (FileDataSource) inlineImage.getDataSource();
            // make sure the supplied file has the same canonical path
            // as the one already associated with this name.
            String existingFilePath = null;
            try {
                existingFilePath = fileDataSource.getFile().getCanonicalPath();
            } catch (final IOException e) {
                throw new EmailException("couldn't get canonical path for file " + fileDataSource.getFile().getName() + "which has already been embedded", e);
            }
            if (filePath.equals(existingFilePath)) {
                return inlineImage.getCid();
            }
            throw new EmailException(
                    "embedded name '" + file.getName() + "' is already bound to file " + existingFilePath + "; existing names cannot be rebound");
        }

        // verify that the file is valid
        if (!file.exists()) {
            throw new EmailException("file " + filePath + " doesn't exist");
        }
        if (!file.isFile()) {
            throw new EmailException("file " + filePath + " isn't a normal file");
        }
        if (!file.canRead()) {
            throw new EmailException("file " + filePath + " isn't readable");
        }

        return embed(new FileDataSource(file), file.getName(), cid);
    }

    /**
     * Parses the specified {@code String} as a URL that will then be embedded in the message.
     *
     * @param urlString String representation of the URL.
     * @param name      The name that will be set in the file name header field.
     * @return A String with the Content-ID of the URL.
     * @throws EmailException when URL supplied is invalid or if {@code name} is null or empty; also see {@link jakarta.mail.internet.MimeBodyPart} for
     *                        definitions
     *
     * @see #embed(URL, String)
     * @since 1.1
     */
    public String embed(final String urlString, final String name) throws EmailException {
        try {
            return embed(new URL(urlString), name);
        } catch (final MalformedURLException e) {
            throw new EmailException("Invalid URL", e);
        }
    }

    /**
     * Embeds an URL in the HTML.
     *
     * <p>
     * This method embeds a file located by an URL into the mail body. It allows, for instance, to add inline images to the email. Inline files may be
     * referenced with a {@code cid:xxxxxx} URL, where xxxxxx is the Content-ID returned by the embed function. It is an error to bind the same name to more
     * than one URL; if the same URL is embedded multiple times, the same Content-ID is guaranteed to be returned.
     * </p>
     * <p>
     * While functionally the same as passing {@code URLDataSource} to {@link #embed(DataSource, String, String)}, this method attempts to validate the URL
     * before embedding it in the message and will throw {@code EmailException} if the validation fails. In this case, the {@code HtmlEmail} object will not be
     * changed.
     * </p>
     * <p>
     * NOTE: Clients should take care to ensure that different URLs are bound to different names. This implementation tries to detect this and throw
     * {@code EmailException}. However, it is not guaranteed to catch all cases, especially when the URL refers to a remote HTTP host that may be part of a
     * virtual host cluster.
     * </p>
     *
     * @param url  The URL of the file.
     * @param name The name that will be set in the file name header field.
     * @return A String with the Content-ID of the file.
     * @throws EmailException when URL supplied is invalid or if {@code name} is null or empty; also see {@link jakarta.mail.internet.MimeBodyPart} for
     *                        definitions
     * @since 1.0
     */
    public String embed(final URL url, final String name) throws EmailException {
        EmailException.checkNonEmpty(name, () -> "Name cannot be null or empty");
        // check if a URLDataSource for this name has already been attached;
        // if so, return the cached CID value.
        final InlineImage inlineImage = inlineEmbeds.get(name);
        if (inlineImage != null) {
            final URLDataSource urlDataSource = (URLDataSource) inlineImage.getDataSource();
            // make sure the supplied URL points to the same thing
            // as the one already associated with this name.
            // NOTE: Comparing URLs with URL.equals() is a blocking operation
            // in the case of a network failure therefore we use
            // url.toExternalForm().equals() here.
            if (url.toExternalForm().equals(urlDataSource.getURL().toExternalForm())) {
                return inlineImage.getCid();
            }
            throw new EmailException("embedded name '" + name + "' is already bound to URL " + urlDataSource.getURL() + "; existing names cannot be rebound");
        }
        // verify that the URL is valid
        try (InputStream inputStream = url.openStream()) {
            // Make sure we can read.
            inputStream.read();
        } catch (final IOException e) {
            throw new EmailException("Invalid URL", e);
        }
        return embed(new URLDataSource(url), name);
    }

    /**
     * Gets the HTML content.
     *
     * @return the HTML content.
     * @since 1.6.0
     */
    public String getHtml() {
        return html;
    }

    /**
     * Gets the message text.
     *
     * @return the message text.
     * @since 1.6.0
     */
    public String getText() {
        return text;
    }

    /**
     * Sets the HTML content.
     *
     * @param html A String.
     * @return An HtmlEmail.
     * @throws EmailException see jakarta.mail.internet.MimeBodyPart for definitions
     * @since 1.0
     */
    public HtmlEmail setHtmlMsg(final String html) throws EmailException {
        this.html = EmailException.checkNonEmpty(html, () -> "Invalid message.");
        return this;
    }

    /**
     * Sets the message.
     *
     * <p>
     * This method overrides {@link MultiPartEmail#setMsg(String)} in order to send an HTML message instead of a plain text message in the mail body. The
     * message is formatted in HTML for the HTML part of the message; it is left as is in the alternate text part.
     * </p>
     *
     * @param msg the message text to use
     * @return this {@code HtmlEmail}
     * @throws EmailException if msg is null or empty; see jakarta.mail.internet.MimeBodyPart for definitions
     * @since 1.0
     */
    @Override
    public Email setMsg(final String msg) throws EmailException {
        setTextMsg(msg);
        final StringBuilder htmlMsgBuf = new StringBuilder(msg.length() + HTML_MESSAGE_START.length() + HTML_MESSAGE_END.length());
        htmlMsgBuf.append(HTML_MESSAGE_START).append(msg).append(HTML_MESSAGE_END);
        setHtmlMsg(htmlMsgBuf.toString());
        return this;
    }

    /**
     * Sets the text content.
     *
     * @param text A String.
     * @return An HtmlEmail.
     * @throws EmailException see jakarta.mail.internet.MimeBodyPart for definitions
     * @since 1.0
     */
    public HtmlEmail setTextMsg(final String text) throws EmailException {
        this.text = EmailException.checkNonEmpty(text, () -> "Invalid message.");
        return this;
    }
}
File Project Line
org/apache/commons/mail2/jakarta/MultiPartEmail.java Apache Commons Email for Jakarta 56
org/apache/commons/mail2/javax/MultiPartEmail.java Apache Commons Email for Javax 56
public class MultiPartEmail extends Email {

    /** Body portion of the email. */
    private MimeMultipart container;

    /** The message container. */
    private BodyPart primaryBodyPart;

    /** The MIME subtype. */
    private String subType;

    /** Indicates if the message has been initialized. */
    private boolean initialized;

    /** Indicates if attachments have been added to the message. */
    private boolean hasAttachments;

    /**
     * Constructs a new instance.
     */
    public MultiPartEmail() {
        // empty
    }

    /**
     * Adds a new part to the email.
     *
     * @param multipart The MimeMultipart.
     * @return An Email.
     * @throws EmailException see jakarta.mail.internet.MimeBodyPart for definitions
     * @since 1.0
     */
    public Email addPart(final MimeMultipart multipart) throws EmailException {
        try {
            return addPart(multipart, getContainer().getCount());
        } catch (final MessagingException e) {
            throw new EmailException(e);
        }
    }

    /**
     * Adds a new part to the email.
     *
     * @param multipart The part to add.
     * @param index     The index to add at.
     * @return The email.
     * @throws EmailException An error occurred while adding the part.
     * @since 1.0
     */
    public Email addPart(final MimeMultipart multipart, final int index) throws EmailException {
        final BodyPart bodyPart = createBodyPart();
        try {
            bodyPart.setContent(multipart);
            getContainer().addBodyPart(bodyPart, index);
        } catch (final MessagingException e) {
            throw new EmailException(e);
        }

        return this;
    }

    /**
     * Adds a new part to the email.
     *
     * @param partContent     The content.
     * @param partContentType The content type.
     * @return An Email.
     * @throws EmailException see jakarta.mail.internet.MimeBodyPart for definitions
     * @since 1.0
     */
    public Email addPart(final String partContent, final String partContentType) throws EmailException {
        final BodyPart bodyPart = createBodyPart();
        try {
            bodyPart.setContent(partContent, partContentType);
            getContainer().addBodyPart(bodyPart);
        } catch (final MessagingException e) {
            throw new EmailException(e);
        }

        return this;
    }

    /**
     * Attaches a file specified as a DataSource interface.
     *
     * @param dataSource  A DataSource interface for the file.
     * @param name        The name field for the attachment.
     * @param description A description for the attachment.
     * @return A MultiPartEmail.
     * @throws EmailException see jakarta.mail.internet.MimeBodyPart for definitions
     * @since 1.0
     */
    public MultiPartEmail attach(final DataSource dataSource, final String name, final String description) throws EmailException {
        EmailException.checkNonNull(dataSource, () -> "Invalid Datasource.");
        // verify that the DataSource is valid
        try (InputStream inputStream = dataSource.getInputStream()) {
            EmailException.checkNonNull(inputStream, () -> "Invalid Datasource.");
        } catch (final IOException e) {
            throw new EmailException("Invalid Datasource.", e);
        }
        return attach(dataSource, name, description, EmailAttachment.ATTACHMENT);
    }

    /**
     * Attaches a file specified as a DataSource interface.
     *
     * @param dataSource  A DataSource interface for the file.
     * @param name        The name field for the attachment.
     * @param description A description for the attachment.
     * @param disposition Either mixed or inline.
     * @return A MultiPartEmail.
     * @throws EmailException see jakarta.mail.internet.MimeBodyPart for definitions
     * @since 1.0
     */
    public MultiPartEmail attach(final DataSource dataSource, String name, final String description, final String disposition) throws EmailException {
        if (EmailUtils.isEmpty(name)) {
            name = dataSource.getName();
        }
        try {
            final BodyPart bodyPart = createBodyPart();
            bodyPart.setDisposition(disposition);
            bodyPart.setFileName(MimeUtility.encodeText(name));
            bodyPart.setDescription(description);
            bodyPart.setDataHandler(new DataHandler(dataSource));
            getContainer().addBodyPart(bodyPart);
        } catch (final UnsupportedEncodingException | MessagingException e) {
            // in case the file name could not be encoded
            throw new EmailException(e);
        }
        setBoolHasAttachments(true);
        return this;
    }

    /**
     * Attaches an EmailAttachment.
     *
     * @param attachment An EmailAttachment.
     * @return A MultiPartEmail.
     * @throws EmailException see jakarta.mail.internet.MimeBodyPart for definitions
     * @since 1.0
     */
    public MultiPartEmail attach(final EmailAttachment attachment) throws EmailException {
        EmailException.checkNonNull(attachment, () -> "Invalid attachment.");
        MultiPartEmail result = null;
        final URL url = attachment.getURL();
        if (url == null) {
            String fileName = null;
            try {
                fileName = attachment.getPath();
                final File file = new File(fileName);
                if (!file.exists()) {
                    throw new IOException("\"" + fileName + "\" does not exist");
                }
                result = attach(new FileDataSource(file), attachment.getName(), attachment.getDescription(), attachment.getDisposition());
            } catch (final IOException e) {
                throw new EmailException("Cannot attach file \"" + fileName + "\"", e);
            }
        } else {
            result = attach(url, attachment.getName(), attachment.getDescription(), attachment.getDisposition());
        }
        return result;
    }

    /**
     * Attaches a file.
     *
     * @param file A file attachment
     * @return A MultiPartEmail.
     * @throws EmailException see jakarta.mail.internet.MimeBodyPart for definitions
     * @since 1.3
     */
    public MultiPartEmail attach(final File file) throws EmailException {
        final String fileName = file.getAbsolutePath();
        try {
            if (!file.exists()) {
                throw new IOException("\"" + fileName + "\" does not exist");
            }
            return attach(new FileDataSource(file), file.getName(), null, EmailAttachment.ATTACHMENT);
        } catch (final IOException e) {
            throw new EmailException("Cannot attach file \"" + fileName + "\"", e);
        }
    }

    /**
     * Attaches a path.
     *
     * @param file    A file attachment.
     * @param options options for opening file streams.
     * @return A MultiPartEmail.
     * @throws EmailException see jakarta.mail.internet.MimeBodyPart for definitions
     * @since 1.6.0
     */
    public MultiPartEmail attach(final Path file, final OpenOption... options) throws EmailException {
        final Path fileName = file.toAbsolutePath();
        try {
            if (!Files.exists(file)) {
                throw new IOException("\"" + fileName + "\" does not exist");
            }
            return attach(new PathDataSource(file, FileTypeMap.getDefaultFileTypeMap(), options), Objects.toString(file.getFileName(), null), null,
                    EmailAttachment.ATTACHMENT);
        } catch (final IOException e) {
            throw new EmailException("Cannot attach file \"" + fileName + "\"", e);
        }
    }

    /**
     * Attaches a file located by its URL. The disposition of the file is set to mixed.
     *
     * @param url         The URL of the file (may be any valid URL).
     * @param name        The name field for the attachment.
     * @param description A description for the attachment.
     * @return A MultiPartEmail.
     * @throws EmailException see jakarta.mail.internet.MimeBodyPart for definitions
     * @since 1.0
     */
    public MultiPartEmail attach(final URL url, final String name, final String description) throws EmailException {
        return attach(url, name, description, EmailAttachment.ATTACHMENT);
    }

    /**
     * Attaches a file located by its URL.
     *
     * @param url         The URL of the file (may be any valid URL).
     * @param name        The name field for the attachment.
     * @param description A description for the attachment.
     * @param disposition Either mixed or inline.
     * @return A MultiPartEmail.
     * @throws EmailException see jakarta.mail.internet.MimeBodyPart for definitions
     * @since 1.0
     */
    public MultiPartEmail attach(final URL url, final String name, final String description, final String disposition) throws EmailException {
        // verify that the URL is valid
        try {
            url.openStream().close();
        } catch (final IOException e) {
            throw new EmailException("Invalid URL set:" + url, e);
        }
        return attach(new URLDataSource(url), name, description, disposition);
    }

    /**
     * Builds the MimeMessage. Please note that a user rarely calls this method directly and only if he/she is interested in the sending the underlying
     * MimeMessage without commons-email.
     *
     * @throws EmailException if there was an error.
     * @since 1.0
     */
    @Override
    public void buildMimeMessage() throws EmailException {
        try {
            if (primaryBodyPart != null) {
                // before a multipart message can be sent, we must make sure that
                // the content for the main body part was actually set. If not,
                // an IOException will be thrown during super.send().

                final BodyPart body = getPrimaryBodyPart();
                try {
                    body.getContent();
                } catch (final IOException e) { // NOPMD
                    // do nothing here.
                    // content will be set to an empty string as a result.
                    // (Should this really be rethrown as an email exception?)
                    // throw new EmailException(e);
                }
            }

            if (subType != null) {
                getContainer().setSubType(subType);
            }

            super.buildMimeMessage();
        } catch (final MessagingException e) {
            throw new EmailException(e);
        }
    }

    /**
     * Creates a body part object. Can be overridden if you don't want to create a BodyPart.
     *
     * @return the created body part
     */
    protected BodyPart createBodyPart() {
        return new MimeBodyPart();
    }

    /**
     * Creates a mime multipart object.
     *
     * @return the created mime part
     */
    protected MimeMultipart createMimeMultipart() {
        return new MimeMultipart();
    }

    /**
     * Gets the message container.
     *
     * @return The message container.
     * @since 1.0
     */
    protected MimeMultipart getContainer() {
        if (!initialized) {
            init();
        }
        return container;
    }

    /**
     * Gets first body part of the message.
     *
     * @return The primary body part.
     * @throws MessagingException An error occurred while getting the primary body part.
     * @since 1.0
     */
    protected BodyPart getPrimaryBodyPart() throws MessagingException {
        if (!initialized) {
            init();
        }
        // Add the first body part to the message. The fist body part must be
        if (primaryBodyPart == null) {
            primaryBodyPart = createBodyPart();
            getContainer().addBodyPart(primaryBodyPart, 0);
        }
        return primaryBodyPart;
    }

    /**
     * Gets the MIME subtype of the email.
     *
     * @return MIME subtype of the email
     * @since 1.0
     */
    public String getSubType() {
        return subType;
    }

    /**
     * Initialize the multipart email.
     *
     * @since 1.0
     */
    protected void init() {
        if (initialized) {
            throw new IllegalStateException("Already initialized");
        }
        container = createMimeMultipart();
        super.setContent(container);
        initialized = true;
    }

    /**
     * Tests whether there are attachments.
     *
     * @return true if there are attachments
     * @since 1.0
     */
    public boolean isBoolHasAttachments() {
        return hasAttachments;
    }

    /**
     * Tests if this object is initialized.
     *
     * @return true if initialized
     */
    protected boolean isInitialized() {
        return initialized;
    }

    /**
     * Sets whether there are attachments.
     *
     * @param hasAttachments the attachments flag
     * @since 1.0
     */
    public void setBoolHasAttachments(final boolean hasAttachments) {
        this.hasAttachments = hasAttachments;
    }

    /**
     * Sets the initialized status of this object.
     *
     * @param initialized the initialized status flag
     */
    protected void setInitialized(final boolean initialized) {
        this.initialized = initialized;
    }

    /**
     * Sets the message of the email.
     *
     * @param msg A String.
     * @return An Email.
     * @throws EmailException see jakarta.mail.internet.MimeBodyPart for definitions
     * @since 1.0
     */
    @Override
    public Email setMsg(final String msg) throws EmailException {
        EmailException.checkNonEmpty(msg, () -> "Invalid message.");
        try {
            final BodyPart primary = getPrimaryBodyPart();
            if (primary instanceof MimePart && EmailUtils.isNotEmpty(getCharsetName())) {
                ((MimePart) primary).setText(msg, getCharsetName());
            } else {
                primary.setText(msg);
            }
        } catch (final MessagingException e) {
            throw new EmailException(e);
        }
        return this;
    }

    /**
     * Sets the MIME subtype of the email.
     *
     * @param subType MIME subtype of the email
     * @since 1.0
     */
    public void setSubType(final String subType) {
        this.subType = subType;
    }

}
File Project Line
org/apache/commons/mail2/jakarta/util/MimeMessageParser.java Apache Commons Email for Jakarta 50
org/apache/commons/mail2/javax/util/MimeMessageParser.java Apache Commons Email for Javax 50
public class MimeMessageParser {

    /** The MimeMessage to convert. */
    private final MimeMessage mimeMessage;

    /** Plain mail content from MimeMessage. */
    private String plainContent;

    /** HTML mail content from MimeMessage. */
    private String htmlContent;

    /** List of attachments of MimeMessage. */
    private final List<DataSource> attachmentList;

    /** Attachments stored by their content-id. */
    private final Map<String, DataSource> cidMap;

    /** Is this a Multipart email. */
    private boolean isMultiPart;

    /**
     * Constructs an instance with the MimeMessage to be extracted.
     *
     * @param mimeMessage the message to parse
     */
    public MimeMessageParser(final MimeMessage mimeMessage) {
        this.attachmentList = new ArrayList<>();
        this.cidMap = new HashMap<>();
        this.mimeMessage = mimeMessage;
        this.isMultiPart = false;
    }

    private List<Address> asList(final Address[] recipients) {
        return recipients != null ? Arrays.asList(recipients) : new ArrayList<>();
    }

    /**
     * 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        error getting InputStream or unsupported encoding
     */
    @SuppressWarnings("resource") // Caller closes InputStream
    protected DataSource createDataSource(final Multipart parent, final MimePart part) throws MessagingException, IOException {
        final DataSource dataSource = part.getDataHandler().getDataSource();
        final String contentType = getBaseMimeType(dataSource.getContentType());
        final String dataSourceName = getDataSourceName(part, dataSource);
        return new InputStreamDataSource(dataSource.getInputStream(), contentType, dataSourceName);
    }

    /**
     * Find an attachment using its content-id.
     * <p>
     * The content-id must be stripped of any angle brackets, i.e. "part1" instead of "&lt;part1&gt;".
     * </p>
     *
     * @param cid the content-id of the attachment
     * @return the corresponding datasource or null if nothing was found
     * @since 1.3.4
     */
    public DataSource findAttachmentByCid(final String cid) {
        return cidMap.get(cid);
    }

    /**
     * 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) {
        for (final DataSource dataSource : getAttachmentList()) {
            if (name.equalsIgnoreCase(dataSource.getName())) {
                return dataSource;
            }
        }
        return null;
    }

    /**
     * Gets the attachment list.
     *
     * @return Returns the attachment list.
     */
    public List<DataSource> getAttachmentList() {
        return attachmentList;
    }

    /**
     * Gets the MIME type.
     *
     * @param fullMimeType the mime type from the mail API
     * @return the real mime type
     */
    private String getBaseMimeType(final String fullMimeType) {
        final int pos = fullMimeType.indexOf(';');
        return pos < 0 ? fullMimeType : fullMimeType.substring(0, pos);
    }

    /**
     * Gets the BCC Address list.
     *
     * @return the 'BCC' recipients of the message
     * @throws MessagingException determining the recipients failed
     */
    public List<Address> getBcc() throws MessagingException {
        return asList(mimeMessage.getRecipients(Message.RecipientType.BCC));
    }

    /**
     * Gets the CC Address list.
     *
     * @return the 'CC' recipients of the message
     * @throws MessagingException determining the recipients failed
     */
    public List<Address> getCc() throws MessagingException {
        return asList(mimeMessage.getRecipients(Message.RecipientType.CC));
    }

    /**
     * Returns a collection of all content-ids in the parsed message.
     * <p>
     * The content-ids are stripped of any angle brackets, i.e. "part1" instead of "&lt;part1&gt;".
     * </p>
     *
     * @return the collection of content ids.
     * @since 1.3.4
     */
    public Collection<String> getContentIds() {
        return Collections.unmodifiableSet(cidMap.keySet());
    }

    /**
     * Determines the name of the data source if it is not already set.
     *
     * @param part       the mail part
     * @param dataSource the data source
     * @return the name of the data source or {@code null} if no name can be determined
     * @throws MessagingException           accessing the part failed
     * @throws UnsupportedEncodingException decoding the text failed
     */
    protected String getDataSourceName(final Part part, final DataSource dataSource) throws MessagingException, UnsupportedEncodingException {
        String result = dataSource.getName();
        if (isEmpty(result)) {
            result = part.getFileName();
        }
        if (!isEmpty(result)) {
            result = MimeUtility.decodeText(result);
        } else {
            result = null;
        }
        return result;
    }

    /**
     * Gets the FROM field.
     *
     * @return the FROM field of the message
     * @throws MessagingException parsing the mime message failed
     */
    public String getFrom() throws MessagingException {
        final Address[] addresses = mimeMessage.getFrom();
        if (isEmpty(addresses)) {
            return null;
        }
        return ((InternetAddress) addresses[0]).getAddress();
    }

    /**
     * Gets the htmlContent if any.
     *
     * @return Returns the htmlContent if any
     */
    public String getHtmlContent() {
        return htmlContent;
    }

    /**
     * Gets the MimeMessage.
     *
     * @return Returns the mimeMessage.
     */
    public MimeMessage getMimeMessage() {
        return mimeMessage;
    }

    /**
     * Gets the plain content if any.
     *
     * @return Returns the plainContent if any
     */
    public String getPlainContent() {
        return plainContent;
    }

    /**
     * Gets the 'replyTo' address of the email.
     *
     * @return the 'replyTo' address of the email
     * @throws MessagingException parsing the mime message failed
     */
    public String getReplyTo() throws MessagingException {
        final Address[] addresses = mimeMessage.getReplyTo();
        if (isEmpty(addresses)) {
            return null;
        }
        return ((InternetAddress) addresses[0]).getAddress();
    }

    /**
     * Gets the MIME message subject.
     *
     * @return the MIME message subject.
     * @throws MessagingException parsing the mime message failed.
     */
    public String getSubject() throws MessagingException {
        return mimeMessage.getSubject();
    }

    /**
     * Gets the MIME message 'to' list.
     *
     * @return the 'to' recipients of the message.
     * @throws MessagingException determining the recipients failed
     */
    public List<Address> getTo() throws MessagingException {
        return asList(mimeMessage.getRecipients(Message.RecipientType.TO));
    }

    /**
     * Tests if attachments are present.
     *
     * @return true if attachments are present.
     */
    public boolean hasAttachments() {
        return !attachmentList.isEmpty();
    }

    /**
     * Tests is HTML content is present.
     *
     * @return true if HTML content is present.
     */
    public boolean hasHtmlContent() {
        return htmlContent != null;
    }

    /**
     * Tests is plain content is present.
     *
     * @return true if a plain content is present.
     */
    public boolean hasPlainContent() {
        return plainContent != null;
    }

    private boolean isEmpty(final Object[] array) {
        return array == null || array.length == 0;
    }

    private boolean isEmpty(final String result) {
        return result == null || result.isEmpty();
    }

    /**
     * Tests whether the MimePart contains an object of the given mime type.
     *
     * @param part     the current MimePart
     * @param mimeType the mime type to check
     * @return {@code true} if the MimePart matches the given mime type, {@code false} otherwise
     * @throws MessagingException parsing the MimeMessage failed
     */
    private boolean isMimeType(final MimePart part, final String mimeType) throws MessagingException {
        // Do not use part.isMimeType(String) as it is broken for MimeBodyPart
        // and does not really check the actual content type.
        try {
            return new ContentType(part.getDataHandler().getContentType()).match(mimeType);
        } catch (final ParseException ex) {
            return part.getContentType().equalsIgnoreCase(mimeType);
        }
    }

    /**
     * Tests whether this is multipart.
     *
     * @return Returns the isMultiPart.
     */
    public boolean isMultipart() {
        return isMultiPart;
    }

    /**
     * Does the actual extraction.
     *
     * @return this instance
     * @throws MessagingException parsing the mime message failed
     * @throws IOException        parsing the mime message failed
     */
    public MimeMessageParser parse() throws MessagingException, IOException {
        parse(null, mimeMessage);
        return this;
    }

    /**
     * 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) throws MessagingException, IOException {
        if (isMimeType(part, "text/plain") && plainContent == null && !Part.ATTACHMENT.equalsIgnoreCase(part.getDisposition())) {
            plainContent = (String) part.getContent();
        } else if (isMimeType(part, "text/html") && htmlContent == null && !Part.ATTACHMENT.equalsIgnoreCase(part.getDisposition())) {
            htmlContent = (String) part.getContent();
        } else if (isMimeType(part, "multipart/*")) {
            isMultiPart = true;
            final Multipart multipart = (Multipart) part.getContent();
            final int count = multipart.getCount();
            // iterate over all MimeBodyPart
            for (int i = 0; i < count; i++) {
                parse(multipart, (MimeBodyPart) multipart.getBodyPart(i));
            }
        } else {
            final String cid = stripContentId(part.getContentID());
            final DataSource dataSource = createDataSource(parent, part);
            if (cid != null) {
                cidMap.put(cid, dataSource);
            }
            attachmentList.add(dataSource);
        }
    }

    /**
     * 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) {
        return contentId == null ? null : contentId.trim().replaceAll("[\\<\\>]", "");
    }
}
File Project Line
org/apache/commons/mail2/jakarta/ImageHtmlEmail.java Apache Commons Email for Jakarta 45
org/apache/commons/mail2/javax/ImageHtmlEmail.java Apache Commons Email for Javax 45
public class ImageHtmlEmail extends HtmlEmail {
    // Regular Expression to find all <IMG SRC="..."> entries in an HTML
    // document.It needs to cater for various things, like more whitespaces
    // including newlines on any place, HTML is not case sensitive and there
    // can be arbitrary text between "IMG" and "SRC" like IDs and other things.

    /** Regexp for extracting {@code <img>} tags */
    public static final String REGEX_IMG_SRC = "(<[Ii][Mm][Gg]\\s*[^>]*?\\s+[Ss][Rr][Cc]\\s*=\\s*[\"'])([^\"']+?)([\"'])";

    /** Regexp for extracting {@code <script>} tags */
    public static final String REGEX_SCRIPT_SRC = "(<[Ss][Cc][Rr][Ii][Pp][Tt]\\s*.*?\\s+[Ss][Rr][Cc]\\s*=\\s*[\"'])([^\"']+?)([\"'])";

    // this pattern looks for the HTML image tag which indicates embedded images,
    // the grouping is necessary to allow to replace the element with the CID

    /** Pattern for extracting {@code <img>} tags */
    private static final Pattern IMG_PATTERN = Pattern.compile(REGEX_IMG_SRC);

    /** Pattern for extracting {@code <script>} tags */
    private static final Pattern SCRIPT_PATTERN = Pattern.compile(REGEX_SCRIPT_SRC);

    /** Resolve the images and script resources to a DataSource */
    private DataSourceResolver dataSourceResolver;

    /**
     * Constructs a new instance.
     */
    public ImageHtmlEmail() {
        // empty
    }

    /**
     * Does the work of actually building the MimeMessage.
     *
     * @see org.apache.commons.mail2.jakarta.HtmlEmail#buildMimeMessage()
     * @throws EmailException building the MimeMessage failed
     */
    @Override
    public void buildMimeMessage() throws EmailException {
        try {
            // embed all the matching image and script resources within the email
            String temp = replacePattern(getHtml(), IMG_PATTERN);
            temp = replacePattern(temp, SCRIPT_PATTERN);
            setHtmlMsg(temp);
            super.buildMimeMessage();
        } catch (final IOException e) {
            throw new EmailException("Building the MimeMessage failed", e);
        }
    }

    /**
     * Gets the data source resolver.
     *
     * @return the resolver
     */
    public DataSourceResolver getDataSourceResolver() {
        return dataSourceResolver;
    }

    /**
     * Replace the regexp matching resource locations with "cid:..." references.
     *
     * @param htmlMessage the HTML message to analyze
     * @param pattern     the regular expression to find resources
     * @return the HTML message containing "cid" references
     * @throws EmailException creating the email failed
     * @throws IOException    resolving the resources failed
     */
    private String replacePattern(final String htmlMessage, final Pattern pattern) throws EmailException, IOException {
        DataSource dataSource;
        final StringBuffer stringBuffer = new StringBuffer();

        // maps "cid" --> name
        final Map<String, String> cidCache = new HashMap<>();

        // maps "name" --> dataSource
        final Map<String, DataSource> dataSourceCache = new HashMap<>();

        // in the String, replace all "img src" with a CID and embed the related
        // image file if we find it.
        final Matcher matcher = pattern.matcher(htmlMessage);

        // the matcher returns all instances one by one
        while (matcher.find()) {
            // in the RegEx we have the <src> element as second "group"
            final String resourceLocation = matcher.group(2);

            // avoid loading the same data source more than once
            if (dataSourceCache.get(resourceLocation) == null) {
                // in lenient mode we might get a 'null' data source if the resource was not found
                dataSource = getDataSourceResolver().resolve(resourceLocation);

                if (dataSource != null) {
                    dataSourceCache.put(resourceLocation, dataSource);
                }
            } else {
                dataSource = dataSourceCache.get(resourceLocation);
            }

            if (dataSource != null) {
                String name = dataSource.getName();
                if (EmailUtils.isEmpty(name)) {
                    name = resourceLocation;
                }

                String cid = cidCache.get(name);

                if (cid == null) {
                    cid = embed(dataSource, name);
                    cidCache.put(name, cid);
                }

                // if we embedded something, then we need to replace the URL with
                // the CID, otherwise the Matcher takes care of adding the
                // non-replaced text afterwards, so no else is necessary here!
                matcher.appendReplacement(stringBuffer, Matcher.quoteReplacement(matcher.group(1) + "cid:" + cid + matcher.group(3)));
            }
        }

        // append the remaining items...
        matcher.appendTail(stringBuffer);

        cidCache.clear();
        dataSourceCache.clear();

        return stringBuffer.toString();
    }

    /**
     * Sets the data source resolver.
     *
     * @param dataSourceResolver the resolver
     */
    public void setDataSourceResolver(final DataSourceResolver dataSourceResolver) {
        this.dataSourceResolver = dataSourceResolver;
    }
}
File Project Line
org/apache/commons/mail2/jakarta/resolver/DataSourceClassPathResolver.java Apache Commons Email for Jakarta 32
org/apache/commons/mail2/javax/resolver/DataSourceClassPathResolver.java Apache Commons Email for Javax 32
public class DataSourceClassPathResolver extends DataSourceBaseResolver {
    /** The base string of the resource relative to the classpath when resolving relative paths */
    private final String classPathBase;

    /**
     * Constructs a new instance.
     */
    public DataSourceClassPathResolver() {
        this("/");
    }

    /**
     * Constructs a new instance.
     *
     * @param classPathBase a base class path
     */
    public DataSourceClassPathResolver(final String classPathBase) {
        this(classPathBase, false);
    }

    /**
     * Constructs a new instance.
     *
     * @param classPathBase a base class path
     * @param lenient       shall we ignore resources not found or throw an exception?
     */
    public DataSourceClassPathResolver(final String classPathBase, final boolean lenient) {
        super(lenient);
        this.classPathBase = classPathBase.endsWith("/") ? classPathBase : classPathBase + "/";
    }

    /**
     * Gets the class path base.
     *
     * @return the classPathBase
     */
    public String getClassPathBase() {
        return classPathBase;
    }

    /**
     * Returns the resource name for a given resource location.
     *
     * @param resourceLocation the resource location
     * @return {@link #getClassPathBase()} + {@code resourceLocation}
     * @see #getClassPathBase()
     */
    private String getResourceName(final String resourceLocation) {
        return (getClassPathBase() + resourceLocation).replace("//", "/");
    }

    /** {@inheritDoc} */
    @Override
    public DataSource resolve(final String resourceLocation) throws IOException {
        return resolve(resourceLocation, isLenient());
    }

    /** {@inheritDoc} */
    @Override
    public DataSource resolve(final String resourceLocation, final boolean isLenient) throws IOException {
        try {
            if (!isCid(resourceLocation) && !isHttpUrl(resourceLocation)) {
                final String mimeType = FileTypeMap.getDefaultFileTypeMap().getContentType(resourceLocation);
                final String resourceName = getResourceName(resourceLocation);
                try (InputStream inputStream = DataSourceClassPathResolver.class.getResourceAsStream(resourceName)) {
                    if (inputStream == null) {
                        if (isLenient) {
                            return null;
                        }
                        throw new IOException("The following class path resource was not found : " + resourceLocation);
                    }
                    final ByteArrayDataSource ds = new ByteArrayDataSource(inputStream, mimeType);
                    // EMAIL-125: set the name of the DataSource to the normalized resource URL
                    // similar to other DataSource implementations, e.g. FileDataSource, URLDataSource
                    final URL resource = DataSourceClassPathResolver.class.getResource(resourceName);
                    if (resource != null) {
                        ds.setName(resource.toString());
                    } else if (isLenient) {
                        return null;
                    } else {
                        throw new IOException("The following class path resource was not found : " + resourceName);
                    }
                    return ds;
                }
            }
            return null;
        } catch (final IOException e) {
            if (isLenient) {
                return null;
            }
            throw e;
        }
    }
}
File Project Line
org/apache/commons/mail2/jakarta/util/MimeMessageUtils.java Apache Commons Email for Jakarta 40
org/apache/commons/mail2/javax/util/MimeMessageUtils.java Apache Commons Email for Javax 40
public final class MimeMessageUtils {

    /**
     * Creates a MimeMessage.
     *
     * @param session the mail session.
     * @param source  the input data.
     * @return the MimeMessage.
     * @throws MessagingException creating the MimeMessage failed.
     * @throws IOException        creating the MimeMessage failed.
     */
    public static MimeMessage createMimeMessage(final Session session, final byte[] source) throws MessagingException, IOException {
        try (InputStream inputStream = new SharedByteArrayInputStream(source)) {
            return new MimeMessage(session, inputStream);
        }
    }

    /**
     * Creates a MimeMessage.
     *
     * @param session the mail session.
     * @param source  the input data.
     * @return the MimeMessage.
     * @throws MessagingException creating the MimeMessage failed.
     * @throws IOException        creating the MimeMessage failed.
     */
    public static MimeMessage createMimeMessage(final Session session, final File source) throws MessagingException, IOException {
        try (InputStream inputStream = new FileInputStream(source)) {
            return createMimeMessage(session, inputStream);
        }
    }

    /**
     * Creates a MimeMessage.
     *
     * @param session the mail session.
     * @param source  the input data.
     * @return the MimeMessage.
     * @throws MessagingException creating the MimeMessage failed.
     */
    public static MimeMessage createMimeMessage(final Session session, final InputStream source) throws MessagingException {
        return new MimeMessage(session, source);
    }

    /**
     * Creates a MimeMessage.
     *
     * @param session the mail session.
     * @param source  the input data.
     * @param options options specifying how the file is opened.
     * @return the MimeMessage.
     * @throws MessagingException creating the MimeMessage failed.
     * @throws IOException        creating the MimeMessage failed.
     */
    public static MimeMessage createMimeMessage(final Session session, final Path source, final OpenOption... options) throws MessagingException, IOException {
        try (InputStream inputStream = Files.newInputStream(source, options)) {
            return createMimeMessage(session, inputStream);
        }
    }

    /**
     * Creates a MimeMessage using the platform's default character encoding.
     *
     * @param session the mail session.
     * @param source  the input data.
     * @return the MimeMessage.
     * @throws MessagingException creating the MimeMessage failed.
     * @throws IOException        creating the MimeMessage failed.
     */
    public static MimeMessage createMimeMessage(final Session session, final String source) throws MessagingException, IOException {
        // RFC1341: https://www.w3.org/Protocols/rfc1341/7_1_Text.html
        return createMimeMessage(session, source.getBytes(StandardCharsets.US_ASCII));
    }

    /**
     * Writes a MimeMessage into a file.
     *
     * @param mimeMessage the MimeMessage to write.
     * @param resultFile  the file containing the MimeMessage.
     * @throws MessagingException accessing MimeMessage failed.
     * @throws IOException        writing the MimeMessage failed.
     */
    public static void writeMimeMessage(final MimeMessage mimeMessage, final File resultFile) throws MessagingException, IOException {
        if (!resultFile.getParentFile().exists() && !resultFile.getParentFile().mkdirs()) {
            throw new IOException("Failed to create the following parent directories: " + resultFile.getParentFile());
        }
        try (OutputStream outputStream = new FileOutputStream(resultFile)) {
            mimeMessage.writeTo(outputStream);
            outputStream.flush();
        }
    }

    /**
     * Instances should NOT be constructed in standard programming.
     */
    private MimeMessageUtils() {
    }
}
File Project Line
org/apache/commons/mail2/jakarta/resolver/DataSourceUrlResolver.java Apache Commons Email for Jakarta 34
org/apache/commons/mail2/javax/resolver/DataSourceUrlResolver.java Apache Commons Email for Javax 34
public class DataSourceUrlResolver extends DataSourceBaseResolver {

    /** The base url of the resource when resolving relative paths */
    private final URL baseUrl;

    /**
     * Constructs a new instance.
     *
     * @param baseUrl the base URL used for resolving relative resource locations
     */
    public DataSourceUrlResolver(final URL baseUrl) {
        this.baseUrl = baseUrl;
    }

    /**
     * Constructs a new instance.
     *
     * @param baseUrl the base URL used for resolving relative resource locations
     * @param lenient shall we ignore resources not found or complain with an exception
     */
    public DataSourceUrlResolver(final URL baseUrl, final boolean lenient) {
        super(lenient);
        this.baseUrl = baseUrl;
    }

    /**
     * Create an URL based on a base URL and a resource location suitable for loading the resource.
     *
     * @param resourceLocation a resource location
     * @return the corresponding URL
     * @throws java.net.MalformedURLException creating the URL failed
     */
    protected URL createUrl(final String resourceLocation) throws MalformedURLException {
        // if we get an non-existing base url than the resource can
        // be directly used to create an URL
        if (baseUrl == null) {
            return new URL(resourceLocation);
        }
        // if we get an non-existing location what we shall do?
        if (EmailUtils.isEmpty(resourceLocation)) {
            throw new IllegalArgumentException("No resource defined");
        }
        // if we get a stand-alone resource than ignore the base url
        if (isFileUrl(resourceLocation) || isHttpUrl(resourceLocation)) {
            return new URL(resourceLocation);
        }
        return new URL(getBaseUrl(), resourceLocation.replace("&amp;", "&"));
    }

    /**
     * Gets the base URL used for resolving relative resource locations.
     *
     * @return the baseUrl
     */
    public URL getBaseUrl() {
        return baseUrl;
    }

    /** {@inheritDoc} */
    @Override
    public DataSource resolve(final String resourceLocation) throws IOException {
        return resolve(resourceLocation, isLenient());
    }

    /** {@inheritDoc} */
    @Override
    public DataSource resolve(final String resourceLocation, final boolean isLenient) throws IOException {
        DataSource result = null;
        try {
            if (!isCid(resourceLocation)) {
                result = new URLDataSource(createUrl(resourceLocation));
                // validate we can read.
                try (InputStream inputStream = result.getInputStream()) {
                    inputStream.read();
                }
            }
            return result;
        } catch (final IOException e) {
            if (isLenient) {
                return null;
            }
            throw e;
        }
    }
}
File Project Line
org/apache/commons/mail2/jakarta/resolver/DataSourcePathResolver.java Apache Commons Email for Jakarta 35
org/apache/commons/mail2/javax/resolver/DataSourcePathResolver.java Apache Commons Email for Javax 35
public final class DataSourcePathResolver extends DataSourceBaseResolver {

    /**
     * The base directory of the resource when resolving relative paths.
     */
    private final Path baseDir;

    /**
     * NIO options to open the data source.
     */
    private final OpenOption[] options;

    /**
     * Constructs a new instance.
     */
    public DataSourcePathResolver() {
        this(Paths.get("."));
    }

    /**
     * Constructs a new instance.
     *
     * @param baseDir the base directory of the resource when resolving relative paths
     */
    public DataSourcePathResolver(final Path baseDir) {
        this(baseDir, false);
    }

    /**
     * Constructs a new instance.
     *
     * @param baseDir the base directory of the resource when resolving relative paths
     * @param lenient shall we ignore resources not found or complain with an exception
     * @param options options for opening streams.
     */
    public DataSourcePathResolver(final Path baseDir, final boolean lenient, final OpenOption... options) {
        super(lenient);
        this.baseDir = baseDir;
        this.options = options;
    }

    /**
     * Gets the base directory used for resolving relative resource locations.
     *
     * @return the baseUrl
     */
    public Path getBaseDir() {
        return baseDir;
    }

    /** {@inheritDoc} */
    @Override
    public DataSource resolve(final String resourceLocation) throws IOException {
        return resolve(resourceLocation, isLenient());
    }

    /** {@inheritDoc} */
    @Override
    public DataSource resolve(final String resourceLocation, final boolean isLenient) throws IOException {
        Path file;
        DataSource result = null;

        if (!isCid(resourceLocation)) {
            file = Paths.get(resourceLocation);

            if (!file.isAbsolute()) {
                file = getBaseDir() != null ? getBaseDir().resolve(resourceLocation) : Paths.get(resourceLocation);
            }

            if (Files.exists(file)) {
                result = new PathDataSource(file, FileTypeMap.getDefaultFileTypeMap(), options);
            } else if (!isLenient) {
                throw new IOException("Cant resolve the following file resource :" + file.toAbsolutePath());
            }
        }

        return result;
    }
}
File Project Line
org/apache/commons/mail2/jakarta/resolver/DataSourceFileResolver.java Apache Commons Email for Jakarta 30
org/apache/commons/mail2/javax/resolver/DataSourceFileResolver.java Apache Commons Email for Javax 30
public class DataSourceFileResolver extends DataSourceBaseResolver {

    /** The base directory of the resource when resolving relative paths */
    private final File baseDir;

    /**
     * Constructs a new instance.
     */
    public DataSourceFileResolver() {
        baseDir = new File(".");
    }

    /**
     * Constructs a new instance.
     *
     * @param baseDir the base directory of the resource when resolving relative paths
     */
    public DataSourceFileResolver(final File baseDir) {
        this.baseDir = baseDir;
    }

    /**
     * Constructs a new instance.
     *
     * @param baseDir the base directory of the resource when resolving relative paths
     * @param lenient shall we ignore resources not found or complain with an exception
     */
    public DataSourceFileResolver(final File baseDir, final boolean lenient) {
        super(lenient);
        this.baseDir = baseDir;
    }

    /**
     * Gets the base directory used for resolving relative resource locations.
     *
     * @return the baseUrl
     */
    public File getBaseDir() {
        return baseDir;
    }

    /** {@inheritDoc} */
    @Override
    public DataSource resolve(final String resourceLocation) throws IOException {
        return resolve(resourceLocation, isLenient());
    }

    /** {@inheritDoc} */
    @Override
    public DataSource resolve(final String resourceLocation, final boolean isLenient) throws IOException {
        File file;
        DataSource result = null;

        if (!isCid(resourceLocation)) {
            file = new File(resourceLocation);

            if (!file.isAbsolute()) {
                file = getBaseDir() != null ? new File(getBaseDir(), resourceLocation) : new File(resourceLocation);
            }

            if (file.exists()) {
                result = new FileDataSource(file);
            } else if (!isLenient) {
                throw new IOException("Cant resolve the following file resource :" + file.getAbsolutePath());
            }
        }

        return result;
    }
}
File Project Line
org/apache/commons/mail2/jakarta/resolver/DataSourceCompositeResolver.java Apache Commons Email for Jakarta 30
org/apache/commons/mail2/javax/resolver/DataSourceCompositeResolver.java Apache Commons Email for Javax 30
public class DataSourceCompositeResolver extends DataSourceBaseResolver {

    /** The list of resolvers */
    private final DataSourceResolver[] dataSourceResolvers;

    /**
     * Constructs a new instance.
     *
     * @param dataSourceResolvers a list of resolvers being used
     */
    public DataSourceCompositeResolver(final DataSourceResolver[] dataSourceResolvers) {
        this.dataSourceResolvers = dataSourceResolvers.clone();
    }

    /**
     * Constructs a new instance.
     *
     * @param dataSourceResolvers a list of resolvers being used
     * @param isLenient           shall we ignore resources not found or throw an exception?
     */
    public DataSourceCompositeResolver(final DataSourceResolver[] dataSourceResolvers, final boolean isLenient) {
        super(isLenient);
        this.dataSourceResolvers = dataSourceResolvers.clone();
    }

    /**
     * Gets the underlying data source resolvers.
     *
     * @return underlying data source resolvers
     */
    public DataSourceResolver[] getDataSourceResolvers() {
        // clone the internal array to prevent external modification (see EMAIL-116)
        return dataSourceResolvers.clone();
    }

    /** {@inheritDoc} */
    @Override
    public DataSource resolve(final String resourceLocation) throws IOException {
        final DataSource result = resolve(resourceLocation, true);
        if (isLenient() || result != null) {
            return result;
        }
        throw new IOException("The following resource was not found : " + resourceLocation);

    }

    /** {@inheritDoc} */
    @Override
    public DataSource resolve(final String resourceLocation, final boolean isLenient) throws IOException {
        for (final DataSourceResolver dataSourceResolver : dataSourceResolvers) {
            final DataSource dataSource = dataSourceResolver.resolve(resourceLocation, isLenient);
            if (dataSource != null) {
                return dataSource;
            }
        }
        if (isLenient) {
            return null;
        }
        throw new IOException("The following resource was not found : " + resourceLocation);
    }
}
File Project Line
org/apache/commons/mail2/jakarta/util/IDNEmailAddressConverter.java Apache Commons Email for Jakarta 34
org/apache/commons/mail2/javax/util/IDNEmailAddressConverter.java Apache Commons Email for Javax 34
public class IDNEmailAddressConverter {

    /**
     * Constructs a new instance.
     */
    public IDNEmailAddressConverter() {
        // empty
    }

    /**
     * Extracts the domain part of the email address.
     *
     * @param email email address.
     * @param idx   index of '@' character.
     * @return domain part of email
     */
    private String getDomainPart(final String email, final int idx) {
        return email.substring(idx + 1);
    }

    /**
     * Extracts the local part of the email address.
     *
     * @param email email address.
     * @param idx   index of '@' character.
     * @return local part of email
     */
    private String getLocalPart(final String email, final int idx) {
        return email.substring(0, idx);
    }

    /**
     * Converts an email address to its ASCII representation using "Punycode".
     *
     * @param email email address.
     * @return The ASCII representation
     */
    public String toASCII(final String email) {
        return toString(email, IDN::toASCII);
    }

    private String toString(final String email, final Function<String, String> converter) {
        final int idx = email == null ? -1 : email.indexOf('@');
        if (idx < 0) {
            return email;
        }
        return getLocalPart(email, idx) + '@' + converter.apply(getDomainPart(email, idx));
    }

    /**
     * Converts the address part of an InternetAddress to its Unicode representation.
     *
     * @param address email address.
     * @return The Unicode representation
     */
    String toUnicode(final InternetAddress address) {
        return address != null ? toUnicode(address.getAddress()) : null;
    }

    /**
     * Converts an "Punycode" email address to its Unicode representation.
     *
     * @param email email address.
     * @return The Unicode representation
     */
    String toUnicode(final String email) {
        return toString(email, IDN::toUnicode);
    }
}
File Project Line
org/apache/commons/mail2/jakarta/activation/PathDataSource.java Apache Commons Email for Jakarta 41
org/apache/commons/mail2/javax/activation/PathDataSource.java Apache Commons Email for Javax 41
public final class PathDataSource implements DataSource {

    /**
     * The source.
     */
    private final Path path;

    /**
     * Defaults to {@link FileTypeMap#getDefaultFileTypeMap()}.
     */
    private final FileTypeMap typeMap;

    /**
     * NIO options to open the source Path.
     */
    private final OpenOption[] options;

    /**
     * Creates a new instance from a Path.
     * <p>
     * The file will not actually be opened until a method is called that requires the path to be opened.
     * </p>
     * <p>
     * The type map defaults to {@link FileTypeMap#getDefaultFileTypeMap()}.
     *
     * @param path the path
     */
    public PathDataSource(final Path path) {
        this(path, FileTypeMap.getDefaultFileTypeMap());
    }

    /**
     * Creates a new instance from a Path.
     * <p>
     * The file will not actually be opened until a method is called that requires the path to be opened.
     * </p>
     *
     * @param path    the path, non-null.
     * @param typeMap the type map, non-null.
     * @param options options for opening file streams.
     */
    public PathDataSource(final Path path, final FileTypeMap typeMap, final OpenOption... options) {
        this.path = Objects.requireNonNull(path, "path");
        this.typeMap = Objects.requireNonNull(typeMap, "typeMap");
        this.options = options;
    }

    /**
     * Gets the MIME type of the data as a String. This method uses the currently installed FileTypeMap. If there is no FileTypeMap explicitly set, the
     * FileDataSource will call the {@link FileTypeMap#getDefaultFileTypeMap} method to acquire a default FileTypeMap.
     * <p>
     * By default, the {@link FileTypeMap} used will be a {@link MimetypesFileTypeMap}.
     * </p>
     *
     * @return the MIME Type
     * @see FileTypeMap#getDefaultFileTypeMap
     */
    @Override
    public String getContentType() {
        return typeMap.getContentType(getName());
    }

    /**
     * Gets an InputStream representing the the data and will throw an IOException if it can not do so. This method will return a new instance of InputStream
     * with each invocation.
     *
     * @return an InputStream
     */
    @Override
    public InputStream getInputStream() throws IOException {
        return Files.newInputStream(path, options);
    }

    /**
     * Gets the <em>name</em> of this object. The FileDataSource will return the file name of the object.
     *
     * @return the name of the object or null.
     * @see jakarta.activation.DataSource
     */
    @Override
    public String getName() {
        return Objects.toString(path.getFileName(), null);
    }

    /**
     * Gets an OutputStream representing the the data and will throw an IOException if it can not do so. This method will return a new instance of OutputStream
     * with each invocation.
     *
     * @return an OutputStream
     */
    @Override
    public OutputStream getOutputStream() throws IOException {
        return Files.newOutputStream(path, options);
    }

    /**
     * Gets the File object that corresponds to this PathDataSource.
     *
     * @return the File object for the file represented by this object.
     */
    public Path getPath() {
        return path;
    }

}
File Project Line
org/apache/commons/mail2/jakarta/EmailAttachment.java Apache Commons Email for Jakarta 32
org/apache/commons/mail2/javax/EmailAttachment.java Apache Commons Email for Javax 32
public static final String INLINE = jakarta.mail.Part.INLINE;

    /** The name of this attachment. */
    private String name = "";

    /** The description of this attachment. */
    private String description = "";

    /** The path to this attachment (ie c:/path/to/file.jpg). */
    private String path = "";

    /** The HttpURI where the file can be got. */
    private URL url;

    /** The disposition. */
    private String disposition = ATTACHMENT;

    /**
     * Constructs a new instance.
     */
    public EmailAttachment() {
        // empty
    }

    /**
     * Gets the description.
     *
     * @return A String.
     * @since 1.0
     */
    public String getDescription() {
        return description;
    }

    /**
     * Gets the disposition.
     *
     * @return A String.
     * @since 1.0
     */
    public String getDisposition() {
        return disposition;
    }

    /**
     * Gets the name.
     *
     * @return A String.
     * @since 1.0
     */
    public String getName() {
        return name;
    }

    /**
     * Gets the path.
     *
     * @return A String.
     * @since 1.0
     */
    public String getPath() {
        return path;
    }

    /**
     * Gets the URL.
     *
     * @return A URL.
     * @since 1.0
     */
    public URL getURL() {
        return url;
    }

    /**
     * Sets the description.
     *
     * @param desc A String.
     * @since 1.0
     */
    public void setDescription(final String desc) {
        this.description = desc;
    }

    /**
     * Sets the disposition.
     *
     * @param aDisposition A String.
     * @since 1.0
     */
    public void setDisposition(final String aDisposition) {
        this.disposition = aDisposition;
    }

    /**
     * Sets the name.
     *
     * @param aName A String.
     * @since 1.0
     */
    public void setName(final String aName) {
        this.name = aName;
    }

    /**
     * Sets the path to the attachment. The path can be absolute or relative and should include the file name.
     * <p>
     * Example: /home/user/images/image.jpg<br>
     * Example: images/image.jpg
     *
     * @param aPath A String.
     * @since 1.0
     */
    public void setPath(final String aPath) {
        this.path = aPath;
    }

    /**
     * Sets the URL.
     *
     * @param aUrl A URL.
     * @since 1.0
     */
    public void setURL(final URL aUrl) {
        this.url = aUrl;
    }
}
File Project Line
org/apache/commons/mail2/jakarta/activation/InputStreamDataSource.java Apache Commons Email for Jakarta 34
org/apache/commons/mail2/javax/activation/InputStreamDataSource.java Apache Commons Email for Javax 34
public final class InputStreamDataSource implements DataSource {

    /**
     * Default content type documented in {@link DataSource#getContentType()}.
     */
    private static final String DEFAULT_CONTENT_TYPE = "application/octet-stream";

    /**
     * The MIME content type.
     */
    private final String contentType;

    /**
     * The source.
     */
    private final InputStream inputStream;

    /**
     * The optional name.
     */
    private final String name;

    /**
     * Constructs a new instance.
     *
     * @param inputStream An input stream.
     * @param contentType A content type.
     */
    public InputStreamDataSource(final InputStream inputStream, final String contentType) {
        this(inputStream, contentType, null);
    }

    /**
     * Constructs a new instance.
     *
     * @param inputStream An input stream.
     * @param contentType A content type.
     * @param name        A name.
     */
    public InputStreamDataSource(final InputStream inputStream, final String contentType, final String name) {
        this.inputStream = inputStream;
        this.contentType = contentType != null ? contentType : DEFAULT_CONTENT_TYPE;
        this.name = name;
    }

    @Override
    public String getContentType() {
        return contentType;
    }

    @Override
    public InputStream getInputStream() throws IOException {
        return inputStream;
    }

    @Override
    public String getName() {
        return name;
    }

    /**
     * Always throws {@link UnsupportedOperationException}.
     *
     * @return Always throws {@link UnsupportedOperationException}.
     * @throws UnsupportedOperationException Always throws {@link UnsupportedOperationException}.
     */
    @Override
    public OutputStream getOutputStream() {
        throw new UnsupportedOperationException();
    }

}
File Project Line
org/apache/commons/mail2/jakarta/resolver/DataSourceBaseResolver.java Apache Commons Email for Jakarta 26
org/apache/commons/mail2/javax/resolver/DataSourceBaseResolver.java Apache Commons Email for Javax 26
public abstract class DataSourceBaseResolver implements DataSourceResolver {

    /** Whether to ignore resources not found or complain with an exception. */
    private final boolean lenient;

    /**
     * Constructs a new instance.
     */
    public DataSourceBaseResolver() {
        this.lenient = false;
    }

    /**
     * Constructs a new instance.
     *
     * @param lenient shall we ignore resources not found or throw an exception?
     */
    public DataSourceBaseResolver(final boolean lenient) {
        this.lenient = lenient;
    }

    /**
     * Tests whether the argument is a content id.
     *
     * @param resourceLocation the resource location to test.
     * @return true if it is a CID.
     */
    protected boolean isCid(final String resourceLocation) {
        return resourceLocation.startsWith("cid:");
    }

    /**
     * Tests whether this a file URL.
     *
     * @param urlString the URL string.
     * @return true if it is a file URL.
     */
    protected boolean isFileUrl(final String urlString) {
        return urlString.startsWith("file:/");
    }

    /**
     * Tests whether this a HTTP or HTTPS URL.
     *
     * @param urlString the URL string to test.
     * @return true if it is a HTTP or HTTPS URL.
     */
    protected boolean isHttpUrl(final String urlString) {
        return urlString.startsWith("http://") || urlString.startsWith("https://");
    }

    /**
     * Tests whether to ignore resources not found or throw an exception.
     *
     * @return the lenient flag.
     */
    public boolean isLenient() {
        return lenient;
    }
}