/****************************************************************
* 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 java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Collection;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Locale;
import java.util.ArrayList;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.internet.ParseException;
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 org.apache.mailet.RFC2822Headers;
import org.apache.mailet.dates.RFC822DateFormat;
import org.apache.james.core.MailImpl;
import org.apache.james.core.MimeMessageUtil;
import org.apache.mailet.GenericMailet;
import org.apache.mailet.Mail;
import org.apache.mailet.MailAddress;
/**
* <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>
* <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>Supports by default the <CODE>passThrough</CODE> init parameter (false if missing).
* Subclasses can override this behaviour overriding {@link #getPassThrough()}.</P>
*
* @version CVS $Revision: 494012 $ $Date: 2007-01-08 11:23:58 +0100 (Mo, 08 Jan 2007) $
* @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 MailAddress SENDER;
public static MailAddress REVERSE_PATH;
public static MailAddress FROM;
public static MailAddress REPLY_TO;
public static MailAddress TO;
public static MailAddress RECIPIENTS;
public static MailAddress DELETE;
public static MailAddress UNALTERED;
public static MailAddress NULL;
static {
try {
SENDER = new MailAddress("sender","address.marker");
REVERSE_PATH = new MailAddress("reverse.path","address.marker");
FROM = new MailAddress("from","address.marker");
REPLY_TO = new MailAddress("reply.to","address.marker");
TO = new MailAddress("to","address.marker");
RECIPIENTS = new MailAddress("recipients","address.marker");
DELETE = new MailAddress("delete","address.marker");
UNALTERED = new MailAddress("unaltered","address.marker");
NULL = new MailAddress("null","address.marker");
} catch (Exception _) {}
}
}
/**
* 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 RFC822DateFormat rfc822DateFormat = new RFC822DateFormat();
/* ******************************************************************** */
/* ****************** Begin of getX and setX methods ****************** */
/* ******************************************************************** */
/**
* <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>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() throws MessagingException {
return new Boolean(getInitParameter("passThrough")).booleanValue();
}
/**
* 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() throws MessagingException {
return new Boolean(getInitParameter("fakeDomainCheck")).booleanValue();
}
/**
* 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() throws MessagingException {
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() throws MessagingException {
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() throws MessagingException {
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 (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) {
newRecipients.add(specialAddress);
} else {
newRecipients.add(new MailAddress(iaarray[i]));
}
}
} 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) throws MessagingException {
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 = null;
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 #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);
for (int i = 0; i < apparentlyTo.length; i++) {
toList.add(apparentlyTo[i]);
}
/* IMPORTANT: setTo() treats null differently from a zero length array,
so it's ok to get a zero length array from replaceSpecialAddresses
*/
apparentlyTo = (InternetAddress[]) replaceInternetAddresses(originalMail, toList).toArray(new InternetAddress[0]);
}
}
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) throws MessagingException {
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() throws MessagingException {
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() throws MessagingException {
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 new Boolean(getInitParameter("attachError")).booleanValue();
}
/**
* 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() throws MessagingException {
return new Boolean(getInitParameter("isReply")).booleanValue();
}
/**
* 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);
}
}
}
}
/* ******************************************************************** */
/* ******************* End of getX and setX methods ******************* */
/* ******************************************************************** */
/**
* 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 = new Boolean(getInitParameter("debug","false")).booleanValue();
isStatic = new Boolean(getInitParameter("static","false")).booleanValue();
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) {
StringBuffer logBuffer =
new StringBuffer(1024)
.append("static")
.append(", passThrough=").append(passThrough)
.append(", fakeDomainCheck=").append(fakeDomainCheck)
.append(", sender=").append(sender)
.append(", replyTo=").append(replyTo)
.append(", reversePath=").append(reversePath)
.append(", message=").append(messageText)
.append(", recipients=").append(arrayToString(recipients == null ? null : recipients.toArray()))
.append(", subject=").append(subject)
.append(", subjectPrefix=").append(subjectPrefix)
.append(", apparentlyTo=").append(arrayToString(apparentlyTo))
.append(", attachError=").append(attachError)
.append(", isReply=").append(isReply)
.append(", attachmentType=").append(attachmentType)
.append(", inLineType=").append(inLineType)
.append(" ");
log(logBuffer.toString());
}
}
}
/**
* 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,newName(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(java.net.InetAddress.getLocalHost().getHostAddress());
newMail.setRemoteHost(java.net.InetAddress.getLocalHost().getHostName());
} catch (java.net.UnknownHostException _) {
newMail.setRemoteAddr("127.0.0.1");
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 {
StringBuffer logBuffer = new StringBuffer(256)
.append(getMailetName())
.append(" mailet cannot forward ")
.append(originalMail.getName())
.append(". Invalid sender domain for ")
.append(newMail.getSender())
.append(". Consider using the Resend mailet ")
.append("using a different sender.");
throw new MessagingException(logBuffer.toString());
}
} finally {
newMail.dispose();
}
if(!getPassThrough(originalMail)) {
originalMail.setState(Mail.GHOST);
}
}
private static final java.util.Random random = new java.util.Random(); // Used to generate new mail names
/**
* Create a unique new primary key name.
*
* @param mail the mail to use as the basis for the new mail name
* @return a new name
*/
private String newName(Mail mail) throws MessagingException {
String oldName = mail.getName();
// Checking if the original mail name is too long, perhaps because of a
// loop caused by a configuration error.
// it could cause a "null pointer exception" in AvalonMailRepository much
// harder to understand.
if (oldName.length() > 76) {
int count = 0;
int index = 0;
while ((index = oldName.indexOf('!', index + 1)) >= 0) {
count++;
}
// It looks like a configuration loop. It's better to stop.
if (count > 7) {
throw new MessagingException("Unable to create a new message name: too long."
+ " Possible loop in config.xml.");
}
else {
oldName = oldName.substring(0, 76);
}
}
StringBuffer nameBuffer =
new StringBuffer(64)
.append(oldName)
.append("-!")
.append(random.nextInt(1048576));
return nameBuffer.toString();
}
/**
* 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 an array of Objects.
*/
private String arrayToString(Object[] array) {
if (array == null) {
return "null";
}
StringBuffer sb = new StringBuffer(1024);
sb.append("[");
for (int i = 0; i < array.length; i++) {
if (i > 0) {
sb.append(",");
}
sb.append(array[i]);
}
sb.append("]");
return sb.toString();
}
/**
* Utility method for obtaining a string representation of a
* Message's headers
*/
protected String getMessageHeaders(MimeMessage message) throws MessagingException {
Enumeration heads = message.getAllHeaderLines();
StringBuffer headBuffer = new StringBuffer(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:
StringBuffer textBuffer =
new StringBuffer(1024)
.append(head)
.append("\r\nMessage:\r\n")
.append(getMessageBody(originalMessage));
part.setText(textBuffer.toString());
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 (int i = 0; i < allowedSpecials.length; i++) {
String allowedSpecial = allowedSpecials[i];
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 {
if (getFakeDomainCheck(mail)) {
return mail.getSender() == null || getMailetContext().getMailServers(mail.getSender().getHost()).size() != 0;
} else return true;
}
/**
* Checks if there are unallowed init parameters specified in the configuration file
* against the String[] allowedInitParameters.
*/
private void checkInitParameters(String[] allowedArray) throws MessagingException {
// if null then no check is requested
if (allowedArray == null) {
return;
}
Collection allowed = new HashSet();
Collection bad = new ArrayList();
for (int i = 0; i < allowedArray.length; i++) {
allowed.add(allowedArray[i]);
}
Iterator iterator = getInitParameterNames();
while (iterator.hasNext()) {
String parameter = (String) iterator.next();
if (!allowed.contains(parameter)) {
bad.add(parameter);
}
}
if (bad.size() > 0) {
throw new MessagingException("Unexpected init parameters found: "
+ arrayToString(bad.toArray()));
}
}
/**
* It changes the subject of the supplied message to to supplied value
* but it also tries to preserve the original charset information.<BR>
*
* 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>
*
* The current algorithm works correctly if only ASCII characters are
* added to an existing subject.<BR>
*
* 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>
*
* 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);
return;
} 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.
*
* 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
String mimeCharset = rawText.substring(iCharsetBegin, iSecondQuestionMark);
return mimeCharset;
}
/**
* 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());
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
MailAddress mailAddress = (MailAddress) iterator.next();
if (!mailAddress.getHost().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 (int i = 0; i < replyToArray.length; i++) {
try {
newList.add(new MailAddress(replyToArray[i]));
parsedAddressCount++;
} catch (ParseException pe) {
log("Unable to parse a \"REPLY_TO\" header address in the original message: " + replyToArray[i] + "; 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) {
continue;
} else if (mailAddress == SpecialAddress.NULL) {
continue;
} 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());
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
InternetAddress internetAddress = (InternetAddress) iterator.next();
MailAddress mailAddress = new MailAddress(internetAddress);
if (!mailAddress.getHost().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) {
for (int i = 0; i < fromArray.length; i++) {
newList.add(fromArray[i]);
}
} 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) {
for (int i = 0; i < replyToArray.length; i++) {
newList.add(replyToArray[i]);
}
} 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 (int i = 0; i < toHeaders.length; i++) {
try {
InternetAddress[] originalToInternetAddresses = InternetAddress.parse(toHeaders[i], false);
for (int j = 0; j < originalToInternetAddresses.length; j++) {
newList.add(originalToInternetAddresses[j]);
}
} catch (MessagingException ae) {
log("Unable to parse a \"TO\" header address in the original message: " + toHeaders[i] + "; ignoring.");
}
}
}
} catch (MessagingException ae) {
log("Unable to parse the \"TO\" header in the original message; ignoring.");
}
} else if (internetAddress.equals(SpecialAddress.UNALTERED.toInternetAddress())) {
continue;
} else if (internetAddress.equals(SpecialAddress.NULL.toInternetAddress())) {
continue;
} else {
newList.add(internetAddress);
}
}
return newList;
}
}
|