0001: /****************************************************************
0002: * Licensed to the Apache Software Foundation (ASF) under one *
0003: * or more contributor license agreements. See the NOTICE file *
0004: * distributed with this work for additional information *
0005: * regarding copyright ownership. The ASF licenses this file *
0006: * to you under the Apache License, Version 2.0 (the *
0007: * "License"); you may not use this file except in compliance *
0008: * with the License. You may obtain a copy of the License at *
0009: * *
0010: * http://www.apache.org/licenses/LICENSE-2.0 *
0011: * *
0012: * Unless required by applicable law or agreed to in writing, *
0013: * software distributed under the License is distributed on an *
0014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
0015: * KIND, either express or implied. See the License for the *
0016: * specific language governing permissions and limitations *
0017: * under the License. *
0018: ****************************************************************/package org.apache.james.transport.mailets;
0019:
0020: import java.io.PrintWriter;
0021: import java.io.StringWriter;
0022:
0023: import java.util.Collection;
0024: import java.util.Date;
0025: import java.util.Enumeration;
0026: import java.util.HashSet;
0027: import java.util.Iterator;
0028: import java.util.Locale;
0029: import java.util.ArrayList;
0030:
0031: import javax.mail.Message;
0032: import javax.mail.MessagingException;
0033: import javax.mail.internet.ParseException;
0034: import javax.mail.Session;
0035: import javax.mail.internet.InternetAddress;
0036: import javax.mail.internet.MimeBodyPart;
0037: import javax.mail.internet.MimeMessage;
0038: import javax.mail.internet.MimeMultipart;
0039:
0040: import org.apache.mailet.RFC2822Headers;
0041: import org.apache.mailet.dates.RFC822DateFormat;
0042: import org.apache.james.core.MailImpl;
0043: import org.apache.james.core.MimeMessageUtil;
0044:
0045: import org.apache.mailet.GenericMailet;
0046: import org.apache.mailet.Mail;
0047: import org.apache.mailet.MailAddress;
0048:
0049: /**
0050: * <P>Abstract mailet providing configurable redirection services.<BR>
0051: * This mailet can be subclassed to make authoring redirection mailets simple.<BR>
0052: * By extending it and overriding one or more of these methods new behaviour can
0053: * be quickly created without the author having to address any other issue than
0054: * the relevant one:</P>
0055: * <UL>
0056: * <LI>attachError() , should error messages be appended to the message</LI>
0057: * <LI>getAttachmentType(), what should be attached to the message</LI>
0058: * <LI>getInLineType(), what should be included in the message</LI>
0059: * <LI>getMessage(), The text of the message itself</LI>
0060: * <LI>getRecipients(), the recipients the mail is sent to</LI>
0061: * <LI>getReplyTo(), where replies to this message will be sent</LI>
0062: * <LI>getReversePath(), what to set the reverse-path to</LI>
0063: * <LI>getSender(), who the mail is from</LI>
0064: * <LI>getSubject(), a string to replace the message subject</LI>
0065: * <LI>getSubjectPrefix(), a prefix to be added to the message subject, possibly already replaced by a new subject</LI>
0066: * <LI>getTo(), a list of people to whom the mail is *apparently* sent</LI>
0067: * <LI>isReply(), should this mailet set the IN_REPLY_TO header to the id of the current message</LI>
0068: * <LI>getPassThrough(), should this mailet allow the original message to continue processing or GHOST it.</LI>
0069: * <LI>getFakeDomainCheck(), should this mailet check if the sender domain address is valid.</LI>
0070: * <LI>isStatic(), should this mailet run the get methods for every mail, or just once.</LI>
0071: * </UL>
0072: * <P>For each of the methods above (generically called "getX()" methods in this class
0073: * and its subclasses), there is an associated "getX(Mail)" method and most times
0074: * a "setX(Mail, Tx, Mail)" method.<BR>
0075: * The roles are the following:</P>
0076: * <UL>
0077: * <LI>a "getX()" method returns the correspondent "X" value that can be evaluated "statically"
0078: * once at init time and then stored in a variable and made available for later use by a
0079: * "getX(Mail)" method;</LI>
0080: * <LI>a "getX(Mail)" method is the one called to return the correspondent "X" value
0081: * that can be evaluated "dynamically", tipically based on the currently serviced mail;
0082: * the default behaviour is to return the value of getX();</LI>
0083: * <LI>a "setX(Mail, Tx, Mail)" method is called to change the correspondent "X" value
0084: * of the redirected Mail object, using the value returned by "gexX(Mail)";
0085: * if such value is null, it does nothing.</LI>
0086: * </UL>
0087: * <P>Here follows the typical pattern of those methods:</P>
0088: * <PRE><CODE>
0089: * ...
0090: * Tx x;
0091: * ...
0092: * protected boolean getX(Mail originalMail) throws MessagingException {
0093: * boolean x = (isStatic()) ? this.x : getX();
0094: * ...
0095: * return x;
0096: * }
0097: * ...
0098: * public void init() throws MessagingException {
0099: * ...
0100: * isStatic = (getInitParameter("static") == null) ? false : new Boolean(getInitParameter("static")).booleanValue();
0101: * if(isStatic()) {
0102: * ...
0103: * X = getX();
0104: * ...
0105: * }
0106: * ...
0107: * public void service(Mail originalMail) throws MessagingException {
0108: * ...
0109: * setX(newMail, getX(originalMail), originalMail);
0110: * ...
0111: * }
0112: * ...
0113: * </CODE></PRE>
0114: * <P>The <I>isStatic</I> variable and method is used to allow for the situations
0115: * (deprecated since version 2.2, but possibly used by previoulsy written extensions
0116: * to {@link Redirect}) in which the getX() methods are non static: in this case
0117: * {@link #isStatic()} must return false.<BR>
0118: * Finally, a "getX()" method may return a "special address" (see {@link SpecialAddress}),
0119: * that later will be resolved ("late bound") by a "getX(Mail)" or "setX(Mail, Tx, Mail)":
0120: * it is a dynamic value that does not require <CODE>isStatic</CODE> to be false.</P>
0121: *
0122: * <P>Supports by default the <CODE>passThrough</CODE> init parameter (false if missing).
0123: * Subclasses can override this behaviour overriding {@link #getPassThrough()}.</P>
0124: *
0125: * @version CVS $Revision: 494012 $ $Date: 2007-01-08 11:23:58 +0100 (Mo, 08 Jan 2007) $
0126: * @since 2.2.0
0127: */
0128:
0129: public abstract class AbstractRedirect extends GenericMailet {
0130:
0131: /**
0132: * Gets the expected init parameters.
0133: *
0134: * @return null meaning no check
0135: */
0136: protected String[] getAllowedInitParameters() {
0137: return null;
0138: }
0139:
0140: /**
0141: * Controls certain log messages.
0142: */
0143: protected boolean isDebug = false;
0144:
0145: /**
0146: * Holds the value of the <CODE>static</CODE> init parameter.
0147: */
0148: protected boolean isStatic = false;
0149:
0150: private static class AddressMarker {
0151: public static MailAddress SENDER;
0152: public static MailAddress REVERSE_PATH;
0153: public static MailAddress FROM;
0154: public static MailAddress REPLY_TO;
0155: public static MailAddress TO;
0156: public static MailAddress RECIPIENTS;
0157: public static MailAddress DELETE;
0158: public static MailAddress UNALTERED;
0159: public static MailAddress NULL;
0160:
0161: static {
0162: try {
0163: SENDER = new MailAddress("sender", "address.marker");
0164: REVERSE_PATH = new MailAddress("reverse.path",
0165: "address.marker");
0166: FROM = new MailAddress("from", "address.marker");
0167: REPLY_TO = new MailAddress("reply.to", "address.marker");
0168: TO = new MailAddress("to", "address.marker");
0169: RECIPIENTS = new MailAddress("recipients",
0170: "address.marker");
0171: DELETE = new MailAddress("delete", "address.marker");
0172: UNALTERED = new MailAddress("unaltered",
0173: "address.marker");
0174: NULL = new MailAddress("null", "address.marker");
0175:
0176: } catch (Exception _) {
0177: }
0178: }
0179: }
0180:
0181: /**
0182: * Class containing "special addresses" constants.
0183: * Such addresses mean dynamic values that later will be resolved ("late bound")
0184: * by a "getX(Mail)" or "setX(Mail, Tx, Mail)".
0185: */
0186: protected static class SpecialAddress {
0187: public static final MailAddress SENDER = AddressMarker.SENDER;
0188: public static final MailAddress REVERSE_PATH = AddressMarker.REVERSE_PATH;
0189: public static final MailAddress FROM = AddressMarker.FROM;
0190: public static final MailAddress REPLY_TO = AddressMarker.REPLY_TO;
0191: public static final MailAddress TO = AddressMarker.TO;
0192: public static final MailAddress RECIPIENTS = AddressMarker.RECIPIENTS;
0193: public static final MailAddress DELETE = AddressMarker.DELETE;
0194: public static final MailAddress UNALTERED = AddressMarker.UNALTERED;
0195: public static final MailAddress NULL = AddressMarker.NULL;
0196: }
0197:
0198: // The values that indicate how to attach the original mail
0199: // to the new mail.
0200:
0201: protected static final int UNALTERED = 0;
0202:
0203: protected static final int HEADS = 1;
0204:
0205: protected static final int BODY = 2;
0206:
0207: protected static final int ALL = 3;
0208:
0209: protected static final int NONE = 4;
0210:
0211: protected static final int MESSAGE = 5;
0212:
0213: private boolean passThrough = false;
0214: private boolean fakeDomainCheck = true;
0215: private int attachmentType = NONE;
0216: private int inLineType = BODY;
0217: private String messageText;
0218: private Collection recipients;
0219: private MailAddress replyTo;
0220: private MailAddress reversePath;
0221: private MailAddress sender;
0222: private String subject;
0223: private String subjectPrefix;
0224: private InternetAddress[] apparentlyTo;
0225: private boolean attachError = false;
0226: private boolean isReply = false;
0227:
0228: private RFC822DateFormat rfc822DateFormat = new RFC822DateFormat();
0229:
0230: /* ******************************************************************** */
0231: /* ****************** Begin of getX and setX methods ****************** */
0232: /* ******************************************************************** */
0233:
0234: /**
0235: * <P>Gets the <CODE>static</CODE> property.</P>
0236: * <P>Return true to reduce calls to getTo, getSender, getRecipients, getReplyTo, getReversePath amd getMessage
0237: * where these values don't change (eg hard coded, or got at startup from the mailet config);
0238: * return false where any of these methods generate their results dynamically eg in response to the message being processed,
0239: * or by reference to a repository of users.</P>
0240: * <P>It is now (from version 2.2) somehow obsolete, as should be always true because the "good practice"
0241: * is to use "getX()" methods statically, and use instead "getX(Mail)" methods for dynamic situations.
0242: * A false value is now meaningful only for subclasses of {@link Redirect} older than version 2.2
0243: * that were relying on this.</P>
0244: *
0245: * <P>Is a "getX()" method.</P>
0246: *
0247: * @return true, as normally "getX()" methods shouls be static
0248: */
0249: protected boolean isStatic() {
0250: return true;
0251: }
0252:
0253: /**
0254: * Gets the <CODE>passThrough</CODE> property.
0255: * Return true to allow the original message to continue through the processor, false to GHOST it.
0256: * Is a "getX()" method.
0257: *
0258: * @return the <CODE>passThrough</CODE> init parameter, or false if missing
0259: */
0260: protected boolean getPassThrough() throws MessagingException {
0261: return new Boolean(getInitParameter("passThrough"))
0262: .booleanValue();
0263: }
0264:
0265: /**
0266: * Gets the <CODE>passThrough</CODE> property,
0267: * built dynamically using the original Mail object.
0268: * Is a "getX(Mail)" method.
0269: *
0270: * @return {@link #getPassThrough()}
0271: */
0272: protected boolean getPassThrough(Mail originalMail)
0273: throws MessagingException {
0274: return (isStatic()) ? this .passThrough : getPassThrough();
0275: }
0276:
0277: /**
0278: * Gets the <CODE>fakeDomainCheck</CODE> property.
0279: * Return true to check if the sender domain is valid.
0280: * Is a "getX()" method.
0281: *
0282: * @return the <CODE>fakeDomainCheck</CODE> init parameter, or true if missing
0283: */
0284: protected boolean getFakeDomainCheck() throws MessagingException {
0285: return new Boolean(getInitParameter("fakeDomainCheck"))
0286: .booleanValue();
0287: }
0288:
0289: /**
0290: * Gets the <CODE>fakeDomainCheck</CODE> property,
0291: * built dynamically using the original Mail object.
0292: * Is a "getX(Mail)" method.
0293: *
0294: * @return {@link #getFakeDomainCheck()}
0295: */
0296: protected boolean getFakeDomainCheck(Mail originalMail)
0297: throws MessagingException {
0298: return (isStatic()) ? this .fakeDomainCheck
0299: : getFakeDomainCheck();
0300: }
0301:
0302: /**
0303: * Gets the <CODE>inline</CODE> property.
0304: * May return one of the following values to indicate how to append the original message
0305: * to build the new message:
0306: * <ul>
0307: * <li><CODE>UNALTERED</CODE> : original message is the new message body</li>
0308: * <li><CODE>BODY</CODE> : original message body is appended to the new message</li>
0309: * <li><CODE>HEADS</CODE> : original message headers are appended to the new message</li>
0310: * <li><CODE>ALL</CODE> : original is appended with all headers</li>
0311: * <li><CODE>NONE</CODE> : original is not appended</li>
0312: * </ul>
0313: * Is a "getX()" method.
0314: *
0315: * @return the <CODE>inline</CODE> init parameter, or <CODE>UNALTERED</CODE> if missing
0316: */
0317: protected int getInLineType() throws MessagingException {
0318: return getTypeCode(getInitParameter("inline", "unaltered"));
0319: }
0320:
0321: /**
0322: * Gets the <CODE>inline</CODE> property,
0323: * built dynamically using the original Mail object.
0324: * Is a "getX(Mail)" method.
0325: *
0326: * @return {@link #getInLineType()}
0327: */
0328: protected int getInLineType(Mail originalMail)
0329: throws MessagingException {
0330: return (isStatic()) ? this .inLineType : getInLineType();
0331: }
0332:
0333: /** Gets the <CODE>attachment</CODE> property.
0334: * May return one of the following values to indicate how to attach the original message
0335: * to the new message:
0336: * <ul>
0337: * <li><CODE>BODY</CODE> : original message body is attached as plain text to the new message</li>
0338: * <li><CODE>HEADS</CODE> : original message headers are attached as plain text to the new message</li>
0339: * <li><CODE>ALL</CODE> : original is attached as plain text with all headers</li>
0340: * <li><CODE>MESSAGE</CODE> : original message is attached as type message/rfc822, a complete mail message.</li>
0341: * <li><CODE>NONE</CODE> : original is not attached</li>
0342: * </ul>
0343: * Is a "getX()" method.
0344: *
0345: * @return the <CODE>attachment</CODE> init parameter, or <CODE>NONE</CODE> if missing
0346: */
0347: protected int getAttachmentType() throws MessagingException {
0348: return getTypeCode(getInitParameter("attachment", "none"));
0349: }
0350:
0351: /**
0352: * Gets the <CODE>attachment</CODE> property,
0353: * built dynamically using the original Mail object.
0354: * Is a "getX(Mail)" method.
0355: *
0356: * @return {@link #getAttachmentType()}
0357: */
0358: protected int getAttachmentType(Mail originalMail)
0359: throws MessagingException {
0360: return (isStatic()) ? this .attachmentType : getAttachmentType();
0361: }
0362:
0363: /**
0364: * Gets the <CODE>message</CODE> property.
0365: * Returns a message to which the original message can be attached/appended
0366: * to build the new message.
0367: * Is a "getX()" method.
0368: *
0369: * @return the <CODE>message</CODE> init parameter or an empty string if missing
0370: */
0371: protected String getMessage() throws MessagingException {
0372: return getInitParameter("message", "");
0373: }
0374:
0375: /**
0376: * Gets the <CODE>message</CODE> property,
0377: * built dynamically using the original Mail object.
0378: * Is a "getX(Mail)" method.
0379: *
0380: * @return {@link #getMessage()}
0381: */
0382: protected String getMessage(Mail originalMail)
0383: throws MessagingException {
0384: return (isStatic()) ? this .messageText : getMessage();
0385: }
0386:
0387: /**
0388: * Gets the <CODE>recipients</CODE> property.
0389: * Returns the collection of recipients of the new message,
0390: * or null if no change is requested.
0391: * Is a "getX()" method.
0392: *
0393: * @return the <CODE>recipients</CODE> init parameter
0394: * or the postmaster address
0395: * or <CODE>SpecialAddress.SENDER</CODE>
0396: * or <CODE>SpecialAddress.FROM</CODE>
0397: * or <CODE>SpecialAddress.REPLY_TO</CODE>
0398: * or <CODE>SpecialAddress.REVERSE_PATH</CODE>
0399: * or <CODE>SpecialAddress.UNALTERED</CODE>
0400: * or <CODE>SpecialAddress.RECIPIENTS</CODE>
0401: * or <CODE>null</CODE> if missing
0402: */
0403: protected Collection getRecipients() throws MessagingException {
0404: Collection newRecipients = new HashSet();
0405: String addressList = getInitParameter("recipients");
0406:
0407: // if nothing was specified, return <CODE>null</CODE> meaning no change
0408: if (addressList == null) {
0409: return null;
0410: }
0411:
0412: try {
0413: InternetAddress[] iaarray = InternetAddress.parse(
0414: addressList, false);
0415: for (int i = 0; i < iaarray.length; i++) {
0416: String addressString = iaarray[i].getAddress();
0417: MailAddress specialAddress = getSpecialAddress(
0418: addressString, new String[] { "postmaster",
0419: "sender", "from", "replyTo",
0420: "reversePath", "unaltered",
0421: "recipients", "to", "null" });
0422: if (specialAddress != null) {
0423: newRecipients.add(specialAddress);
0424: } else {
0425: newRecipients.add(new MailAddress(iaarray[i]));
0426: }
0427: }
0428: } catch (Exception e) {
0429: throw new MessagingException(
0430: "Exception thrown in getRecipients() parsing: "
0431: + addressList, e);
0432: }
0433: if (newRecipients.size() == 0) {
0434: throw new MessagingException(
0435: "Failed to initialize \"recipients\" list; empty <recipients> init parameter found.");
0436: }
0437:
0438: return newRecipients;
0439: }
0440:
0441: /**
0442: * Gets the <CODE>recipients</CODE> property,
0443: * built dynamically using the original Mail object.
0444: * Is a "getX(Mail)" method.
0445: *
0446: * @return {@link #replaceMailAddresses} on {@link #getRecipients()},
0447: */
0448: protected Collection getRecipients(Mail originalMail)
0449: throws MessagingException {
0450: Collection recipients = (isStatic()) ? this .recipients
0451: : getRecipients();
0452: if (recipients != null) {
0453: if (recipients.size() == 1
0454: && (recipients.contains(SpecialAddress.UNALTERED) || recipients
0455: .contains(SpecialAddress.RECIPIENTS))) {
0456: recipients = null;
0457: } else {
0458: recipients = replaceMailAddresses(originalMail,
0459: recipients);
0460: }
0461: }
0462: return recipients;
0463: }
0464:
0465: /**
0466: * Sets the recipients of <I>newMail</I> to <I>recipients</I>.
0467: * If the requested value is null does nothing.
0468: * Is a "setX(Mail, Tx, Mail)" method.
0469: */
0470: protected void setRecipients(Mail newMail, Collection recipients,
0471: Mail originalMail) throws MessagingException {
0472: if (recipients != null) {
0473: newMail.setRecipients(recipients);
0474: if (isDebug) {
0475: log("recipients set to: "
0476: + arrayToString(recipients.toArray()));
0477: }
0478: }
0479: }
0480:
0481: /**
0482: * Gets the <CODE>to</CODE> property.
0483: * Returns the "To:" recipients of the new message.
0484: * or null if no change is requested.
0485: * Is a "getX()" method.
0486: *
0487: * @return the <CODE>to</CODE> init parameter
0488: * or the postmaster address
0489: * or <CODE>SpecialAddress.SENDER</CODE>
0490: * or <CODE>SpecialAddress.REVERSE_PATH</CODE>
0491: * or <CODE>SpecialAddress.FROM</CODE>
0492: * or <CODE>SpecialAddress.REPLY_TO</CODE>
0493: * or <CODE>SpecialAddress.UNALTERED</CODE>
0494: * or <CODE>SpecialAddress.TO</CODE>
0495: * or <CODE>null</CODE> if missing
0496: */
0497: protected InternetAddress[] getTo() throws MessagingException {
0498: InternetAddress[] iaarray = null;
0499: String addressList = getInitParameter("to");
0500:
0501: // if nothing was specified, return null meaning no change
0502: if (addressList == null) {
0503: return null;
0504: }
0505:
0506: try {
0507: iaarray = InternetAddress.parse(addressList, false);
0508: for (int i = 0; i < iaarray.length; ++i) {
0509: String addressString = iaarray[i].getAddress();
0510: MailAddress specialAddress = getSpecialAddress(
0511: addressString, new String[] { "postmaster",
0512: "sender", "from", "replyTo",
0513: "reversePath", "unaltered",
0514: "recipients", "to", "null" });
0515: if (specialAddress != null) {
0516: iaarray[i] = specialAddress.toInternetAddress();
0517: }
0518: }
0519: } catch (Exception e) {
0520: throw new MessagingException(
0521: "Exception thrown in getTo() parsing: "
0522: + addressList, e);
0523: }
0524: if (iaarray.length == 0) {
0525: throw new MessagingException(
0526: "Failed to initialize \"to\" list; empty <to> init parameter found.");
0527: }
0528:
0529: return iaarray;
0530: }
0531:
0532: /**
0533: * Gets the <CODE>to</CODE> property,
0534: * built dynamically using the original Mail object.
0535: * Its outcome will be the the value the <I>TO:</I> header will be set to,
0536: * that could be different from the real recipient (see {@link #getRecipients}).
0537: * Is a "getX(Mail)" method.
0538: *
0539: * @return {@link #replaceInternetAddresses} on {@link #getRecipients()},
0540: */
0541: protected InternetAddress[] getTo(Mail originalMail)
0542: throws MessagingException {
0543: InternetAddress[] apparentlyTo = (isStatic()) ? this .apparentlyTo
0544: : getTo();
0545: if (apparentlyTo != null) {
0546: if (apparentlyTo.length == 1
0547: && (apparentlyTo[0].equals(SpecialAddress.UNALTERED
0548: .toInternetAddress()) || apparentlyTo[0]
0549: .equals(SpecialAddress.TO
0550: .toInternetAddress()))) {
0551: apparentlyTo = null;
0552: } else {
0553: Collection toList = new ArrayList(apparentlyTo.length);
0554: for (int i = 0; i < apparentlyTo.length; i++) {
0555: toList.add(apparentlyTo[i]);
0556: }
0557: /* IMPORTANT: setTo() treats null differently from a zero length array,
0558: so it's ok to get a zero length array from replaceSpecialAddresses
0559: */
0560: apparentlyTo = (InternetAddress[]) replaceInternetAddresses(
0561: originalMail, toList).toArray(
0562: new InternetAddress[0]);
0563: }
0564: }
0565:
0566: return apparentlyTo;
0567: }
0568:
0569: /**
0570: * Sets the "To:" header of <I>newMail</I> to <I>to</I>.
0571: * If the requested value is null does nothing.
0572: * Is a "setX(Mail, Tx, Mail)" method.
0573: */
0574: protected void setTo(Mail newMail, InternetAddress[] to,
0575: Mail originalMail) throws MessagingException {
0576: if (to != null) {
0577: newMail.getMessage().setRecipients(
0578: Message.RecipientType.TO, to);
0579: if (isDebug) {
0580: log("apparentlyTo set to: " + arrayToString(to));
0581: }
0582: }
0583: }
0584:
0585: /**
0586: * Gets the <CODE>replyto</CODE> property.
0587: * Returns the Reply-To address of the new message,
0588: * or null if no change is requested.
0589: * Is a "getX()" method.
0590: *
0591: * @return the <CODE>replyto</CODE> init parameter
0592: * or the postmaster address
0593: * or <CODE>SpecialAddress.SENDER</CODE>
0594: * or <CODE>SpecialAddress.UNALTERED</CODE>
0595: * or <CODE>SpecialAddress.NULL</CODE>
0596: * or <CODE>null</CODE> if missing
0597: */
0598: protected MailAddress getReplyTo() throws MessagingException {
0599: String addressString = getInitParameter("replyTo",
0600: getInitParameter("replyto"));
0601:
0602: if (addressString != null) {
0603: MailAddress specialAddress = getSpecialAddress(
0604: addressString, new String[] { "postmaster",
0605: "sender", "null", "unaltered" });
0606: if (specialAddress != null) {
0607: return specialAddress;
0608: }
0609:
0610: try {
0611: return new MailAddress(addressString);
0612: } catch (Exception e) {
0613: throw new MessagingException(
0614: "Exception thrown in getReplyTo() parsing: "
0615: + addressString, e);
0616: }
0617: }
0618:
0619: return null;
0620: }
0621:
0622: /**
0623: * Gets the <CODE>replyTo</CODE> property,
0624: * built dynamically using the original Mail object.
0625: * Is a "getX(Mail)" method.
0626: *
0627: * @return {@link #getReplyTo()}
0628: * replacing <CODE>SpecialAddress.UNALTERED</CODE> if applicable with null
0629: * and <CODE>SpecialAddress.SENDER</CODE> with the original mail sender
0630: */
0631: protected MailAddress getReplyTo(Mail originalMail)
0632: throws MessagingException {
0633: MailAddress replyTo = (isStatic()) ? this .replyTo
0634: : getReplyTo();
0635: if (replyTo != null) {
0636: if (replyTo == SpecialAddress.UNALTERED) {
0637: replyTo = null;
0638: } else if (replyTo == SpecialAddress.SENDER) {
0639: replyTo = originalMail.getSender();
0640: }
0641: }
0642: return replyTo;
0643: }
0644:
0645: /**
0646: * <P>Sets the "Reply-To:" header of <I>newMail</I> to <I>replyTo</I>.</P>
0647: * If the requested value is <CODE>SpecialAddress.NULL</CODE> will remove the "Reply-To:" header.
0648: * If the requested value is null does nothing.</P>
0649: * Is a "setX(Mail, Tx, Mail)" method.
0650: */
0651: protected void setReplyTo(Mail newMail, MailAddress replyTo,
0652: Mail originalMail) throws MessagingException {
0653: if (replyTo != null) {
0654: InternetAddress[] iart = null;
0655: if (replyTo != SpecialAddress.NULL) {
0656: iart = new InternetAddress[1];
0657: iart[0] = replyTo.toInternetAddress();
0658: }
0659:
0660: // Note: if iart is null will remove the header
0661: newMail.getMessage().setReplyTo(iart);
0662:
0663: if (isDebug) {
0664: log("replyTo set to: " + replyTo);
0665: }
0666: }
0667: }
0668:
0669: /**
0670: * Gets the <CODE>reversePath</CODE> property.
0671: * Returns the reverse-path of the new message,
0672: * or null if no change is requested.
0673: * Is a "getX()" method.
0674: *
0675: * @return the <CODE>reversePath</CODE> init parameter
0676: * or the postmaster address
0677: * or <CODE>SpecialAddress.SENDER</CODE>
0678: * or <CODE>SpecialAddress.NULL</CODE>
0679: * or <CODE>SpecialAddress.UNALTERED</CODE>
0680: * or <CODE>null</CODE> if missing
0681: */
0682: protected MailAddress getReversePath() throws MessagingException {
0683: String addressString = getInitParameter("reversePath");
0684: if (addressString != null) {
0685: MailAddress specialAddress = getSpecialAddress(
0686: addressString, new String[] { "postmaster",
0687: "sender", "null", "unaltered" });
0688: if (specialAddress != null) {
0689: return specialAddress;
0690: }
0691:
0692: try {
0693: return new MailAddress(addressString);
0694: } catch (Exception e) {
0695: throw new MessagingException(
0696: "Exception thrown in getReversePath() parsing: "
0697: + addressString, e);
0698: }
0699: }
0700:
0701: return null;
0702: }
0703:
0704: /**
0705: * Gets the <CODE>reversePath</CODE> property,
0706: * built dynamically using the original Mail object.
0707: * Is a "getX(Mail)" method.
0708: *
0709: * @return {@link #getReversePath()},
0710: * replacing <CODE>SpecialAddress.SENDER</CODE> if applicable with null,
0711: * replacing <CODE>SpecialAddress.UNALTERED</CODE>
0712: * and <CODE>SpecialAddress.REVERSE_PATH</CODE> if applicable with null,
0713: * but not replacing <CODE>SpecialAddress.NULL</CODE>
0714: * that will be handled by {@link #setReversePath}
0715: */
0716: protected MailAddress getReversePath(Mail originalMail)
0717: throws MessagingException {
0718: MailAddress reversePath = (isStatic()) ? this .reversePath
0719: : getReversePath();
0720: if (reversePath != null) {
0721: if (reversePath == SpecialAddress.UNALTERED
0722: || reversePath == SpecialAddress.REVERSE_PATH) {
0723: reversePath = null;
0724: } else if (reversePath == SpecialAddress.SENDER) {
0725: reversePath = null;
0726: }
0727: }
0728: return reversePath;
0729: }
0730:
0731: /**
0732: * Sets the "reverse-path" of <I>newMail</I> to <I>reversePath</I>.
0733: * If the requested value is <CODE>SpecialAddress.NULL</CODE> sets it to "<>".
0734: * If the requested value is null does nothing.
0735: * Is a "setX(Mail, Tx, Mail)" method.
0736: */
0737: protected void setReversePath(MailImpl newMail,
0738: MailAddress reversePath, Mail originalMail)
0739: throws MessagingException {
0740: if (reversePath != null) {
0741: if (reversePath == SpecialAddress.NULL) {
0742: reversePath = null;
0743: }
0744: newMail.setSender(reversePath);
0745: if (isDebug) {
0746: log("reversePath set to: " + reversePath);
0747: }
0748: }
0749: }
0750:
0751: /**
0752: * Gets the <CODE>sender</CODE> property.
0753: * Returns the new sender as a MailAddress,
0754: * or null if no change is requested.
0755: * Is a "getX()" method.
0756: *
0757: * @return the <CODE>sender</CODE> init parameter
0758: * or the postmaster address
0759: * or <CODE>SpecialAddress.SENDER</CODE>
0760: * or <CODE>SpecialAddress.UNALTERED</CODE>
0761: * or <CODE>null</CODE> if missing
0762: */
0763: protected MailAddress getSender() throws MessagingException {
0764: String addressString = getInitParameter("sender");
0765: if (addressString != null) {
0766: MailAddress specialAddress = getSpecialAddress(
0767: addressString, new String[] { "postmaster",
0768: "sender", "unaltered" });
0769: if (specialAddress != null) {
0770: return specialAddress;
0771: }
0772:
0773: try {
0774: return new MailAddress(addressString);
0775: } catch (Exception e) {
0776: throw new MessagingException(
0777: "Exception thrown in getSender() parsing: "
0778: + addressString, e);
0779: }
0780: }
0781:
0782: return null;
0783: }
0784:
0785: /**
0786: * Gets the <CODE>sender</CODE> property,
0787: * built dynamically using the original Mail object.
0788: * Is a "getX(Mail)" method.
0789: *
0790: * @return {@link #getSender()}
0791: * replacing <CODE>SpecialAddress.UNALTERED</CODE>
0792: * and <CODE>SpecialAddress.SENDER</CODE> if applicable with null
0793: */
0794: protected MailAddress getSender(Mail originalMail)
0795: throws MessagingException {
0796: MailAddress sender = (isStatic()) ? this .sender : getSender();
0797: if (sender != null) {
0798: if (sender == SpecialAddress.UNALTERED
0799: || sender == SpecialAddress.SENDER) {
0800: sender = null;
0801: }
0802: }
0803: return sender;
0804: }
0805:
0806: /**
0807: * Sets the "From:" header of <I>newMail</I> to <I>sender</I>.
0808: * If the requested value is null does nothing.
0809: * Is a "setX(Mail, Tx, Mail)" method.
0810: */
0811: protected void setSender(Mail newMail, MailAddress sender,
0812: Mail originalMail) throws MessagingException {
0813: if (sender != null) {
0814: newMail.getMessage().setFrom(sender.toInternetAddress());
0815:
0816: if (isDebug) {
0817: log("sender set to: " + sender);
0818: }
0819: }
0820: }
0821:
0822: /**
0823: * Gets the <CODE>subject</CODE> property.
0824: * Returns a string for the new message subject.
0825: * Is a "getX()" method.
0826: *
0827: * @return the <CODE>subject</CODE> init parameter or null if missing
0828: */
0829: protected String getSubject() throws MessagingException {
0830: return getInitParameter("subject");
0831: }
0832:
0833: /**
0834: * Gets the <CODE>subject</CODE> property,
0835: * built dynamically using the original Mail object.
0836: * Is a "getX(Mail)" method.
0837: *
0838: * @return {@link #getSubject()}
0839: */
0840: protected String getSubject(Mail originalMail)
0841: throws MessagingException {
0842: return (isStatic()) ? this .subject : getSubject();
0843: }
0844:
0845: /**
0846: * Gets the <CODE>prefix</CODE> property.
0847: * Returns a prefix for the new message subject.
0848: * Is a "getX()" method.
0849: *
0850: * @return the <CODE>prefix</CODE> init parameter or an empty string if missing
0851: */
0852: protected String getSubjectPrefix() throws MessagingException {
0853: return getInitParameter("prefix");
0854: }
0855:
0856: /**
0857: * Gets the <CODE>subjectPrefix</CODE> property,
0858: * built dynamically using the original Mail object.
0859: * Is a "getX(Mail)" method.
0860: *
0861: * @return {@link #getSubjectPrefix()}
0862: */
0863: protected String getSubjectPrefix(Mail originalMail)
0864: throws MessagingException {
0865: return (isStatic()) ? this .subjectPrefix : getSubjectPrefix();
0866: }
0867:
0868: /**
0869: * Builds the subject of <I>newMail</I> appending the subject
0870: * of <I>originalMail</I> to <I>subjectPrefix</I>.
0871: * Is a "setX(Mail, Tx, Mail)" method.
0872: */
0873: protected void setSubjectPrefix(Mail newMail, String subjectPrefix,
0874: Mail originalMail) throws MessagingException {
0875: String subject = getSubject(originalMail);
0876: if ((subjectPrefix != null && subjectPrefix.length() > 0)
0877: || subject != null) {
0878: if (subject == null) {
0879: subject = originalMail.getMessage().getSubject();
0880: } else {
0881: // replacing the subject
0882: if (isDebug) {
0883: log("subject set to: " + subject);
0884: }
0885: }
0886: // Was null in original?
0887: if (subject == null) {
0888: subject = "";
0889: }
0890:
0891: if (subjectPrefix != null) {
0892: subject = subjectPrefix + subject;
0893: // adding a prefix
0894: if (isDebug) {
0895: log("subjectPrefix set to: " + subjectPrefix);
0896: }
0897: }
0898: // newMail.getMessage().setSubject(subject);
0899: changeSubject(newMail.getMessage(), subject);
0900: }
0901: }
0902:
0903: /**
0904: * Gets the <CODE>attachError</CODE> property.
0905: * Returns a boolean indicating whether to append a description of any error to the main body part
0906: * of the new message, if getInlineType does not return "UNALTERED".
0907: * Is a "getX()" method.
0908: *
0909: * @return the <CODE>attachError</CODE> init parameter; false if missing
0910: */
0911: protected boolean attachError() throws MessagingException {
0912: return new Boolean(getInitParameter("attachError"))
0913: .booleanValue();
0914: }
0915:
0916: /**
0917: * Gets the <CODE>attachError</CODE> property,
0918: * built dynamically using the original Mail object.
0919: * Is a "getX(Mail)" method.
0920: *
0921: * @return {@link #attachError()}
0922: */
0923: protected boolean attachError(Mail originalMail)
0924: throws MessagingException {
0925: return (isStatic()) ? this .attachError : attachError();
0926: }
0927:
0928: /**
0929: * Gets the <CODE>isReply</CODE> property.
0930: * Returns a boolean indicating whether the new message must be considered
0931: * a reply to the original message, setting the IN_REPLY_TO header of the new
0932: * message to the id of the original message.
0933: * Is a "getX()" method.
0934: *
0935: * @return the <CODE>isReply</CODE> init parameter; false if missing
0936: */
0937: protected boolean isReply() throws MessagingException {
0938: return new Boolean(getInitParameter("isReply")).booleanValue();
0939: }
0940:
0941: /**
0942: * Gets the <CODE>isReply</CODE> property,
0943: * built dynamically using the original Mail object.
0944: * Is a "getX(Mail)" method.
0945: *
0946: * @return {@link #isReply()}
0947: */
0948: protected boolean isReply(Mail originalMail)
0949: throws MessagingException {
0950: return (isStatic()) ? this .isReply : isReply();
0951: }
0952:
0953: /**
0954: * Sets the "In-Reply-To:" header of <I>newMail</I> to the "Message-Id:" of
0955: * <I>originalMail</I>, if <I>isReply</I> is true.
0956: */
0957: protected void setIsReply(Mail newMail, boolean isReply,
0958: Mail originalMail) throws MessagingException {
0959: if (isReply) {
0960: String messageId = originalMail.getMessage().getMessageID();
0961: if (messageId != null) {
0962: newMail.getMessage().setHeader(
0963: RFC2822Headers.IN_REPLY_TO, messageId);
0964: if (isDebug) {
0965: log("IN_REPLY_TO set to: " + messageId);
0966: }
0967: }
0968: }
0969: }
0970:
0971: /* ******************************************************************** */
0972: /* ******************* End of getX and setX methods ******************* */
0973: /* ******************************************************************** */
0974:
0975: /**
0976: * Mailet initialization routine.
0977: * Will setup static values for each "x" initialization parameter in config.xml,
0978: * using getX(), if {@link #isStatic()} returns true.
0979: */
0980: public void init() throws MessagingException {
0981: isDebug = new Boolean(getInitParameter("debug", "false"))
0982: .booleanValue();
0983:
0984: isStatic = new Boolean(getInitParameter("static", "false"))
0985: .booleanValue();
0986:
0987: if (isDebug) {
0988: log("Initializing");
0989: }
0990:
0991: // check that all init parameters have been declared in allowedInitParameters
0992: checkInitParameters(getAllowedInitParameters());
0993:
0994: if (isStatic()) {
0995: passThrough = getPassThrough();
0996: fakeDomainCheck = getFakeDomainCheck();
0997: attachmentType = getAttachmentType();
0998: inLineType = getInLineType();
0999: messageText = getMessage();
1000: recipients = getRecipients();
1001: replyTo = getReplyTo();
1002: reversePath = getReversePath();
1003: sender = getSender();
1004: subject = getSubject();
1005: subjectPrefix = getSubjectPrefix();
1006: apparentlyTo = getTo();
1007: attachError = attachError();
1008: isReply = isReply();
1009: if (isDebug) {
1010: StringBuffer logBuffer = new StringBuffer(1024).append(
1011: "static").append(", passThrough=").append(
1012: passThrough).append(", fakeDomainCheck=")
1013: .append(fakeDomainCheck).append(", sender=")
1014: .append(sender).append(", replyTo=").append(
1015: replyTo).append(", reversePath=")
1016: .append(reversePath).append(", message=")
1017: .append(messageText).append(", recipients=")
1018: .append(
1019: arrayToString(recipients == null ? null
1020: : recipients.toArray()))
1021: .append(", subject=").append(subject).append(
1022: ", subjectPrefix=").append(
1023: subjectPrefix)
1024: .append(", apparentlyTo=").append(
1025: arrayToString(apparentlyTo)).append(
1026: ", attachError=").append(attachError)
1027: .append(", isReply=").append(isReply).append(
1028: ", attachmentType=").append(
1029: attachmentType).append(", inLineType=")
1030: .append(inLineType).append(" ");
1031: log(logBuffer.toString());
1032: }
1033: }
1034: }
1035:
1036: /**
1037: * Service does the hard work,and redirects the originalMail in the form specified.
1038: *
1039: * @param originalMail the mail to process and redirect
1040: * @throws MessagingException if a problem arises formulating the redirected mail
1041: */
1042: public void service(Mail originalMail) throws MessagingException {
1043:
1044: boolean keepMessageId = false;
1045:
1046: // duplicates the Mail object, to be able to modify the new mail keeping the original untouched
1047: MailImpl newMail = new MailImpl(originalMail,
1048: newName(originalMail));
1049: try {
1050: // We don't need to use the original Remote Address and Host,
1051: // and doing so would likely cause a loop with spam detecting
1052: // matchers.
1053: try {
1054: newMail.setRemoteAddr(java.net.InetAddress
1055: .getLocalHost().getHostAddress());
1056: newMail.setRemoteHost(java.net.InetAddress
1057: .getLocalHost().getHostName());
1058: } catch (java.net.UnknownHostException _) {
1059: newMail.setRemoteAddr("127.0.0.1");
1060: newMail.setRemoteHost("localhost");
1061: }
1062:
1063: if (isDebug) {
1064: log("New mail - sender: "
1065: + newMail.getSender()
1066: + ", recipients: "
1067: + arrayToString(newMail.getRecipients()
1068: .toArray()) + ", name: "
1069: + newMail.getName() + ", remoteHost: "
1070: + newMail.getRemoteHost() + ", remoteAddr: "
1071: + newMail.getRemoteAddr() + ", state: "
1072: + newMail.getState() + ", lastUpdated: "
1073: + newMail.getLastUpdated() + ", errorMessage: "
1074: + newMail.getErrorMessage());
1075: }
1076:
1077: //Create the message
1078: if (getInLineType(originalMail) != UNALTERED) {
1079: if (isDebug) {
1080: log("Alter message");
1081: }
1082: newMail.setMessage(new MimeMessage(Session
1083: .getDefaultInstance(System.getProperties(),
1084: null)));
1085:
1086: // handle the new message if altered
1087: buildAlteredMessage(newMail, originalMail);
1088:
1089: } else {
1090: // if we need the original, create a copy of this message to redirect
1091: if (getPassThrough(originalMail)) {
1092: newMail.setMessage(new MimeMessage(originalMail
1093: .getMessage()) {
1094: protected void updateHeaders()
1095: throws MessagingException {
1096: if (getMessageID() == null)
1097: super .updateHeaders();
1098: else {
1099: modified = false;
1100: }
1101: }
1102: });
1103: }
1104: if (isDebug) {
1105: log("Message resent unaltered.");
1106: }
1107: keepMessageId = true;
1108: }
1109:
1110: //Set additional headers
1111:
1112: setRecipients(newMail, getRecipients(originalMail),
1113: originalMail);
1114:
1115: setTo(newMail, getTo(originalMail), originalMail);
1116:
1117: setSubjectPrefix(newMail, getSubjectPrefix(originalMail),
1118: originalMail);
1119:
1120: if (newMail.getMessage().getHeader(RFC2822Headers.DATE) == null) {
1121: newMail.getMessage().setHeader(RFC2822Headers.DATE,
1122: rfc822DateFormat.format(new Date()));
1123: }
1124:
1125: setReplyTo(newMail, getReplyTo(originalMail), originalMail);
1126:
1127: setReversePath(newMail, getReversePath(originalMail),
1128: originalMail);
1129:
1130: setSender(newMail, getSender(originalMail), originalMail);
1131:
1132: setIsReply(newMail, isReply(originalMail), originalMail);
1133:
1134: newMail.getMessage().saveChanges();
1135:
1136: if (keepMessageId) {
1137: setMessageId(newMail, originalMail);
1138: }
1139:
1140: if (senderDomainIsValid(newMail)) {
1141: //Send it off...
1142: getMailetContext().sendMail(newMail);
1143: } else {
1144: StringBuffer logBuffer = new StringBuffer(256).append(
1145: getMailetName()).append(
1146: " mailet cannot forward ").append(
1147: originalMail.getName()).append(
1148: ". Invalid sender domain for ").append(
1149: newMail.getSender()).append(
1150: ". Consider using the Resend mailet ").append(
1151: "using a different sender.");
1152: throw new MessagingException(logBuffer.toString());
1153: }
1154:
1155: } finally {
1156: newMail.dispose();
1157: }
1158:
1159: if (!getPassThrough(originalMail)) {
1160: originalMail.setState(Mail.GHOST);
1161: }
1162: }
1163:
1164: private static final java.util.Random random = new java.util.Random(); // Used to generate new mail names
1165:
1166: /**
1167: * Create a unique new primary key name.
1168: *
1169: * @param mail the mail to use as the basis for the new mail name
1170: * @return a new name
1171: */
1172: private String newName(Mail mail) throws MessagingException {
1173: String oldName = mail.getName();
1174:
1175: // Checking if the original mail name is too long, perhaps because of a
1176: // loop caused by a configuration error.
1177: // it could cause a "null pointer exception" in AvalonMailRepository much
1178: // harder to understand.
1179: if (oldName.length() > 76) {
1180: int count = 0;
1181: int index = 0;
1182: while ((index = oldName.indexOf('!', index + 1)) >= 0) {
1183: count++;
1184: }
1185: // It looks like a configuration loop. It's better to stop.
1186: if (count > 7) {
1187: throw new MessagingException(
1188: "Unable to create a new message name: too long."
1189: + " Possible loop in config.xml.");
1190: } else {
1191: oldName = oldName.substring(0, 76);
1192: }
1193: }
1194:
1195: StringBuffer nameBuffer = new StringBuffer(64).append(oldName)
1196: .append("-!").append(random.nextInt(1048576));
1197: return nameBuffer.toString();
1198: }
1199:
1200: /**
1201: * A private method to convert types from string to int.
1202: *
1203: * @param param the string type
1204: * @return the corresponding int enumeration
1205: */
1206: protected int getTypeCode(String param) {
1207: param = param.toLowerCase(Locale.US);
1208: if (param.compareTo("unaltered") == 0) {
1209: return UNALTERED;
1210: }
1211: if (param.compareTo("heads") == 0) {
1212: return HEADS;
1213: }
1214: if (param.compareTo("body") == 0) {
1215: return BODY;
1216: }
1217: if (param.compareTo("all") == 0) {
1218: return ALL;
1219: }
1220: if (param.compareTo("none") == 0) {
1221: return NONE;
1222: }
1223: if (param.compareTo("message") == 0) {
1224: return MESSAGE;
1225: }
1226: return NONE;
1227: }
1228:
1229: /**
1230: * Utility method for obtaining a string representation of an array of Objects.
1231: */
1232: private String arrayToString(Object[] array) {
1233: if (array == null) {
1234: return "null";
1235: }
1236: StringBuffer sb = new StringBuffer(1024);
1237: sb.append("[");
1238: for (int i = 0; i < array.length; i++) {
1239: if (i > 0) {
1240: sb.append(",");
1241: }
1242: sb.append(array[i]);
1243: }
1244: sb.append("]");
1245: return sb.toString();
1246: }
1247:
1248: /**
1249: * Utility method for obtaining a string representation of a
1250: * Message's headers
1251: */
1252: protected String getMessageHeaders(MimeMessage message)
1253: throws MessagingException {
1254: Enumeration heads = message.getAllHeaderLines();
1255: StringBuffer headBuffer = new StringBuffer(1024);
1256: while (heads.hasMoreElements()) {
1257: headBuffer.append(heads.nextElement().toString()).append(
1258: "\r\n");
1259: }
1260: return headBuffer.toString();
1261: }
1262:
1263: /**
1264: * Utility method for obtaining a string representation of a
1265: * Message's body
1266: */
1267: private String getMessageBody(MimeMessage message) throws Exception {
1268: java.io.ByteArrayOutputStream bodyOs = new java.io.ByteArrayOutputStream();
1269: MimeMessageUtil.writeMessageBodyTo(message, bodyOs);
1270: return bodyOs.toString();
1271: }
1272:
1273: /**
1274: * Builds the message of the newMail in case it has to be altered.
1275: *
1276: * @param originalMail the original Mail object
1277: * @param newMail the Mail object to build
1278: */
1279: protected void buildAlteredMessage(Mail newMail, Mail originalMail)
1280: throws MessagingException {
1281:
1282: MimeMessage originalMessage = originalMail.getMessage();
1283: MimeMessage newMessage = newMail.getMessage();
1284:
1285: // Copy the relevant headers
1286: String[] relevantHeaderNames = { RFC2822Headers.DATE,
1287: RFC2822Headers.FROM, RFC2822Headers.REPLY_TO,
1288: RFC2822Headers.TO, RFC2822Headers.SUBJECT,
1289: RFC2822Headers.RETURN_PATH };
1290: Enumeration headerEnum = originalMessage
1291: .getMatchingHeaderLines(relevantHeaderNames);
1292: while (headerEnum.hasMoreElements()) {
1293: newMessage.addHeaderLine((String) headerEnum.nextElement());
1294: }
1295:
1296: StringWriter sout = new StringWriter();
1297: PrintWriter out = new PrintWriter(sout, true);
1298: String head = getMessageHeaders(originalMessage);
1299: boolean all = false;
1300:
1301: String messageText = getMessage(originalMail);
1302: if (messageText != null) {
1303: out.println(messageText);
1304: }
1305:
1306: if (isDebug) {
1307: log("inline:" + getInLineType(originalMail));
1308: }
1309: switch (getInLineType(originalMail)) {
1310: case ALL: //ALL:
1311: all = true;
1312: case HEADS: //HEADS:
1313: out.println("Message Headers:");
1314: out.println(head);
1315: if (!all) {
1316: break;
1317: }
1318: case BODY: //BODY:
1319: out.println("Message:");
1320: try {
1321: out.println(getMessageBody(originalMessage));
1322: } catch (Exception e) {
1323: out.println("body unavailable");
1324: }
1325: break;
1326: default:
1327: case NONE: //NONE:
1328: break;
1329: }
1330:
1331: try {
1332: //Create the message body
1333: MimeMultipart multipart = new MimeMultipart("mixed");
1334:
1335: // Create the message
1336: MimeMultipart mpContent = new MimeMultipart("alternative");
1337: MimeBodyPart contentPartRoot = new MimeBodyPart();
1338: contentPartRoot.setContent(mpContent);
1339:
1340: multipart.addBodyPart(contentPartRoot);
1341:
1342: MimeBodyPart part = new MimeBodyPart();
1343: part.setText(sout.toString());
1344: part.setDisposition("inline");
1345: mpContent.addBodyPart(part);
1346: if (isDebug) {
1347: log("attachmentType:" + getAttachmentType(originalMail));
1348: }
1349: if (getAttachmentType(originalMail) != NONE) {
1350: part = new MimeBodyPart();
1351: switch (getAttachmentType(originalMail)) {
1352: case HEADS: //HEADS:
1353: part.setText(head);
1354: break;
1355: case BODY: //BODY:
1356: try {
1357: part.setText(getMessageBody(originalMessage));
1358: } catch (Exception e) {
1359: part.setText("body unavailable");
1360: }
1361: break;
1362: case ALL: //ALL:
1363: StringBuffer textBuffer = new StringBuffer(1024)
1364: .append(head).append("\r\nMessage:\r\n")
1365: .append(getMessageBody(originalMessage));
1366: part.setText(textBuffer.toString());
1367: break;
1368: case MESSAGE: //MESSAGE:
1369: part.setContent(originalMessage, "message/rfc822");
1370: break;
1371: }
1372: if ((originalMessage.getSubject() != null)
1373: && (originalMessage.getSubject().trim()
1374: .length() > 0)) {
1375: part.setFileName(originalMessage.getSubject()
1376: .trim());
1377: } else {
1378: part.setFileName("No Subject");
1379: }
1380: part.setDisposition("Attachment");
1381: multipart.addBodyPart(part);
1382: }
1383: //if set, attach the original mail's error message
1384: if (attachError(originalMail)
1385: && originalMail.getErrorMessage() != null) {
1386: part = new MimeBodyPart();
1387: part.setContent(originalMail.getErrorMessage(),
1388: "text/plain");
1389: part.setHeader(RFC2822Headers.CONTENT_TYPE,
1390: "text/plain");
1391: part.setFileName("Reasons");
1392: part.setDisposition(javax.mail.Part.ATTACHMENT);
1393: multipart.addBodyPart(part);
1394: }
1395: newMail.getMessage().setContent(multipart);
1396: newMail.getMessage().setHeader(RFC2822Headers.CONTENT_TYPE,
1397: multipart.getContentType());
1398:
1399: } catch (Exception ioe) {
1400: throw new MessagingException(
1401: "Unable to create multipart body", ioe);
1402: }
1403: }
1404:
1405: /**
1406: * Sets the message id of originalMail into newMail.
1407: */
1408: private void setMessageId(Mail newMail, Mail originalMail)
1409: throws MessagingException {
1410: String messageId = originalMail.getMessage().getMessageID();
1411: if (messageId != null) {
1412: newMail.getMessage().setHeader(RFC2822Headers.MESSAGE_ID,
1413: messageId);
1414: if (isDebug) {
1415: log("MESSAGE_ID restored to: " + messageId);
1416: }
1417: }
1418: }
1419:
1420: /**
1421: * Returns the {@link SpecialAddress} that corresponds to an init parameter value.
1422: * The init parameter value is checked against a String[] of allowed values.
1423: * The checks are case insensitive.
1424: *
1425: * @param addressString the string to check if is a special address
1426: * @param allowedSpecials a String[] with the allowed special addresses
1427: * @return a SpecialAddress if found, null if not found or addressString is null
1428: * @throws MessagingException if is a special address not in the allowedSpecials array
1429: */
1430: protected final MailAddress getSpecialAddress(String addressString,
1431: String[] allowedSpecials) throws MessagingException {
1432: if (addressString == null) {
1433: return null;
1434: }
1435:
1436: addressString = addressString.toLowerCase(Locale.US);
1437: addressString = addressString.trim();
1438:
1439: MailAddress specialAddress = null;
1440:
1441: if (addressString.compareTo("postmaster") == 0) {
1442: specialAddress = getMailetContext().getPostmaster();
1443: }
1444: if (addressString.compareTo("sender") == 0) {
1445: specialAddress = SpecialAddress.SENDER;
1446: }
1447: if (addressString.compareTo("reversepath") == 0) {
1448: specialAddress = SpecialAddress.REVERSE_PATH;
1449: }
1450: if (addressString.compareTo("from") == 0) {
1451: specialAddress = SpecialAddress.FROM;
1452: }
1453: if (addressString.compareTo("replyto") == 0) {
1454: specialAddress = SpecialAddress.REPLY_TO;
1455: }
1456: if (addressString.compareTo("to") == 0) {
1457: specialAddress = SpecialAddress.TO;
1458: }
1459: if (addressString.compareTo("recipients") == 0) {
1460: specialAddress = SpecialAddress.RECIPIENTS;
1461: }
1462: if (addressString.compareTo("delete") == 0) {
1463: specialAddress = SpecialAddress.DELETE;
1464: }
1465: if (addressString.compareTo("unaltered") == 0) {
1466: specialAddress = SpecialAddress.UNALTERED;
1467: }
1468: if (addressString.compareTo("null") == 0) {
1469: specialAddress = SpecialAddress.NULL;
1470: }
1471:
1472: // if is a special address, must be in the allowedSpecials array
1473: if (specialAddress != null) {
1474: // check if is an allowed special
1475: boolean allowed = false;
1476: for (int i = 0; i < allowedSpecials.length; i++) {
1477: String allowedSpecial = allowedSpecials[i];
1478: allowedSpecial = allowedSpecial.toLowerCase(Locale.US);
1479: allowedSpecial = allowedSpecial.trim();
1480: if (addressString.compareTo(allowedSpecial) == 0) {
1481: allowed = true;
1482: break;
1483: }
1484: }
1485: if (!allowed) {
1486: throw new MessagingException(
1487: "Special (\"magic\") address found not allowed: "
1488: + addressString
1489: + ", allowed values are \""
1490: + arrayToString(allowedSpecials) + "\"");
1491: }
1492: }
1493:
1494: return specialAddress;
1495: }
1496:
1497: /**
1498: * <P>Checks if a sender domain of <I>mail</I> is valid.</P>
1499: * <P>If we do not do this check, and someone uses a redirection mailet in a
1500: * processor initiated by SenderInFakeDomain, then a fake
1501: * sender domain will cause an infinite loop (the forwarded
1502: * e-mail still appears to come from a fake domain).<BR>
1503: * Although this can be viewed as a configuration error, the
1504: * consequences of such a mis-configuration are severe enough
1505: * to warrant protecting against the infinite loop.</P>
1506: * <P>This check can be skipped if {@link #getFakeDomainCheck(Mail)} returns true.</P>
1507: *
1508: * @param mail the mail object to check
1509: * @return true if the if the sender is null or
1510: * {@link org.apache.mailet.MailetContext#getMailServers} returns true for
1511: * the sender host part
1512: */
1513: protected final boolean senderDomainIsValid(Mail mail)
1514: throws MessagingException {
1515: if (getFakeDomainCheck(mail)) {
1516: return mail.getSender() == null
1517: || getMailetContext().getMailServers(
1518: mail.getSender().getHost()).size() != 0;
1519: } else
1520: return true;
1521: }
1522:
1523: /**
1524: * Checks if there are unallowed init parameters specified in the configuration file
1525: * against the String[] allowedInitParameters.
1526: */
1527: private void checkInitParameters(String[] allowedArray)
1528: throws MessagingException {
1529: // if null then no check is requested
1530: if (allowedArray == null) {
1531: return;
1532: }
1533:
1534: Collection allowed = new HashSet();
1535: Collection bad = new ArrayList();
1536:
1537: for (int i = 0; i < allowedArray.length; i++) {
1538: allowed.add(allowedArray[i]);
1539: }
1540:
1541: Iterator iterator = getInitParameterNames();
1542: while (iterator.hasNext()) {
1543: String parameter = (String) iterator.next();
1544: if (!allowed.contains(parameter)) {
1545: bad.add(parameter);
1546: }
1547: }
1548:
1549: if (bad.size() > 0) {
1550: throw new MessagingException(
1551: "Unexpected init parameters found: "
1552: + arrayToString(bad.toArray()));
1553: }
1554: }
1555:
1556: /**
1557: * It changes the subject of the supplied message to to supplied value
1558: * but it also tries to preserve the original charset information.<BR>
1559: *
1560: * This method was needed to avoid sending the subject using a charset
1561: * (usually the default charset on the server) which doesn't contain
1562: * the characters in the subject, resulting in the loss of these characters.
1563: * The most simple method would be to either send it in ASCII unencoded
1564: * or in UTF-8 if non-ASCII characters are present but unfortunately UTF-8
1565: * is not yet a MIME standard and not all email clients
1566: * are supporting it. The optimal method would be to determine the best
1567: * charset by analyzing the actual characters. That would require much
1568: * more work (exept if an open source library already exists for this).
1569: * However there is nothing to stop somebody to add a detection algorithm
1570: * for a specific charset. <BR>
1571: *
1572: * The current algorithm works correctly if only ASCII characters are
1573: * added to an existing subject.<BR>
1574: *
1575: * If the new value is ASCII only, then it doesn't apply any encoding to
1576: * the subject header. (This is provided by MimeMessage.setSubject()).<BR>
1577: *
1578: * Possible enhancement: under java 1.4 java.nio the system can determine if the
1579: * suggested charset fits or not (if there is untranslatable
1580: * characters). If the charset doesn't fit the new value, it
1581: * can fall back to UTF-8.<BR>
1582: *
1583: * @param message the message of which subject is changed
1584: * @param newValue the new (unencoded) value of the subject. It must
1585: * not be null.
1586: * @throws MessagingException - according to the JavaMail doc most likely
1587: * this is never thrown
1588: */
1589: public static void changeSubject(MimeMessage message,
1590: String newValue) throws MessagingException {
1591: String rawSubject = message.getHeader(RFC2822Headers.SUBJECT,
1592: null);
1593: String mimeCharset = determineMailHeaderEncodingCharset(rawSubject);
1594: if (mimeCharset == null) { // most likely ASCII
1595: // it uses the system charset or the value of the
1596: // mail.mime.charset property if set
1597: message.setSubject(newValue);
1598: return;
1599: } else { // original charset determined
1600: String javaCharset = javax.mail.internet.MimeUtility
1601: .javaCharset(mimeCharset);
1602: try {
1603: message.setSubject(newValue, javaCharset);
1604: } catch (MessagingException e) {
1605: // known, but unsupported encoding
1606: // this should be logged, the admin may setup a more i18n
1607: // capable JRE, but the log API cannot be accessed from here
1608: //if (charset != null) log(charset +
1609: // " charset unsupported by the JRE, email subject may be damaged");
1610: message.setSubject(newValue); // recover
1611: }
1612: }
1613: }
1614:
1615: /**
1616: * It attempts to determine the charset used to encode an "unstructured"
1617: * RFC 822 header (like Subject). The encoding is specified in RFC 2047.
1618: * If it cannot determine or the the text is not encoded then it returns null.
1619: *
1620: * Here is an example raw text:
1621: * Subject: =?iso-8859-2?Q?leg=FAjabb_pr=F3ba_l=F5elemmel?=
1622: *
1623: * @param rawText the raw (not decoded) value of the header. Null means
1624: * that the header was not present (in this case it always return null).
1625: * @return the MIME charset name or null if no encoding applied
1626: */
1627: static private String determineMailHeaderEncodingCharset(
1628: String rawText) {
1629: if (rawText == null)
1630: return null;
1631: int iEncodingPrefix = rawText.indexOf("=?");
1632: if (iEncodingPrefix == -1)
1633: return null;
1634: int iCharsetBegin = iEncodingPrefix + 2;
1635: int iSecondQuestionMark = rawText.indexOf('?', iCharsetBegin);
1636: if (iSecondQuestionMark == -1)
1637: return null;
1638: // safety checks
1639: if (iSecondQuestionMark == iCharsetBegin)
1640: return null; // empty charset? impossible
1641: int iThirdQuestionMark = rawText.indexOf('?',
1642: iSecondQuestionMark + 1);
1643: if (iThirdQuestionMark == -1)
1644: return null; // there must be one after encoding
1645: if (-1 == rawText.indexOf("?=", iThirdQuestionMark + 1))
1646: return null; // closing tag
1647: String mimeCharset = rawText.substring(iCharsetBegin,
1648: iSecondQuestionMark);
1649: return mimeCharset;
1650: }
1651:
1652: /**
1653: * Returns a new Collection built over <I>list</I> replacing special addresses
1654: * with real <CODE>MailAddress</CODE>-es.<BR>
1655: * Manages <CODE>SpecialAddress.SENDER</CODE>, <CODE>SpecialAddress.REVERSE_PATH</CODE>,
1656: * <CODE>SpecialAddress.FROM</CODE>, <CODE>SpecialAddress.REPLY_TO</CODE>,
1657: * <CODE>SpecialAddress.RECIPIENTS</CODE>, <CODE>SpecialAddress.TO</CODE>,
1658: * <CODE>SpecialAddress.NULL</CODE> and <CODE>SpecialAddress.UNALTERED</CODE>.<BR>
1659: * <CODE>SpecialAddress.FROM</CODE> is made equivalent to <CODE>SpecialAddress.SENDER</CODE>;
1660: * <CODE>SpecialAddress.TO</CODE> is made equivalent to <CODE>SpecialAddress.RECIPIENTS</CODE>.<BR>
1661: * <CODE>SpecialAddress.REPLY_TO</CODE> uses the ReplyTo header if available, otherwise the
1662: * From header if available, otherwise the Sender header if available, otherwise the return-path.<BR>
1663: * <CODE>SpecialAddress.NULL</CODE> and <CODE>SpecialAddress.UNALTERED</CODE> are ignored.<BR>
1664: * Any other address is not replaced.
1665: */
1666: protected Collection replaceMailAddresses(Mail mail, Collection list) {
1667: Collection newList = new HashSet(list.size());
1668: Iterator iterator = list.iterator();
1669: while (iterator.hasNext()) {
1670: MailAddress mailAddress = (MailAddress) iterator.next();
1671: if (!mailAddress.getHost().equalsIgnoreCase(
1672: "address.marker")) {
1673: newList.add(mailAddress);
1674: } else if (mailAddress == SpecialAddress.SENDER
1675: || mailAddress == SpecialAddress.FROM) {
1676: MailAddress sender = mail.getSender();
1677: if (sender != null) {
1678: newList.add(sender);
1679: }
1680: } else if (mailAddress == SpecialAddress.REPLY_TO) {
1681: int parsedAddressCount = 0;
1682: try {
1683: InternetAddress[] replyToArray = (InternetAddress[]) mail
1684: .getMessage().getReplyTo();
1685: if (replyToArray != null) {
1686: for (int i = 0; i < replyToArray.length; i++) {
1687: try {
1688: newList.add(new MailAddress(
1689: replyToArray[i]));
1690: parsedAddressCount++;
1691: } catch (ParseException pe) {
1692: log("Unable to parse a \"REPLY_TO\" header address in the original message: "
1693: + replyToArray[i]
1694: + "; ignoring.");
1695: }
1696: }
1697: }
1698: } catch (MessagingException ae) {
1699: log("Unable to parse the \"REPLY_TO\" header in the original message; ignoring.");
1700: }
1701: // no address was parsed?
1702: if (parsedAddressCount == 0) {
1703: MailAddress sender = mail.getSender();
1704: if (sender != null) {
1705: newList.add(sender);
1706: }
1707: }
1708: } else if (mailAddress == SpecialAddress.REVERSE_PATH) {
1709: MailAddress reversePath = mail.getSender();
1710: if (reversePath != null) {
1711: newList.add(reversePath);
1712: }
1713: } else if (mailAddress == SpecialAddress.RECIPIENTS
1714: || mailAddress == SpecialAddress.TO) {
1715: newList.addAll(mail.getRecipients());
1716: } else if (mailAddress == SpecialAddress.UNALTERED) {
1717: continue;
1718: } else if (mailAddress == SpecialAddress.NULL) {
1719: continue;
1720: } else {
1721: newList.add(mailAddress);
1722: }
1723: }
1724: return newList;
1725: }
1726:
1727: /**
1728: * Returns a new Collection built over <I>list</I> replacing special addresses
1729: * with real <CODE>InternetAddress</CODE>-es.<BR>
1730: * Manages <CODE>SpecialAddress.SENDER</CODE>, <CODE>SpecialAddress.REVERSE_PATH</CODE>,
1731: * <CODE>SpecialAddress.FROM</CODE>, <CODE>SpecialAddress.REPLY_TO</CODE>,
1732: * <CODE>SpecialAddress.RECIPIENTS</CODE>, <CODE>SpecialAddress.TO</CODE>,
1733: * <CODE>SpecialAddress.NULL</CODE> and <CODE>SpecialAddress.UNALTERED</CODE>.<BR>
1734: * <CODE>SpecialAddress.RECIPIENTS</CODE> is made equivalent to <CODE>SpecialAddress.TO</CODE>.<BR>
1735: * <CODE>SpecialAddress.FROM</CODE> uses the From header if available, otherwise the Sender header if available,
1736: * otherwise the return-path.<BR>
1737: * <CODE>SpecialAddress.REPLY_TO</CODE> uses the ReplyTo header if available, otherwise the
1738: * From header if available, otherwise the Sender header if available, otherwise the return-path.<BR>
1739: * <CODE>SpecialAddress.UNALTERED</CODE> is ignored.<BR>
1740: * Any other address is not replaced.<BR>
1741: */
1742: protected Collection replaceInternetAddresses(Mail mail,
1743: Collection list) throws MessagingException {
1744: Collection newList = new HashSet(list.size());
1745: Iterator iterator = list.iterator();
1746: while (iterator.hasNext()) {
1747: InternetAddress internetAddress = (InternetAddress) iterator
1748: .next();
1749: MailAddress mailAddress = new MailAddress(internetAddress);
1750: if (!mailAddress.getHost().equalsIgnoreCase(
1751: "address.marker")) {
1752: newList.add(internetAddress);
1753: } else if (internetAddress.equals(SpecialAddress.SENDER
1754: .toInternetAddress())) {
1755: MailAddress sender = mail.getSender();
1756: if (sender != null) {
1757: newList.add(sender.toInternetAddress());
1758: }
1759: } else if (internetAddress
1760: .equals(SpecialAddress.REVERSE_PATH
1761: .toInternetAddress())) {
1762: MailAddress reversePath = mail.getSender();
1763: if (reversePath != null) {
1764: newList.add(reversePath.toInternetAddress());
1765: }
1766: } else if (internetAddress.equals(SpecialAddress.FROM
1767: .toInternetAddress())) {
1768: try {
1769: InternetAddress[] fromArray = (InternetAddress[]) mail
1770: .getMessage().getFrom();
1771: if (fromArray != null) {
1772: for (int i = 0; i < fromArray.length; i++) {
1773: newList.add(fromArray[i]);
1774: }
1775: } else {
1776: MailAddress reversePath = mail.getSender();
1777: if (reversePath != null) {
1778: newList
1779: .add(reversePath
1780: .toInternetAddress());
1781: }
1782: }
1783: } catch (MessagingException me) {
1784: log("Unable to parse the \"FROM\" header in the original message; ignoring.");
1785: }
1786: } else if (internetAddress.equals(SpecialAddress.REPLY_TO
1787: .toInternetAddress())) {
1788: try {
1789: InternetAddress[] replyToArray = (InternetAddress[]) mail
1790: .getMessage().getReplyTo();
1791: if (replyToArray != null) {
1792: for (int i = 0; i < replyToArray.length; i++) {
1793: newList.add(replyToArray[i]);
1794: }
1795: } else {
1796: MailAddress reversePath = mail.getSender();
1797: if (reversePath != null) {
1798: newList
1799: .add(reversePath
1800: .toInternetAddress());
1801: }
1802: }
1803: } catch (MessagingException me) {
1804: log("Unable to parse the \"REPLY_TO\" header in the original message; ignoring.");
1805: }
1806: } else if (internetAddress.equals(SpecialAddress.TO
1807: .toInternetAddress())
1808: || internetAddress.equals(SpecialAddress.RECIPIENTS
1809: .toInternetAddress())) {
1810: try {
1811: String[] toHeaders = mail.getMessage().getHeader(
1812: RFC2822Headers.TO);
1813: if (toHeaders != null) {
1814: for (int i = 0; i < toHeaders.length; i++) {
1815: try {
1816: InternetAddress[] originalToInternetAddresses = InternetAddress
1817: .parse(toHeaders[i], false);
1818: for (int j = 0; j < originalToInternetAddresses.length; j++) {
1819: newList
1820: .add(originalToInternetAddresses[j]);
1821: }
1822: } catch (MessagingException ae) {
1823: log("Unable to parse a \"TO\" header address in the original message: "
1824: + toHeaders[i] + "; ignoring.");
1825: }
1826: }
1827: }
1828: } catch (MessagingException ae) {
1829: log("Unable to parse the \"TO\" header in the original message; ignoring.");
1830: }
1831: } else if (internetAddress.equals(SpecialAddress.UNALTERED
1832: .toInternetAddress())) {
1833: continue;
1834: } else if (internetAddress.equals(SpecialAddress.NULL
1835: .toInternetAddress())) {
1836: continue;
1837: } else {
1838: newList.add(internetAddress);
1839: }
1840: }
1841: return newList;
1842: }
1843:
1844: }
|