Package org.apache.james.transport.mailets

Source Code of org.apache.james.transport.mailets.AbstractRedirect

/****************************************************************
* Licensed to the Apache Software Foundation (ASF) under one   *
* or more contributor license agreements.  See the NOTICE file *
* distributed with this work for additional information        *
* regarding copyright ownership.  The ASF licenses this file   *
* to you under the Apache License, Version 2.0 (the            *
* "License"); you may not use this file except in compliance   *
* with the License.  You may obtain a copy of the License at   *
*                                                              *
*   http://www.apache.org/licenses/LICENSE-2.0                 *
*                                                              *
* Unless required by applicable law or agreed to in writing,   *
* software distributed under the License is distributed on an  *
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
* KIND, either express or implied.  See the License for the    *
* specific language governing permissions and limitations      *
* under the License.                                           *
****************************************************************/

package org.apache.james.transport.mailets;

import com.google.common.base.Throwables;
import org.apache.james.core.MailImpl;
import org.apache.james.core.MimeMessageUtil;
import org.apache.james.dnsservice.api.DNSService;
import org.apache.mailet.Mail;
import org.apache.mailet.MailAddress;
import org.apache.mailet.base.GenericMailet;
import org.apache.mailet.base.RFC2822Headers;
import org.apache.mailet.base.RFC822DateFormat;

import javax.inject.Inject;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.ParseException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Locale;

/**
* <p>
* Abstract mailet providing configurable redirection services.<br>
* This mailet can be subclassed to make authoring redirection mailets simple.<br>
* By extending it and overriding one or more of these methods new behaviour can
* be quickly created without the author having to address any other issue than
* the relevant one:
* </p>
* <ul>
* <li>attachError() , should error messages be appended to the message</li>
* <li>getAttachmentType(), what should be attached to the message</li>
* <li>getInLineType(), what should be included in the message</li>
* <li>getMessage(), The text of the message itself</li>
* <li>getRecipients(), the recipients the mail is sent to</li>
* <li>getReplyTo(), where replies to this message will be sent</li>
* <li>getReversePath(), what to set the reverse-path to</li>
* <li>getSender(), who the mail is from</li>
* <li>getSubject(), a string to replace the message subject</li>
* <li>getSubjectPrefix(), a prefix to be added to the message subject, possibly
* already replaced by a new subject</li>
* <li>getTo(), a list of people to whom the mail is *apparently* sent</li>
* <li>isReply(), should this mailet set the IN_REPLY_TO header to the id of the
* current message</li>
* <li>getPassThrough(), should this mailet allow the original message to
* continue processing or GHOST it.</li>
* <li>getFakeDomainCheck(), should this mailet check if the sender domain
* address is valid.</li>
* <li>isStatic(), should this mailet run the get methods for every mail, or
* just once.</li>
* </ul>
* <p>
* For each of the methods above (generically called "getX()" methods in this
* class and its subclasses), there is an associated "getX(Mail)" method and
* most times a "setX(Mail, Tx, Mail)" method.<br>
* The roles are the following:
* </p>
* <ul>
* <li>a "getX()" method returns the correspondent "X" value that can be
* evaluated "statically" once at init time and then stored in a variable and
* made available for later use by a "getX(Mail)" method;</li>
* <li>a "getX(Mail)" method is the one called to return the correspondent "X"
* value that can be evaluated "dynamically", tipically based on the currently
* serviced mail; the default behaviour is to return the value of getX();</li>
* <li>a "setX(Mail, Tx, Mail)" method is called to change the correspondent "X"
* value of the redirected Mail object, using the value returned by
* "gexX(Mail)"; if such value is null, it does nothing.</li>
* </ul>
* <p>
* Here follows the typical pattern of those methods:
* </p>
* <p/>
* <pre>
* <code>
*    ...
*    Tx x;
*    ...
*    protected boolean getX(Mail originalMail) throws MessagingException {
*        boolean x = (isStatic()) ? this.x : getX();
*        ...
*        return x;
*    }
*    ...
*    public void init() throws MessagingException {
*        ...
*        isStatic = (getInitParameter("static") == null) ? false : new Boolean(getInitParameter("static")).booleanValue();
*        if(isStatic()) {
*            ...
*            X  = getX();
*            ...
*        }
*    ...
*    public void service(Mail originalMail) throws MessagingException {
*    ...
*    setX(newMail, getX(originalMail), originalMail);
*    ...
*    }
*    ...
* </code>
* </pre>
* <p>
* The <i>isStatic</i> variable and method is used to allow for the situations
* (deprecated since version 2.2, but possibly used by previoulsy written
* extensions to {@link Redirect}) in which the getX() methods are non static:
* in this case {@link #isStatic()} must return false.<br>
* Finally, a "getX()" method may return a "special address" (see
* {@link SpecialAddress}), that later will be resolved ("late bound") by a
* "getX(Mail)" or "setX(Mail, Tx, Mail)": it is a dynamic value that does not
* require <code>isStatic</code> to be false.
* </p>
* <p/>
* <p>
* Supports by default the <code>passThrough</code> init parameter (false if
* missing). Subclasses can override this behaviour overriding
* {@link #getPassThrough()}.
* </p>
*
* @since 2.2.0
*/

public abstract class AbstractRedirect extends GenericMailet {

    /**
     * Gets the expected init parameters.
     *
     * @return null meaning no check
     */
    protected String[] getAllowedInitParameters() {
        return null;
    }

    /**
     * Controls certain log messages.
     */
    protected boolean isDebug = false;

    /**
     * Holds the value of the <code>static</code> init parameter.
     */
    protected boolean isStatic = false;

    private static class AddressMarker {
        public static final MailAddress SENDER = mailAddressUncheckedException("sender", "address.marker");
        public static final MailAddress REVERSE_PATH = mailAddressUncheckedException("reverse.path", "address.marker");
        public static final MailAddress FROM = mailAddressUncheckedException("from", "address.marker");
        public static final MailAddress REPLY_TO = mailAddressUncheckedException("reply.to", "address.marker");
        public static final MailAddress TO = mailAddressUncheckedException("to", "address.marker");
        public static final MailAddress RECIPIENTS = mailAddressUncheckedException("recipients", "address.marker");
        public static final MailAddress DELETE = mailAddressUncheckedException("delete", "address.marker");
        public static final MailAddress UNALTERED = mailAddressUncheckedException("unaltered", "address.marker");
        public static final MailAddress NULL = mailAddressUncheckedException("null", "address.marker");

        private static MailAddress mailAddressUncheckedException(String localPart, String domain) {
            try {
                return new MailAddress(localPart, domain);
            } catch (Exception e) {
                throw Throwables.propagate(e);
            }
        }
    }

    /**
     * Class containing "special addresses" constants. Such addresses mean
     * dynamic values that later will be resolved ("late bound") by a
     * "getX(Mail)" or "setX(Mail, Tx, Mail)".
     */
    protected static class SpecialAddress {
        public static final MailAddress SENDER = AddressMarker.SENDER;
        public static final MailAddress REVERSE_PATH = AddressMarker.REVERSE_PATH;
        public static final MailAddress FROM = AddressMarker.FROM;
        public static final MailAddress REPLY_TO = AddressMarker.REPLY_TO;
        public static final MailAddress TO = AddressMarker.TO;
        public static final MailAddress RECIPIENTS = AddressMarker.RECIPIENTS;
        public static final MailAddress DELETE = AddressMarker.DELETE;
        public static final MailAddress UNALTERED = AddressMarker.UNALTERED;
        public static final MailAddress NULL = AddressMarker.NULL;
    }

    // The values that indicate how to attach the original mail
    // to the new mail.

    protected static final int UNALTERED = 0;

    protected static final int HEADS = 1;

    protected static final int BODY = 2;

    protected static final int ALL = 3;

    protected static final int NONE = 4;

    protected static final int MESSAGE = 5;

    private boolean passThrough = false;
    private boolean fakeDomainCheck = true;
    private int attachmentType = NONE;
    private int inLineType = BODY;
    private String messageText;
    private Collection recipients;
    private MailAddress replyTo;
    private MailAddress reversePath;
    private MailAddress sender;
    private String subject;
    private String subjectPrefix;
    private InternetAddress[] apparentlyTo;
    private boolean attachError = false;
    private boolean isReply = false;

    private final RFC822DateFormat rfc822DateFormat = new RFC822DateFormat();

    protected DNSService dns;

    @Inject
    public void setDNSService(DNSService dns) {
        this.dns = dns;
    }

    /**
     * <p>
     * Gets the <code>static</code> property.
     * </p>
     * <p>
     * Return true to reduce calls to getTo, getSender, getRecipients,
     * getReplyTo, getReversePath amd getMessage where these values don't change
     * (eg hard coded, or got at startup from the mailet config); return false
     * where any of these methods generate their results dynamically eg in
     * response to the message being processed, or by reference to a repository
     * of users.
     * </p>
     * <p>
     * It is now (from version 2.2) somehow obsolete, as should be always true
     * because the "good practice" is to use "getX()" methods statically, and
     * use instead "getX(Mail)" methods for dynamic situations. A false value is
     * now meaningful only for subclasses of {@link Redirect} older than version
     * 2.2 that were relying on this.
     * </p>
     * <p/>
     * <p>
     * Is a "getX()" method.
     * </p>
     *
     * @return true, as normally "getX()" methods shouls be static
     */
    protected boolean isStatic() {
        return true;
    }

    /**
     * Gets the <code>passThrough</code> property. Return true to allow the
     * original message to continue through the processor, false to GHOST it. Is
     * a "getX()" method.
     *
     * @return the <code>passThrough</code> init parameter, or false if missing
     */
    protected boolean getPassThrough() {
        return Boolean.valueOf(getInitParameter("passThrough"));
    }

    /**
     * Gets the <code>passThrough</code> property, built dynamically using the
     * original Mail object. Is a "getX(Mail)" method.
     *
     * @return {@link #getPassThrough()}
     */
    protected boolean getPassThrough(Mail originalMail) throws MessagingException {
        return (isStatic()) ? this.passThrough : getPassThrough();
    }

    /**
     * Gets the <code>fakeDomainCheck</code> property. Return true to check if
     * the sender domain is valid. Is a "getX()" method.
     *
     * @return the <code>fakeDomainCheck</code> init parameter, or true if
     *         missing
     */
    protected boolean getFakeDomainCheck() {
        return Boolean.valueOf(getInitParameter("fakeDomainCheck"));
    }

    /**
     * Gets the <code>fakeDomainCheck</code> property, built dynamically using
     * the original Mail object. Is a "getX(Mail)" method.
     *
     * @return {@link #getFakeDomainCheck()}
     */
    protected boolean getFakeDomainCheck(Mail originalMail) throws MessagingException {
        return (isStatic()) ? this.fakeDomainCheck : getFakeDomainCheck();
    }

    /**
     * Gets the <code>inline</code> property. May return one of the following
     * values to indicate how to append the original message to build the new
     * message:
     * <ul>
     * <li><code>UNALTERED</code> : original message is the new message body</li>
     * <li><code>BODY</code> : original message body is appended to the new
     * message</li>
     * <li><code>HEADS</code> : original message headers are appended to the new
     * message</li>
     * <li><code>ALL</code> : original is appended with all headers</li>
     * <li><code>NONE</code> : original is not appended</li>
     * </ul>
     * Is a "getX()" method.
     *
     * @return the <code>inline</code> init parameter, or <code>UNALTERED</code>
     *         if missing
     */
    protected int getInLineType() {
        return getTypeCode(getInitParameter("inline", "unaltered"));
    }

    /**
     * Gets the <code>inline</code> property, built dynamically using the
     * original Mail object. Is a "getX(Mail)" method.
     *
     * @return {@link #getInLineType()}
     */
    protected int getInLineType(Mail originalMail) throws MessagingException {
        return (isStatic()) ? this.inLineType : getInLineType();
    }

    /**
     * Gets the <code>attachment</code> property. May return one of the
     * following values to indicate how to attach the original message to the
     * new message:
     * <ul>
     * <li><code>BODY</code> : original message body is attached as plain text
     * to the new message</li>
     * <li><code>HEADS</code> : original message headers are attached as plain
     * text to the new message</li>
     * <li><code>ALL</code> : original is attached as plain text with all
     * headers</li>
     * <li><code>MESSAGE</code> : original message is attached as type
     * message/rfc822, a complete mail message.</li>
     * <li><code>NONE</code> : original is not attached</li>
     * </ul>
     * Is a "getX()" method.
     *
     * @return the <code>attachment</code> init parameter, or <code>NONE</code>
     *         if missing
     */
    protected int getAttachmentType() {
        return getTypeCode(getInitParameter("attachment", "none"));
    }

    /**
     * Gets the <code>attachment</code> property, built dynamically using the
     * original Mail object. Is a "getX(Mail)" method.
     *
     * @return {@link #getAttachmentType()}
     */
    protected int getAttachmentType(Mail originalMail) throws MessagingException {
        return (isStatic()) ? this.attachmentType : getAttachmentType();
    }

    /**
     * Gets the <code>message</code> property. Returns a message to which the
     * original message can be attached/appended to build the new message. Is a
     * "getX()" method.
     *
     * @return the <code>message</code> init parameter or an empty string if
     *         missing
     */
    protected String getMessage() {
        return getInitParameter("message", "");
    }

    /**
     * Gets the <code>message</code> property, built dynamically using the
     * original Mail object. Is a "getX(Mail)" method.
     *
     * @return {@link #getMessage()}
     */
    protected String getMessage(Mail originalMail) throws MessagingException {
        return (isStatic()) ? this.messageText : getMessage();
    }

    /**
     * Gets the <code>recipients</code> property. Returns the collection of
     * recipients of the new message, or null if no change is requested. Is a
     * "getX()" method.
     *
     * @return the <code>recipients</code> init parameter or the postmaster
     *         address or <code>SpecialAddress.SENDER</code> or
     *         <code>SpecialAddress.FROM</code> or
     *         <code>SpecialAddress.REPLY_TO</code> or
     *         <code>SpecialAddress.REVERSE_PATH</code> or
     *         <code>SpecialAddress.UNALTERED</code> or
     *         <code>SpecialAddress.RECIPIENTS</code> or <code>null</code> if
     *         missing
     */
    protected Collection getRecipients() throws MessagingException {
        Collection newRecipients = new HashSet();
        String addressList = getInitParameter("recipients");

        // if nothing was specified, return <code>null</code> meaning no change
        if (addressList == null) {
            return null;
        }

        try {
            InternetAddress[] iaarray = InternetAddress.parse(addressList, false);
            for (InternetAddress anIaarray : iaarray) {
                String addressString = anIaarray.getAddress();
                MailAddress specialAddress = getSpecialAddress(addressString, new String[]{"postmaster", "sender", "from", "replyTo", "reversePath", "unaltered", "recipients", "to", "null"});
                if (specialAddress != null) {
                    newRecipients.add(specialAddress);
                } else {
                    newRecipients.add(new MailAddress(anIaarray));
                }
            }
        } catch (Exception e) {
            throw new MessagingException("Exception thrown in getRecipients() parsing: " + addressList, e);
        }
        if (newRecipients.size() == 0) {
            throw new MessagingException("Failed to initialize \"recipients\" list; empty <recipients> init parameter found.");
        }

        return newRecipients;
    }

    /**
     * Gets the <code>recipients</code> property, built dynamically using the
     * original Mail object. Is a "getX(Mail)" method.
     *
     * @return {@link #replaceMailAddresses} on {@link #getRecipients()},
     */
    protected Collection getRecipients(Mail originalMail) throws MessagingException {
        Collection recipients = (isStatic()) ? this.recipients : getRecipients();
        if (recipients != null) {
            if (recipients.size() == 1 && (recipients.contains(SpecialAddress.UNALTERED) || recipients.contains(SpecialAddress.RECIPIENTS))) {
                recipients = null;
            } else {
                recipients = replaceMailAddresses(originalMail, recipients);
            }
        }
        return recipients;
    }

    /**
     * Sets the recipients of <i>newMail</i> to <i>recipients</i>. If the
     * requested value is null does nothing. Is a "setX(Mail, Tx, Mail)" method.
     */
    protected void setRecipients(Mail newMail, Collection recipients, Mail originalMail) {
        if (recipients != null) {
            newMail.setRecipients(recipients);
            if (isDebug) {
                log("recipients set to: " + arrayToString(recipients.toArray()));
            }
        }
    }

    /**
     * Gets the <code>to</code> property. Returns the "To:" recipients of the
     * new message. or null if no change is requested. Is a "getX()" method.
     *
     * @return the <code>to</code> init parameter or the postmaster address or
     *         <code>SpecialAddress.SENDER</code> or
     *         <code>SpecialAddress.REVERSE_PATH</code> or
     *         <code>SpecialAddress.FROM</code> or
     *         <code>SpecialAddress.REPLY_TO</code> or
     *         <code>SpecialAddress.UNALTERED</code> or
     *         <code>SpecialAddress.TO</code> or <code>null</code> if missing
     */
    protected InternetAddress[] getTo() throws MessagingException {
        InternetAddress[] iaarray;
        String addressList = getInitParameter("to");

        // if nothing was specified, return null meaning no change
        if (addressList == null) {
            return null;
        }

        try {
            iaarray = InternetAddress.parse(addressList, false);
            for (int i = 0; i < iaarray.length; ++i) {
                String addressString = iaarray[i].getAddress();
                MailAddress specialAddress = getSpecialAddress(addressString, new String[]{"postmaster", "sender", "from", "replyTo", "reversePath", "unaltered", "recipients", "to", "null"});
                if (specialAddress != null) {
                    iaarray[i] = specialAddress.toInternetAddress();
                }
            }
        } catch (Exception e) {
            throw new MessagingException("Exception thrown in getTo() parsing: " + addressList, e);
        }
        if (iaarray.length == 0) {
            throw new MessagingException("Failed to initialize \"to\" list; empty <to> init parameter found.");
        }

        return iaarray;
    }

    /**
     * Gets the <code>to</code> property, built dynamically using the original
     * Mail object. Its outcome will be the the value the <i>TO:</i> header will
     * be set to, that could be different from the real recipient (see
     * {@link Mail#getRecipients}). Is a "getX(Mail)" method.
     *
     * @return {@link #replaceInternetAddresses} on {@link #getRecipients()},
     */
    protected InternetAddress[] getTo(Mail originalMail) throws MessagingException {
        InternetAddress[] apparentlyTo = (isStatic()) ? this.apparentlyTo : getTo();
        if (apparentlyTo != null) {
            if (apparentlyTo.length == 1 && (apparentlyTo[0].equals(SpecialAddress.UNALTERED.toInternetAddress()) || apparentlyTo[0].equals(SpecialAddress.TO.toInternetAddress()))) {
                apparentlyTo = null;
            } else {
                Collection toList = new ArrayList(apparentlyTo.length);
                Collections.addAll(toList, apparentlyTo);
                /*
                 * IMPORTANT: setTo() treats null differently from a zero length
                 * array, so it's ok to get a zero length array from
                 * replaceSpecialAddresses
                 */
                Collection var = replaceInternetAddresses(originalMail, toList);
                apparentlyTo = (InternetAddress[]) var.toArray(new InternetAddress[var.size()]);
            }
        }

        return apparentlyTo;
    }

    /**
     * Sets the "To:" header of <i>newMail</i> to <i>to</i>. If the requested
     * value is null does nothing. Is a "setX(Mail, Tx, Mail)" method.
     */
    protected void setTo(Mail newMail, InternetAddress[] to, Mail originalMail) throws MessagingException {
        if (to != null) {
            newMail.getMessage().setRecipients(Message.RecipientType.TO, to);
            if (isDebug) {
                log("apparentlyTo set to: " + arrayToString(to));
            }
        }
    }

    /**
     * Gets the <code>replyto</code> property. Returns the Reply-To address of
     * the new message, or null if no change is requested. Is a "getX()" method.
     *
     * @return the <code>replyto</code> init parameter or the postmaster address
     *         or <code>SpecialAddress.SENDER</code> or
     *         <code>SpecialAddress.UNALTERED</code> or
     *         <code>SpecialAddress.NULL</code> or <code>null</code> if missing
     */
    protected MailAddress getReplyTo() throws MessagingException {
        String addressString = getInitParameter("replyTo", getInitParameter("replyto"));

        if (addressString != null) {
            MailAddress specialAddress = getSpecialAddress(addressString, new String[]{"postmaster", "sender", "null", "unaltered"});
            if (specialAddress != null) {
                return specialAddress;
            }

            try {
                return new MailAddress(addressString);
            } catch (Exception e) {
                throw new MessagingException("Exception thrown in getReplyTo() parsing: " + addressString, e);
            }
        }

        return null;
    }

    /**
     * Gets the <code>replyTo</code> property, built dynamically using the
     * original Mail object. Is a "getX(Mail)" method.
     *
     * @return {@link #getReplyTo()} replacing
     *         <code>SpecialAddress.UNALTERED</code> if applicable with null and
     *         <code>SpecialAddress.SENDER</code> with the original mail sender
     */
    protected MailAddress getReplyTo(Mail originalMail) throws MessagingException {
        MailAddress replyTo = (isStatic()) ? this.replyTo : getReplyTo();
        if (replyTo != null) {
            if (replyTo == SpecialAddress.UNALTERED) {
                replyTo = null;
            } else if (replyTo == SpecialAddress.SENDER) {
                replyTo = originalMail.getSender();
            }
        }
        return replyTo;
    }

    /**
     * <p>
     * Sets the "Reply-To:" header of <i>newMail</i> to <i>replyTo</i>.
     * </p>
     * If the requested value is <code>SpecialAddress.NULL</code> will remove
     * the "Reply-To:" header. If the requested value is null does nothing.</p>
     * Is a "setX(Mail, Tx, Mail)" method.
     */
    protected void setReplyTo(Mail newMail, MailAddress replyTo, Mail originalMail) throws MessagingException {
        if (replyTo != null) {
            InternetAddress[] iart = null;
            if (replyTo != SpecialAddress.NULL) {
                iart = new InternetAddress[1];
                iart[0] = replyTo.toInternetAddress();
            }

            // Note: if iart is null will remove the header
            newMail.getMessage().setReplyTo(iart);

            if (isDebug) {
                log("replyTo set to: " + replyTo);
            }
        }
    }

    /**
     * Gets the <code>reversePath</code> property. Returns the reverse-path of
     * the new message, or null if no change is requested. Is a "getX()" method.
     *
     * @return the <code>reversePath</code> init parameter or the postmaster
     *         address or <code>SpecialAddress.SENDER</code> or
     *         <code>SpecialAddress.NULL</code> or
     *         <code>SpecialAddress.UNALTERED</code> or <code>null</code> if
     *         missing
     */
    protected MailAddress getReversePath() throws MessagingException {
        String addressString = getInitParameter("reversePath");
        if (addressString != null) {
            MailAddress specialAddress = getSpecialAddress(addressString, new String[]{"postmaster", "sender", "null", "unaltered"});
            if (specialAddress != null) {
                return specialAddress;
            }

            try {
                return new MailAddress(addressString);
            } catch (Exception e) {
                throw new MessagingException("Exception thrown in getReversePath() parsing: " + addressString, e);
            }
        }

        return null;
    }

    /**
     * Gets the <code>reversePath</code> property, built dynamically using the
     * original Mail object. Is a "getX(Mail)" method.
     *
     * @return {@link #getReversePath()}, replacing
     *         <code>SpecialAddress.SENDER</code> if applicable with null,
     *         replacing <code>SpecialAddress.UNALTERED</code> and
     *         <code>SpecialAddress.REVERSE_PATH</code> if applicable with null,
     *         but not replacing <code>SpecialAddress.NULL</code> that will be
     *         handled by {@link #setReversePath}
     */
    protected MailAddress getReversePath(Mail originalMail) throws MessagingException {
        MailAddress reversePath = (isStatic()) ? this.reversePath : getReversePath();
        if (reversePath != null) {
            if (reversePath == SpecialAddress.UNALTERED || reversePath == SpecialAddress.REVERSE_PATH) {
                reversePath = null;
            } else if (reversePath == SpecialAddress.SENDER) {
                reversePath = null;
            }
        }
        return reversePath;
    }

    /**
     * Sets the "reverse-path" of <i>newMail</i> to <i>reversePath</i>. If the
     * requested value is <code>SpecialAddress.NULL</code> sets it to "<>". If
     * the requested value is null does nothing. Is a "setX(Mail, Tx, Mail)"
     * method.
     */
    protected void setReversePath(MailImpl newMail, MailAddress reversePath, Mail originalMail) {
        if (reversePath != null) {
            if (reversePath == SpecialAddress.NULL) {
                reversePath = null;
            }
            newMail.setSender(reversePath);
            if (isDebug) {
                log("reversePath set to: " + reversePath);
            }
        }
    }

    /**
     * Gets the <code>sender</code> property. Returns the new sender as a
     * MailAddress, or null if no change is requested. Is a "getX()" method.
     *
     * @return the <code>sender</code> init parameter or the postmaster address
     *         or <code>SpecialAddress.SENDER</code> or
     *         <code>SpecialAddress.UNALTERED</code> or <code>null</code> if
     *         missing
     */
    protected MailAddress getSender() throws MessagingException {
        String addressString = getInitParameter("sender");
        if (addressString != null) {
            MailAddress specialAddress = getSpecialAddress(addressString, new String[]{"postmaster", "sender", "unaltered"});
            if (specialAddress != null) {
                return specialAddress;
            }

            try {
                return new MailAddress(addressString);
            } catch (Exception e) {
                throw new MessagingException("Exception thrown in getSender() parsing: " + addressString, e);
            }
        }

        return null;
    }

    /**
     * Gets the <code>sender</code> property, built dynamically using the
     * original Mail object. Is a "getX(Mail)" method.
     *
     * @return {@link #getSender()} replacing
     *         <code>SpecialAddress.UNALTERED</code> and
     *         <code>SpecialAddress.SENDER</code> if applicable with null
     */
    protected MailAddress getSender(Mail originalMail) throws MessagingException {
        MailAddress sender = (isStatic()) ? this.sender : getSender();
        if (sender != null) {
            if (sender == SpecialAddress.UNALTERED || sender == SpecialAddress.SENDER) {
                sender = null;
            }
        }
        return sender;
    }

    /**
     * Sets the "From:" header of <i>newMail</i> to <i>sender</i>. If the
     * requested value is null does nothing. Is a "setX(Mail, Tx, Mail)" method.
     */
    protected void setSender(Mail newMail, MailAddress sender, Mail originalMail) throws MessagingException {
        if (sender != null) {
            newMail.getMessage().setFrom(sender.toInternetAddress());

            if (isDebug) {
                log("sender set to: " + sender);
            }
        }
    }

    /**
     * Gets the <code>subject</code> property. Returns a string for the new
     * message subject. Is a "getX()" method.
     *
     * @return the <code>subject</code> init parameter or null if missing
     */
    protected String getSubject() {
        return getInitParameter("subject");
    }

    /**
     * Gets the <code>subject</code> property, built dynamically using the
     * original Mail object. Is a "getX(Mail)" method.
     *
     * @return {@link #getSubject()}
     */
    protected String getSubject(Mail originalMail) throws MessagingException {
        return (isStatic()) ? this.subject : getSubject();
    }

    /**
     * Gets the <code>prefix</code> property. Returns a prefix for the new
     * message subject. Is a "getX()" method.
     *
     * @return the <code>prefix</code> init parameter or an empty string if
     *         missing
     */
    protected String getSubjectPrefix() {
        return getInitParameter("prefix");
    }

    /**
     * Gets the <code>subjectPrefix</code> property, built dynamically using the
     * original Mail object. Is a "getX(Mail)" method.
     *
     * @return {@link #getSubjectPrefix()}
     */
    protected String getSubjectPrefix(Mail originalMail) throws MessagingException {
        return (isStatic()) ? this.subjectPrefix : getSubjectPrefix();
    }

    /**
     * Builds the subject of <i>newMail</i> appending the subject of
     * <i>originalMail</i> to <i>subjectPrefix</i>. Is a "setX(Mail, Tx, Mail)"
     * method.
     */
    protected void setSubjectPrefix(Mail newMail, String subjectPrefix, Mail originalMail) throws MessagingException {
        String subject = getSubject(originalMail);
        if ((subjectPrefix != null && subjectPrefix.length() > 0) || subject != null) {
            if (subject == null) {
                subject = originalMail.getMessage().getSubject();
            } else {
                // replacing the subject
                if (isDebug) {
                    log("subject set to: " + subject);
                }
            }
            // Was null in original?
            if (subject == null) {
                subject = "";
            }

            if (subjectPrefix != null) {
                subject = subjectPrefix + subject;
                // adding a prefix
                if (isDebug) {
                    log("subjectPrefix set to: " + subjectPrefix);
                }
            }
            // newMail.getMessage().setSubject(subject);
            changeSubject(newMail.getMessage(), subject);
        }
    }

    /**
     * Gets the <code>attachError</code> property. Returns a boolean indicating
     * whether to append a description of any error to the main body part of the
     * new message, if getInlineType does not return "UNALTERED". Is a "getX()"
     * method.
     *
     * @return the <code>attachError</code> init parameter; false if missing
     */
    protected boolean attachError() throws MessagingException {
        return Boolean.valueOf(getInitParameter("attachError"));
    }

    /**
     * Gets the <code>attachError</code> property, built dynamically using the
     * original Mail object. Is a "getX(Mail)" method.
     *
     * @return {@link #attachError()}
     */
    protected boolean attachError(Mail originalMail) throws MessagingException {
        return (isStatic()) ? this.attachError : attachError();
    }

    /**
     * Gets the <code>isReply</code> property. Returns a boolean indicating
     * whether the new message must be considered a reply to the original
     * message, setting the IN_REPLY_TO header of the new message to the id of
     * the original message. Is a "getX()" method.
     *
     * @return the <code>isReply</code> init parameter; false if missing
     */
    protected boolean isReply() {
        return Boolean.valueOf(getInitParameter("isReply"));
    }

    /**
     * Gets the <code>isReply</code> property, built dynamically using the
     * original Mail object. Is a "getX(Mail)" method.
     *
     * @return {@link #isReply()}
     */
    protected boolean isReply(Mail originalMail) throws MessagingException {
        return (isStatic()) ? this.isReply : isReply();
    }

    /**
     * Sets the "In-Reply-To:" header of <i>newMail</i> to the "Message-Id:" of
     * <i>originalMail</i>, if <i>isReply</i> is true.
     */
    protected void setIsReply(Mail newMail, boolean isReply, Mail originalMail) throws MessagingException {
        if (isReply) {
            String messageId = originalMail.getMessage().getMessageID();
            if (messageId != null) {
                newMail.getMessage().setHeader(RFC2822Headers.IN_REPLY_TO, messageId);
                if (isDebug) {
                    log("IN_REPLY_TO set to: " + messageId);
                }
            }
        }
    }

    /**
     * Mailet initialization routine. Will setup static values for each "x"
     * initialization parameter in config.xml, using getX(), if
     * {@link #isStatic()} returns true.
     */
    public void init() throws MessagingException {
        isDebug = Boolean.valueOf(getInitParameter("debug", "false"));

        isStatic = Boolean.valueOf(getInitParameter("static", "false"));

        if (isDebug) {
            log("Initializing");
        }

        // check that all init parameters have been declared in
        // allowedInitParameters
        checkInitParameters(getAllowedInitParameters());

        if (isStatic()) {
            passThrough = getPassThrough();
            fakeDomainCheck = getFakeDomainCheck();
            attachmentType = getAttachmentType();
            inLineType = getInLineType();
            messageText = getMessage();
            recipients = getRecipients();
            replyTo = getReplyTo();
            reversePath = getReversePath();
            sender = getSender();
            subject = getSubject();
            subjectPrefix = getSubjectPrefix();
            apparentlyTo = getTo();
            attachError = attachError();
            isReply = isReply();
            if (isDebug) {
                String logBuffer = "static" + ", passThrough=" + passThrough + ", fakeDomainCheck=" + fakeDomainCheck + ", sender=" + sender + ", replyTo=" + replyTo + ", reversePath=" + reversePath + ", message=" + messageText + ", recipients=" + arrayToString(recipients == null ? null : recipients.toArray()) + ", subject=" + subject + ", subjectPrefix=" + subjectPrefix + ", apparentlyTo=" + arrayToString(apparentlyTo) + ", attachError=" + attachError + ", isReply=" + isReply + ", attachmentType=" + attachmentType + ", inLineType=" + inLineType + " ";
                log(logBuffer);
            }
        }
    }

    /**
     * Service does the hard work,and redirects the originalMail in the form
     * specified.
     *
     * @param originalMail the mail to process and redirect
     * @throws MessagingException if a problem arises formulating the redirected mail
     */
    public void service(Mail originalMail) throws MessagingException {

        boolean keepMessageId = false;

        // duplicates the Mail object, to be able to modify the new mail keeping
        // the original untouched
        MailImpl newMail = new MailImpl(originalMail);
        try {
            // We don't need to use the original Remote Address and Host,
            // and doing so would likely cause a loop with spam detecting
            // matchers.
            try {
                newMail.setRemoteAddr(dns.getLocalHost().getHostAddress());
            } catch (UnknownHostException e) {
                newMail.setRemoteAddr("127.0.0.1");

            }
            try {
                newMail.setRemoteHost(dns.getLocalHost().getHostName());
            } catch (UnknownHostException e) {
                newMail.setRemoteHost("localhost");
            }

            if (isDebug) {
                log("New mail - sender: " + newMail.getSender() + ", recipients: " + arrayToString(newMail.getRecipients().toArray()) + ", name: " + newMail.getName() + ", remoteHost: " + newMail.getRemoteHost() + ", remoteAddr: " + newMail.getRemoteAddr() + ", state: " + newMail.getState()
                        + ", lastUpdated: " + newMail.getLastUpdated() + ", errorMessage: " + newMail.getErrorMessage());
            }

            // Create the message
            if (getInLineType(originalMail) != UNALTERED) {
                if (isDebug) {
                    log("Alter message");
                }
                newMail.setMessage(new MimeMessage(Session.getDefaultInstance(System.getProperties(), null)));

                // handle the new message if altered
                buildAlteredMessage(newMail, originalMail);

            } else {
                // if we need the original, create a copy of this message to
                // redirect
                if (getPassThrough(originalMail)) {
                    newMail.setMessage(new MimeMessage(originalMail.getMessage()) {
                        protected void updateHeaders() throws MessagingException {
                            if (getMessageID() == null)
                                super.updateHeaders();
                            else {
                                modified = false;
                            }
                        }
                    });
                }
                if (isDebug) {
                    log("Message resent unaltered.");
                }
                keepMessageId = true;
            }

            // Set additional headers

            setRecipients(newMail, getRecipients(originalMail), originalMail);

            setTo(newMail, getTo(originalMail), originalMail);

            setSubjectPrefix(newMail, getSubjectPrefix(originalMail), originalMail);

            if (newMail.getMessage().getHeader(RFC2822Headers.DATE) == null) {
                newMail.getMessage().setHeader(RFC2822Headers.DATE, rfc822DateFormat.format(new Date()));
            }

            setReplyTo(newMail, getReplyTo(originalMail), originalMail);

            setReversePath(newMail, getReversePath(originalMail), originalMail);

            setSender(newMail, getSender(originalMail), originalMail);

            setIsReply(newMail, isReply(originalMail), originalMail);

            newMail.getMessage().saveChanges();

            if (keepMessageId) {
                setMessageId(newMail, originalMail);
            }

            if (senderDomainIsValid(newMail)) {
                // Send it off...
                getMailetContext().sendMail(newMail);
            } else {
                String logBuffer = getMailetName() + " mailet cannot forward " + originalMail.getName() + ". Invalid sender domain for " + newMail.getSender() + ". Consider using the Resend mailet " + "using a different sender.";
                throw new MessagingException(logBuffer);
            }

        } finally {
            newMail.dispose();
        }

        if (!getPassThrough(originalMail)) {
            originalMail.setState(Mail.GHOST);
        }
    }

    /**
     * A private method to convert types from string to int.
     *
     * @param param the string type
     * @return the corresponding int enumeration
     */
    protected int getTypeCode(String param) {
        param = param.toLowerCase(Locale.US);
        if (param.compareTo("unaltered") == 0) {
            return UNALTERED;
        }
        if (param.compareTo("heads") == 0) {
            return HEADS;
        }
        if (param.compareTo("body") == 0) {
            return BODY;
        }
        if (param.compareTo("all") == 0) {
            return ALL;
        }
        if (param.compareTo("none") == 0) {
            return NONE;
        }
        if (param.compareTo("message") == 0) {
            return MESSAGE;
        }
        return NONE;
    }

    /**
     * Utility method for obtaining a string representation of a Message's
     * headers
     */
    protected String getMessageHeaders(MimeMessage message) throws MessagingException {
        Enumeration heads = message.getAllHeaderLines();
        StringBuilder headBuffer = new StringBuilder(1024);
        while (heads.hasMoreElements()) {
            headBuffer.append(heads.nextElement().toString()).append("\r\n");
        }
        return headBuffer.toString();
    }

    /**
     * Utility method for obtaining a string representation of a Message's body
     */
    private String getMessageBody(MimeMessage message) throws Exception {
        java.io.ByteArrayOutputStream bodyOs = new java.io.ByteArrayOutputStream();
        MimeMessageUtil.writeMessageBodyTo(message, bodyOs);
        return bodyOs.toString();
    }

    /**
     * Builds the message of the newMail in case it has to be altered.
     *
     * @param originalMail the original Mail object
     * @param newMail      the Mail object to build
     */
    protected void buildAlteredMessage(Mail newMail, Mail originalMail) throws MessagingException {

        MimeMessage originalMessage = originalMail.getMessage();
        MimeMessage newMessage = newMail.getMessage();

        // Copy the relevant headers
        String[] relevantHeaderNames = {RFC2822Headers.DATE, RFC2822Headers.FROM, RFC2822Headers.REPLY_TO, RFC2822Headers.TO, RFC2822Headers.SUBJECT, RFC2822Headers.RETURN_PATH};
        Enumeration headerEnum = originalMessage.getMatchingHeaderLines(relevantHeaderNames);
        while (headerEnum.hasMoreElements()) {
            newMessage.addHeaderLine((String) headerEnum.nextElement());
        }

        StringWriter sout = new StringWriter();
        PrintWriter out = new PrintWriter(sout, true);
        String head = getMessageHeaders(originalMessage);
        boolean all = false;

        String messageText = getMessage(originalMail);
        if (messageText != null) {
            out.println(messageText);
        }

        if (isDebug) {
            log("inline:" + getInLineType(originalMail));
        }
        switch (getInLineType(originalMail)) {
            case ALL: // ALL:
                all = true;
            case HEADS: // HEADS:
                out.println("Message Headers:");
                out.println(head);
                if (!all) {
                    break;
                }
            case BODY: // BODY:
                out.println("Message:");
                try {
                    out.println(getMessageBody(originalMessage));
                } catch (Exception e) {
                    out.println("body unavailable");
                }
                break;
            default:
            case NONE: // NONE:
                break;
        }

        try {
            // Create the message body
            MimeMultipart multipart = new MimeMultipart("mixed");

            // Create the message
            MimeMultipart mpContent = new MimeMultipart("alternative");
            MimeBodyPart contentPartRoot = new MimeBodyPart();
            contentPartRoot.setContent(mpContent);

            multipart.addBodyPart(contentPartRoot);

            MimeBodyPart part = new MimeBodyPart();
            part.setText(sout.toString());
            part.setDisposition("inline");
            mpContent.addBodyPart(part);
            if (isDebug) {
                log("attachmentType:" + getAttachmentType(originalMail));
            }
            if (getAttachmentType(originalMail) != NONE) {
                part = new MimeBodyPart();
                switch (getAttachmentType(originalMail)) {
                    case HEADS: // HEADS:
                        part.setText(head);
                        break;
                    case BODY: // BODY:
                        try {
                            part.setText(getMessageBody(originalMessage));
                        } catch (Exception e) {
                            part.setText("body unavailable");
                        }
                        break;
                    case ALL: // ALL:
                        String textBuffer = head + "\r\nMessage:\r\n" + getMessageBody(originalMessage);
                        part.setText(textBuffer);
                        break;
                    case MESSAGE: // MESSAGE:
                        part.setContent(originalMessage, "message/rfc822");
                        break;
                }
                if ((originalMessage.getSubject() != null) && (originalMessage.getSubject().trim().length() > 0)) {
                    part.setFileName(originalMessage.getSubject().trim());
                } else {
                    part.setFileName("No Subject");
                }
                part.setDisposition("Attachment");
                multipart.addBodyPart(part);
            }
            // if set, attach the original mail's error message
            if (attachError(originalMail) && originalMail.getErrorMessage() != null) {
                part = new MimeBodyPart();
                part.setContent(originalMail.getErrorMessage(), "text/plain");
                part.setHeader(RFC2822Headers.CONTENT_TYPE, "text/plain");
                part.setFileName("Reasons");
                part.setDisposition(javax.mail.Part.ATTACHMENT);
                multipart.addBodyPart(part);
            }
            newMail.getMessage().setContent(multipart);
            newMail.getMessage().setHeader(RFC2822Headers.CONTENT_TYPE, multipart.getContentType());

        } catch (Exception ioe) {
            throw new MessagingException("Unable to create multipart body", ioe);
        }
    }

    /**
     * Sets the message id of originalMail into newMail.
     */
    private void setMessageId(Mail newMail, Mail originalMail) throws MessagingException {
        String messageId = originalMail.getMessage().getMessageID();
        if (messageId != null) {
            newMail.getMessage().setHeader(RFC2822Headers.MESSAGE_ID, messageId);
            if (isDebug) {
                log("MESSAGE_ID restored to: " + messageId);
            }
        }
    }

    /**
     * Returns the {@link SpecialAddress} that corresponds to an init parameter
     * value. The init parameter value is checked against a String[] of allowed
     * values. The checks are case insensitive.
     *
     * @param addressString   the string to check if is a special address
     * @param allowedSpecials a String[] with the allowed special addresses
     * @return a SpecialAddress if found, null if not found or addressString is
     *         null
     * @throws MessagingException if is a special address not in the allowedSpecials array
     */
    protected final MailAddress getSpecialAddress(String addressString, String[] allowedSpecials) throws MessagingException {
        if (addressString == null) {
            return null;
        }

        addressString = addressString.toLowerCase(Locale.US);
        addressString = addressString.trim();

        MailAddress specialAddress = null;

        if (addressString.compareTo("postmaster") == 0) {
            specialAddress = getMailetContext().getPostmaster();
        }
        if (addressString.compareTo("sender") == 0) {
            specialAddress = SpecialAddress.SENDER;
        }
        if (addressString.compareTo("reversepath") == 0) {
            specialAddress = SpecialAddress.REVERSE_PATH;
        }
        if (addressString.compareTo("from") == 0) {
            specialAddress = SpecialAddress.FROM;
        }
        if (addressString.compareTo("replyto") == 0) {
            specialAddress = SpecialAddress.REPLY_TO;
        }
        if (addressString.compareTo("to") == 0) {
            specialAddress = SpecialAddress.TO;
        }
        if (addressString.compareTo("recipients") == 0) {
            specialAddress = SpecialAddress.RECIPIENTS;
        }
        if (addressString.compareTo("delete") == 0) {
            specialAddress = SpecialAddress.DELETE;
        }
        if (addressString.compareTo("unaltered") == 0) {
            specialAddress = SpecialAddress.UNALTERED;
        }
        if (addressString.compareTo("null") == 0) {
            specialAddress = SpecialAddress.NULL;
        }

        // if is a special address, must be in the allowedSpecials array
        if (specialAddress != null) {
            // check if is an allowed special
            boolean allowed = false;
            for (String allowedSpecial1 : allowedSpecials) {
                String allowedSpecial = allowedSpecial1;
                allowedSpecial = allowedSpecial.toLowerCase(Locale.US);
                allowedSpecial = allowedSpecial.trim();
                if (addressString.compareTo(allowedSpecial) == 0) {
                    allowed = true;
                    break;
                }
            }
            if (!allowed) {
                throw new MessagingException("Special (\"magic\") address found not allowed: " + addressString + ", allowed values are \"" + arrayToString(allowedSpecials) + "\"");
            }
        }

        return specialAddress;
    }

    /**
     * <p>
     * Checks if a sender domain of <i>mail</i> is valid.
     * </p>
     * <p>
     * If we do not do this check, and someone uses a redirection mailet in a
     * processor initiated by SenderInFakeDomain, then a fake sender domain will
     * cause an infinite loop (the forwarded e-mail still appears to come from a
     * fake domain).<br>
     * Although this can be viewed as a configuration error, the consequences of
     * such a mis-configuration are severe enough to warrant protecting against
     * the infinite loop.
     * </p>
     * <p>
     * This check can be skipped if {@link #getFakeDomainCheck(Mail)} returns
     * true.
     * </p>
     *
     * @param mail the mail object to check
     * @return true if the if the sender is null or
     *         {@link org.apache.mailet.MailetContext#getMailServers} returns
     *         true for the sender host part
     */
    protected final boolean senderDomainIsValid(Mail mail) throws MessagingException {
        return !getFakeDomainCheck(mail) || mail.getSender() == null || getMailetContext().getMailServers(mail.getSender().getDomain()).size() != 0;
    }

    /**
     * It changes the subject of the supplied message to to supplied value but
     * it also tries to preserve the original charset information.<br>
     * <p/>
     * This method was needed to avoid sending the subject using a charset
     * (usually the default charset on the server) which doesn't contain the
     * characters in the subject, resulting in the loss of these characters. The
     * most simple method would be to either send it in ASCII unencoded or in
     * UTF-8 if non-ASCII characters are present but unfortunately UTF-8 is not
     * yet a MIME standard and not all email clients are supporting it. The
     * optimal method would be to determine the best charset by analyzing the
     * actual characters. That would require much more work (exept if an open
     * source library already exists for this). However there is nothing to stop
     * somebody to add a detection algorithm for a specific charset. <br>
     * <p/>
     * The current algorithm works correctly if only ASCII characters are added
     * to an existing subject.<br>
     * <p/>
     * If the new value is ASCII only, then it doesn't apply any encoding to the
     * subject header. (This is provided by MimeMessage.setSubject()).<br>
     * <p/>
     * Possible enhancement: under java 1.4 java.nio the system can determine if
     * the suggested charset fits or not (if there is untranslatable
     * characters). If the charset doesn't fit the new value, it can fall back
     * to UTF-8.<br>
     *
     * @param message  the message of which subject is changed
     * @param newValue the new (unencoded) value of the subject. It must not be null.
     * @throws MessagingException - according to the JavaMail doc most likely this is never
     *                            thrown
     */
    public static void changeSubject(MimeMessage message, String newValue) throws MessagingException {
        String rawSubject = message.getHeader(RFC2822Headers.SUBJECT, null);
        String mimeCharset = determineMailHeaderEncodingCharset(rawSubject);
        if (mimeCharset == null) { // most likely ASCII
            // it uses the system charset or the value of the
            // mail.mime.charset property if set
            message.setSubject(newValue);
        } else { // original charset determined
            String javaCharset = javax.mail.internet.MimeUtility.javaCharset(mimeCharset);
            try {
                message.setSubject(newValue, javaCharset);
            } catch (MessagingException e) {
                // known, but unsupported encoding
                // this should be logged, the admin may setup a more i18n
                // capable JRE, but the log API cannot be accessed from here
                // if (charset != null) log(charset +
                // " charset unsupported by the JRE, email subject may be damaged");
                message.setSubject(newValue); // recover
            }
        }
    }

    /**
     * It attempts to determine the charset used to encode an "unstructured" RFC
     * 822 header (like Subject). The encoding is specified in RFC 2047. If it
     * cannot determine or the the text is not encoded then it returns null.
     * <p/>
     * Here is an example raw text: Subject:
     * =?iso-8859-2?Q?leg=FAjabb_pr=F3ba_l=F5elemmel?=
     *
     * @param rawText the raw (not decoded) value of the header. Null means that the
     *                header was not present (in this case it always return null).
     * @return the MIME charset name or null if no encoding applied
     */
    static private String determineMailHeaderEncodingCharset(String rawText) {
        if (rawText == null)
            return null;
        int iEncodingPrefix = rawText.indexOf("=?");
        if (iEncodingPrefix == -1)
            return null;
        int iCharsetBegin = iEncodingPrefix + 2;
        int iSecondQuestionMark = rawText.indexOf('?', iCharsetBegin);
        if (iSecondQuestionMark == -1)
            return null;
        // safety checks
        if (iSecondQuestionMark == iCharsetBegin)
            return null; // empty charset? impossible
        int iThirdQuestionMark = rawText.indexOf('?', iSecondQuestionMark + 1);
        if (iThirdQuestionMark == -1)
            return null; // there must be one after encoding
        if (-1 == rawText.indexOf("?=", iThirdQuestionMark + 1))
            return null; // closing tag
        return rawText.substring(iCharsetBegin, iSecondQuestionMark);
    }

    /**
     * Returns a new Collection built over <i>list</i> replacing special
     * addresses with real <code>MailAddress</code>-es.<br>
     * Manages <code>SpecialAddress.SENDER</code>,
     * <code>SpecialAddress.REVERSE_PATH</code>,
     * <code>SpecialAddress.FROM</code>, <code>SpecialAddress.REPLY_TO</code>,
     * <code>SpecialAddress.RECIPIENTS</code>, <code>SpecialAddress.TO</code>,
     * <code>SpecialAddress.NULL</code> and
     * <code>SpecialAddress.UNALTERED</code>.<br>
     * <code>SpecialAddress.FROM</code> is made equivalent to
     * <code>SpecialAddress.SENDER</code>; <code>SpecialAddress.TO</code> is
     * made equivalent to <code>SpecialAddress.RECIPIENTS</code>.<br>
     * <code>SpecialAddress.REPLY_TO</code> uses the ReplyTo header if
     * available, otherwise the From header if available, otherwise the Sender
     * header if available, otherwise the return-path.<br>
     * <code>SpecialAddress.NULL</code> and
     * <code>SpecialAddress.UNALTERED</code> are ignored.<br>
     * Any other address is not replaced.
     */
    protected Collection replaceMailAddresses(Mail mail, Collection list) {
        Collection newList = new HashSet(list.size());
        for (Object aList : list) {
            MailAddress mailAddress = (MailAddress) aList;
            if (!mailAddress.getDomain().equalsIgnoreCase("address.marker")) {
                newList.add(mailAddress);
            } else if (mailAddress == SpecialAddress.SENDER || mailAddress == SpecialAddress.FROM) {
                MailAddress sender = mail.getSender();
                if (sender != null) {
                    newList.add(sender);
                }
            } else if (mailAddress == SpecialAddress.REPLY_TO) {
                int parsedAddressCount = 0;
                try {
                    InternetAddress[] replyToArray = (InternetAddress[]) mail.getMessage().getReplyTo();
                    if (replyToArray != null) {
                        for (InternetAddress aReplyToArray : replyToArray) {
                            try {
                                newList.add(new MailAddress(aReplyToArray));
                                parsedAddressCount++;
                            } catch (ParseException pe) {
                                log("Unable to parse a \"REPLY_TO\" header address in the original message: " + aReplyToArray + "; ignoring.");
                            }
                        }
                    }
                } catch (MessagingException ae) {
                    log("Unable to parse the \"REPLY_TO\" header in the original message; ignoring.");
                }
                // no address was parsed?
                if (parsedAddressCount == 0) {
                    MailAddress sender = mail.getSender();
                    if (sender != null) {
                        newList.add(sender);
                    }
                }
            } else if (mailAddress == SpecialAddress.REVERSE_PATH) {
                MailAddress reversePath = mail.getSender();
                if (reversePath != null) {
                    newList.add(reversePath);
                }
            } else if (mailAddress == SpecialAddress.RECIPIENTS || mailAddress == SpecialAddress.TO) {
                newList.addAll(mail.getRecipients());
            } else if (mailAddress == SpecialAddress.UNALTERED) {
            } else if (mailAddress == SpecialAddress.NULL) {
            } else {
                newList.add(mailAddress);
            }
        }
        return newList;
    }

    /**
     * Returns a new Collection built over <i>list</i> replacing special
     * addresses with real <code>InternetAddress</code>-es.<br>
     * Manages <code>SpecialAddress.SENDER</code>,
     * <code>SpecialAddress.REVERSE_PATH</code>,
     * <code>SpecialAddress.FROM</code>, <code>SpecialAddress.REPLY_TO</code>,
     * <code>SpecialAddress.RECIPIENTS</code>, <code>SpecialAddress.TO</code>,
     * <code>SpecialAddress.NULL</code> and
     * <code>SpecialAddress.UNALTERED</code>.<br>
     * <code>SpecialAddress.RECIPIENTS</code> is made equivalent to
     * <code>SpecialAddress.TO</code>.<br>
     * <code>SpecialAddress.FROM</code> uses the From header if available,
     * otherwise the Sender header if available, otherwise the return-path.<br>
     * <code>SpecialAddress.REPLY_TO</code> uses the ReplyTo header if
     * available, otherwise the From header if available, otherwise the Sender
     * header if available, otherwise the return-path.<br>
     * <code>SpecialAddress.UNALTERED</code> is ignored.<br>
     * Any other address is not replaced.<br>
     */
    protected Collection replaceInternetAddresses(Mail mail, Collection list) throws MessagingException {
        Collection newList = new HashSet(list.size());
        for (Object aList : list) {
            InternetAddress internetAddress = (InternetAddress) aList;
            MailAddress mailAddress = new MailAddress(internetAddress);
            if (!mailAddress.getDomain().equalsIgnoreCase("address.marker")) {
                newList.add(internetAddress);
            } else if (internetAddress.equals(SpecialAddress.SENDER.toInternetAddress())) {
                MailAddress sender = mail.getSender();
                if (sender != null) {
                    newList.add(sender.toInternetAddress());
                }
            } else if (internetAddress.equals(SpecialAddress.REVERSE_PATH.toInternetAddress())) {
                MailAddress reversePath = mail.getSender();
                if (reversePath != null) {
                    newList.add(reversePath.toInternetAddress());
                }
            } else if (internetAddress.equals(SpecialAddress.FROM.toInternetAddress())) {
                try {
                    InternetAddress[] fromArray = (InternetAddress[]) mail.getMessage().getFrom();
                    if (fromArray != null) {
                        Collections.addAll(newList, fromArray);
                    } else {
                        MailAddress reversePath = mail.getSender();
                        if (reversePath != null) {
                            newList.add(reversePath.toInternetAddress());
                        }
                    }
                } catch (MessagingException me) {
                    log("Unable to parse the \"FROM\" header in the original message; ignoring.");
                }
            } else if (internetAddress.equals(SpecialAddress.REPLY_TO.toInternetAddress())) {
                try {
                    InternetAddress[] replyToArray = (InternetAddress[]) mail.getMessage().getReplyTo();
                    if (replyToArray != null) {
                        Collections.addAll(newList, replyToArray);
                    } else {
                        MailAddress reversePath = mail.getSender();
                        if (reversePath != null) {
                            newList.add(reversePath.toInternetAddress());
                        }
                    }
                } catch (MessagingException me) {
                    log("Unable to parse the \"REPLY_TO\" header in the original message; ignoring.");
                }
            } else if (internetAddress.equals(SpecialAddress.TO.toInternetAddress()) || internetAddress.equals(SpecialAddress.RECIPIENTS.toInternetAddress())) {
                try {
                    String[] toHeaders = mail.getMessage().getHeader(RFC2822Headers.TO);
                    if (toHeaders != null) {
                        for (String toHeader : toHeaders) {
                            try {
                                InternetAddress[] originalToInternetAddresses = InternetAddress.parse(toHeader, false);
                                Collections.addAll(newList, originalToInternetAddresses);
                            } catch (MessagingException ae) {
                                log("Unable to parse a \"TO\" header address in the original message: " + toHeader + "; ignoring.");
                            }
                        }
                    }
                } catch (MessagingException ae) {
                    log("Unable to parse the \"TO\" header  in the original message; ignoring.");
                }
            } else if (internetAddress.equals(SpecialAddress.UNALTERED.toInternetAddress())) {
            } else if (internetAddress.equals(SpecialAddress.NULL.toInternetAddress())) {
            } else {
                newList.add(internetAddress);
            }
        }
        return newList;
    }

}
TOP

Related Classes of org.apache.james.transport.mailets.AbstractRedirect

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.