View Javadoc

1   /************************************************************************
2    * Copyright (c) 2000-2006 The Apache Software Foundation.             *
3    * All rights reserved.                                                *
4    * ------------------------------------------------------------------- *
5    * Licensed under the Apache License, Version 2.0 (the "License"); you *
6    * may not use this file except in compliance with the License. You    *
7    * may obtain a copy of the License at:                                *
8    *                                                                     *
9    *     http://www.apache.org/licenses/LICENSE-2.0                      *
10   *                                                                     *
11   * Unless required by applicable law or agreed to in writing, software *
12   * distributed under the License is distributed on an "AS IS" BASIS,   *
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or     *
14   * implied.  See the License for the specific language governing       *
15   * permissions and limitations under the License.                      *
16   ***********************************************************************/
17  
18  package org.apache.james.transport.mailets;
19  
20  import java.io.PrintWriter;
21  import java.io.StringWriter;
22  
23  import java.util.Collection;
24  import java.util.Date;
25  import java.util.Enumeration;
26  import java.util.HashSet;
27  import java.util.Iterator;
28  import java.util.Locale;
29  import java.util.ArrayList;
30  
31  
32  import javax.mail.Message;
33  import javax.mail.MessagingException;
34  import javax.mail.internet.ParseException;
35  import javax.mail.Session;
36  import javax.mail.internet.InternetAddress;
37  import javax.mail.internet.MimeBodyPart;
38  import javax.mail.internet.MimeMessage;
39  import javax.mail.internet.MimeMultipart;
40  
41  import org.apache.mailet.RFC2822Headers;
42  import org.apache.mailet.dates.RFC822DateFormat;
43  import org.apache.james.core.MailImpl;
44  import org.apache.james.core.MimeMessageUtil;
45  
46  import org.apache.mailet.GenericMailet;
47  import org.apache.mailet.Mail;
48  import org.apache.mailet.MailAddress;
49  
50  
51  /***
52   * <P>Abstract mailet providing configurable redirection services.<BR>
53   * This mailet can be subclassed to make authoring redirection mailets simple.<BR>
54   * By extending it and overriding one or more of these methods new behaviour can
55   * be quickly created without the author having to address any other issue than
56   * the relevant one:</P>
57   * <UL>
58   * <LI>attachError() , should error messages be appended to the message</LI>
59   * <LI>getAttachmentType(), what should be attached to the message</LI>
60   * <LI>getInLineType(), what should be included in the message</LI>
61   * <LI>getMessage(), The text of the message itself</LI>
62   * <LI>getRecipients(), the recipients the mail is sent to</LI>
63   * <LI>getReplyTo(), where replies to this message will be sent</LI>
64   * <LI>getReversePath(), what to set the reverse-path to</LI>
65   * <LI>getSender(), who the mail is from</LI>
66   * <LI>getSubject(), a string to replace the message subject</LI>
67   * <LI>getSubjectPrefix(), a prefix to be added to the message subject, possibly already replaced by a new subject</LI>
68   * <LI>getTo(), a list of people to whom the mail is *apparently* sent</LI>
69   * <LI>isReply(), should this mailet set the IN_REPLY_TO header to the id of the current message</LI>
70   * <LI>getPassThrough(), should this mailet allow the original message to continue processing or GHOST it.</LI>
71   * <LI>getFakeDomainCheck(), should this mailet check if the sender domain address is valid.</LI>
72   * <LI>isStatic(), should this mailet run the get methods for every mail, or just once.</LI>
73   * </UL>
74   * <P>For each of the methods above (generically called "getX()" methods in this class
75   * and its subclasses), there is an associated "getX(Mail)" method and most times
76   * a "setX(Mail, Tx, Mail)" method.<BR>
77   * The roles are the following:</P>
78   * <UL>
79   * <LI>a "getX()" method returns the correspondent "X" value that can be evaluated "statically"
80   * once at init time and then stored in a variable and made available for later use by a
81   * "getX(Mail)" method;</LI>
82   * <LI>a "getX(Mail)" method is the one called to return the correspondent "X" value
83   * that can be evaluated "dynamically", tipically based on the currently serviced mail;
84   * the default behaviour is to return the value of getX();</LI>
85   * <LI>a "setX(Mail, Tx, Mail)" method is called to change the correspondent "X" value
86   * of the redirected Mail object, using the value returned by "gexX(Mail)";
87   * if such value is null, it does nothing.</LI>
88   * </UL>
89   * <P>Here follows the typical pattern of those methods:</P>
90   * <PRE><CODE>
91   *    ...
92   *    Tx x;
93   *    ...
94   *    protected boolean getX(Mail originalMail) throws MessagingException {
95   *        boolean x = (isStatic()) ? this.x : getX();
96   *        ...
97   *        return x;
98   *    }
99   *    ...
100  *    public void init() throws MessagingException {
101  *        ...
102  *        isStatic = (getInitParameter("static") == null) ? false : new Boolean(getInitParameter("static")).booleanValue();
103  *        if(isStatic()) {
104  *            ...
105  *            X  = getX();
106  *            ...
107  *        }
108  *    ...
109  *    public void service(Mail originalMail) throws MessagingException {
110  *    ...
111  *    setX(newMail, getX(originalMail), originalMail);
112  *    ...
113  *    }
114  *    ...
115  * </CODE></PRE>
116  * <P>The <I>isStatic</I> variable and method is used to allow for the situations
117  * (deprecated since version 2.2, but possibly used by previoulsy written extensions
118  * to {@link Redirect}) in which the getX() methods are non static: in this case
119  * {@link #isStatic()} must return false.<BR>
120  * Finally, a "getX()" method may return a "special address" (see {@link SpecialAddress}),
121  * that later will be resolved ("late bound") by a "getX(Mail)" or "setX(Mail, Tx, Mail)":
122  * it is a dynamic value that does not require <CODE>isStatic</CODE> to be false.</P>
123  *
124  * <P>Supports by default the <CODE>passThrough</CODE> init parameter (false if missing).
125  * Subclasses can override this behaviour overriding {@link #getPassThrough()}.</P>
126  *
127  * @version CVS $Revision: 428992 $ $Date: 2006-08-05 12:26:23 +0000 (sab, 05 ago 2006) $
128  * @since 2.2.0
129  */
130 
131 public abstract class AbstractRedirect extends GenericMailet {
132     
133     /***
134      * Gets the expected init parameters.
135      *
136      * @return null meaning no check
137      */
138     protected  String[] getAllowedInitParameters() {
139         return null;
140     }
141     
142     /***
143      * Controls certain log messages.
144      */
145     protected boolean isDebug = false;
146 
147     /***
148      * Holds the value of the <CODE>static</CODE> init parameter.
149      */
150     protected boolean isStatic = false;
151 
152     private static class AddressMarker {
153         public static MailAddress SENDER;
154         public static MailAddress REVERSE_PATH;
155         public static MailAddress FROM;
156         public static MailAddress REPLY_TO;
157         public static MailAddress TO;
158         public static MailAddress RECIPIENTS;
159         public static MailAddress DELETE;
160         public static MailAddress UNALTERED;
161         public static MailAddress NULL;
162 
163         static {
164             try {
165                 SENDER          = new MailAddress("sender","address.marker");
166                 REVERSE_PATH    = new MailAddress("reverse.path","address.marker");
167                 FROM            = new MailAddress("from","address.marker");
168                 REPLY_TO        = new MailAddress("reply.to","address.marker");
169                 TO              = new MailAddress("to","address.marker");
170                 RECIPIENTS      = new MailAddress("recipients","address.marker");
171                 DELETE          = new MailAddress("delete","address.marker");
172                 UNALTERED       = new MailAddress("unaltered","address.marker");
173                 NULL            = new MailAddress("null","address.marker");
174 
175             } catch (Exception _) {}
176         }
177     }
178 
179     /***
180      * Class containing "special addresses" constants.
181      * Such addresses mean dynamic values that later will be resolved ("late bound")
182      * by a "getX(Mail)" or "setX(Mail, Tx, Mail)".
183      */
184     protected static class SpecialAddress {
185         public static final MailAddress SENDER          = AddressMarker.SENDER;
186         public static final MailAddress REVERSE_PATH    = AddressMarker.REVERSE_PATH;
187         public static final MailAddress FROM            = AddressMarker.FROM;
188         public static final MailAddress REPLY_TO        = AddressMarker.REPLY_TO;
189         public static final MailAddress TO              = AddressMarker.TO;
190         public static final MailAddress RECIPIENTS      = AddressMarker.RECIPIENTS;
191         public static final MailAddress DELETE          = AddressMarker.DELETE;
192         public static final MailAddress UNALTERED       = AddressMarker.UNALTERED;
193         public static final MailAddress NULL            = AddressMarker.NULL;
194     }
195 
196     // The values that indicate how to attach the original mail
197     // to the new mail.
198 
199     protected static final int UNALTERED        = 0;
200 
201     protected static final int HEADS            = 1;
202 
203     protected static final int BODY             = 2;
204 
205     protected static final int ALL              = 3;
206 
207     protected static final int NONE             = 4;
208 
209     protected static final int MESSAGE          = 5;
210 
211     private boolean passThrough = false;
212     private boolean fakeDomainCheck = true;
213     private int attachmentType = NONE;
214     private int inLineType = BODY;
215     private String messageText;
216     private Collection recipients;
217     private MailAddress replyTo;
218     private MailAddress reversePath;
219     private MailAddress sender;
220     private String subject;
221     private String subjectPrefix;
222     private InternetAddress[] apparentlyTo;
223     private boolean attachError = false;
224     private boolean isReply = false;
225 
226     private RFC822DateFormat rfc822DateFormat = new RFC822DateFormat();
227 
228     /* ******************************************************************** */
229     /* ****************** Begin of getX and setX methods ****************** */
230     /* ******************************************************************** */
231 
232     /***
233      * <P>Gets the <CODE>static</CODE> property.</P>
234      * <P>Return true to reduce calls to getTo, getSender, getRecipients, getReplyTo, getReversePath amd getMessage
235      * where these values don't change (eg hard coded, or got at startup from the mailet config);
236      * return false where any of these methods generate their results dynamically eg in response to the message being processed,
237      * or by reference to a repository of users.</P>
238      * <P>It is now (from version 2.2) somehow obsolete, as should be always true because the "good practice"
239      * is to use "getX()" methods statically, and use instead "getX(Mail)" methods for dynamic situations.
240      * A false value is now meaningful only for subclasses of {@link Redirect} older than version 2.2
241      * that were relying on this.</P>
242      *
243      * <P>Is a "getX()" method.</P>
244      *
245      * @return true, as normally "getX()" methods shouls be static
246      */
247     protected boolean isStatic() {
248         return true;
249     }
250 
251     /***
252      * Gets the <CODE>passThrough</CODE> property.
253      * Return true to allow the original message to continue through the processor, false to GHOST it.
254      * Is a "getX()" method.
255      *
256      * @return the <CODE>passThrough</CODE> init parameter, or false if missing
257      */
258     protected boolean getPassThrough() throws MessagingException {
259         return new Boolean(getInitParameter("passThrough")).booleanValue();
260     }
261 
262     /***
263      * Gets the <CODE>passThrough</CODE> property,
264      * built dynamically using the original Mail object.
265      * Is a "getX(Mail)" method.
266      *
267      * @return {@link #getPassThrough()}
268      */
269     protected boolean getPassThrough(Mail originalMail) throws MessagingException {
270         return (isStatic()) ? this.passThrough : getPassThrough();
271     }
272 
273     /***
274      * Gets the <CODE>fakeDomainCheck</CODE> property.
275      * Return true to check if the sender domain is valid.
276      * Is a "getX()" method.
277      *
278      * @return the <CODE>fakeDomainCheck</CODE> init parameter, or true if missing
279      */
280     protected boolean getFakeDomainCheck() throws MessagingException {
281         return new Boolean(getInitParameter("fakeDomainCheck")).booleanValue();
282     }
283 
284     /***
285      * Gets the <CODE>fakeDomainCheck</CODE> property,
286      * built dynamically using the original Mail object.
287      * Is a "getX(Mail)" method.
288      *
289      * @return {@link #getFakeDomainCheck()}
290      */
291     protected boolean getFakeDomainCheck(Mail originalMail) throws MessagingException {
292         return (isStatic()) ? this.fakeDomainCheck : getFakeDomainCheck();
293     }
294 
295     /***
296      * Gets the <CODE>inline</CODE> property.
297      * May return one of the following values to indicate how to append the original message
298      * to build the new message:
299      * <ul>
300      *    <li><CODE>UNALTERED</CODE> : original message is the new message body</li>
301      *    <li><CODE>BODY</CODE> : original message body is appended to the new message</li>
302      *    <li><CODE>HEADS</CODE> : original message headers are appended to the new message</li>
303      *    <li><CODE>ALL</CODE> : original is appended with all headers</li>
304      *    <li><CODE>NONE</CODE> : original is not appended</li>
305      * </ul>
306      * Is a "getX()" method.
307      *
308      * @return the <CODE>inline</CODE> init parameter, or <CODE>UNALTERED</CODE> if missing
309      */
310     protected int getInLineType() throws MessagingException {
311         return getTypeCode(getInitParameter("inline","unaltered"));
312     }
313     
314     /***
315      * Gets the <CODE>inline</CODE> property,
316      * built dynamically using the original Mail object.
317      * Is a "getX(Mail)" method.
318      *
319      * @return {@link #getInLineType()}
320      */
321     protected int getInLineType(Mail originalMail) throws MessagingException {
322         return (isStatic()) ? this.inLineType : getInLineType();
323     }
324 
325     /*** Gets the <CODE>attachment</CODE> property.
326      * May return one of the following values to indicate how to attach the original message
327      * to the new message:
328      * <ul>
329      *    <li><CODE>BODY</CODE> : original message body is attached as plain text to the new message</li>
330      *    <li><CODE>HEADS</CODE> : original message headers are attached as plain text to the new message</li>
331      *    <li><CODE>ALL</CODE> : original is attached as plain text with all headers</li>
332      *    <li><CODE>MESSAGE</CODE> : original message is attached as type message/rfc822, a complete mail message.</li>
333      *    <li><CODE>NONE</CODE> : original is not attached</li>
334      * </ul>
335      * Is a "getX()" method.
336      *
337      * @return the <CODE>attachment</CODE> init parameter, or <CODE>NONE</CODE> if missing
338      */
339     protected int getAttachmentType() throws MessagingException {
340         return getTypeCode(getInitParameter("attachment","none"));
341     }
342 
343     /***
344      * Gets the <CODE>attachment</CODE> property,
345      * built dynamically using the original Mail object.
346      * Is a "getX(Mail)" method.
347      *
348      * @return {@link #getAttachmentType()}
349      */
350     protected int getAttachmentType(Mail originalMail) throws MessagingException {
351         return (isStatic()) ? this.attachmentType : getAttachmentType();
352     }
353 
354     /***
355      * Gets the <CODE>message</CODE> property.
356      * Returns a message to which the original message can be attached/appended
357      * to build the new message.
358      * Is a "getX()" method.
359      *
360      * @return the <CODE>message</CODE> init parameter or an empty string if missing
361      */
362     protected String getMessage() throws MessagingException {
363         return getInitParameter("message","");
364     }
365 
366     /***
367      * Gets the <CODE>message</CODE> property,
368      * built dynamically using the original Mail object.
369      * Is a "getX(Mail)" method.
370      *
371      * @return {@link #getMessage()}
372      */
373     protected String getMessage(Mail originalMail) throws MessagingException {
374         return (isStatic()) ? this.messageText : getMessage();
375     }
376 
377     /***
378      * Gets the <CODE>recipients</CODE> property.
379      * Returns the collection of recipients of the new message,
380      * or null if no change is requested.
381      * Is a "getX()" method.
382      *
383      * @return the <CODE>recipients</CODE> init parameter
384      * or the postmaster address
385      * or <CODE>SpecialAddress.SENDER</CODE>
386      * or <CODE>SpecialAddress.FROM</CODE>
387      * or <CODE>SpecialAddress.REPLY_TO</CODE>
388      * or <CODE>SpecialAddress.REVERSE_PATH</CODE>
389      * or <CODE>SpecialAddress.UNALTERED</CODE>
390      * or <CODE>SpecialAddress.RECIPIENTS</CODE>
391      * or <CODE>null</CODE> if missing
392      */
393     protected Collection getRecipients() throws MessagingException {
394         Collection newRecipients = new HashSet();
395         String addressList = getInitParameter("recipients");
396         
397         // if nothing was specified, return <CODE>null</CODE> meaning no change
398         if (addressList == null) {
399             return null;
400         }
401 
402         try {
403             InternetAddress[] iaarray = InternetAddress.parse(addressList, false);
404             for (int i = 0; i < iaarray.length; i++) {
405                 String addressString = iaarray[i].getAddress();
406                 MailAddress specialAddress = getSpecialAddress(addressString,
407                 new String[] {"postmaster", "sender", "from", "replyTo", "reversePath", "unaltered", "recipients", "to", "null"});
408                 if (specialAddress != null) {
409                     newRecipients.add(specialAddress);
410                 } else {
411                     newRecipients.add(new MailAddress(iaarray[i]));
412                 }
413             }
414         } catch (Exception e) {
415             throw new MessagingException("Exception thrown in getRecipients() parsing: " + addressList, e);
416         }
417         if (newRecipients.size() == 0) {
418             throw new MessagingException("Failed to initialize \"recipients\" list; empty <recipients> init parameter found.");
419         }
420 
421         return newRecipients;
422     }
423 
424     /***
425      * Gets the <CODE>recipients</CODE> property,
426      * built dynamically using the original Mail object.
427      * Is a "getX(Mail)" method.
428      *
429      * @return {@link #replaceMailAddresses} on {@link #getRecipients()},
430      */
431     protected Collection getRecipients(Mail originalMail) throws MessagingException {
432         Collection recipients = (isStatic()) ? this.recipients : getRecipients();
433         if (recipients != null) {
434             if (recipients.size() == 1 && (recipients.contains(SpecialAddress.UNALTERED) || recipients.contains(SpecialAddress.RECIPIENTS))) {
435                 recipients = null;
436             } else {
437                 recipients = replaceMailAddresses(originalMail, recipients);
438             }
439         }
440         return recipients;
441     }
442 
443     /***
444      * Sets the recipients of <I>newMail</I> to <I>recipients</I>.
445      * If the requested value is null does nothing.
446      * Is a "setX(Mail, Tx, Mail)" method.
447      */
448     protected void setRecipients(Mail newMail, Collection recipients, Mail originalMail) throws MessagingException {
449         if (recipients != null) {
450             newMail.setRecipients(recipients);
451             if (isDebug) {
452                 log("recipients set to: " + arrayToString(recipients.toArray()));
453             }
454         }
455     }
456 
457     /***
458      * Gets the <CODE>to</CODE> property.
459      * Returns the "To:" recipients of the new message.
460      * or null if no change is requested.
461      * Is a "getX()" method.
462      *
463      * @return the <CODE>to</CODE> init parameter
464      * or the postmaster address
465      * or <CODE>SpecialAddress.SENDER</CODE>
466      * or <CODE>SpecialAddress.REVERSE_PATH</CODE>
467      * or <CODE>SpecialAddress.FROM</CODE>
468      * or <CODE>SpecialAddress.REPLY_TO</CODE>
469      * or <CODE>SpecialAddress.UNALTERED</CODE>
470      * or <CODE>SpecialAddress.TO</CODE>
471      * or <CODE>null</CODE> if missing
472      */
473     protected InternetAddress[] getTo() throws MessagingException {
474         InternetAddress[] iaarray = null;
475         String addressList = getInitParameter("to");
476         
477         // if nothing was specified, return null meaning no change
478         if (addressList == null) {
479             return null;
480         }
481 
482         try {
483             iaarray = InternetAddress.parse(addressList, false);
484             for(int i = 0; i < iaarray.length; ++i) {
485                 String addressString = iaarray[i].getAddress();
486                 MailAddress specialAddress = getSpecialAddress(addressString,
487                                                 new String[] {"postmaster", "sender", "from", "replyTo", "reversePath", "unaltered", "recipients", "to", "null"});
488                 if (specialAddress != null) {
489                     iaarray[i] = specialAddress.toInternetAddress();
490                 }
491             }
492         } catch (Exception e) {
493             throw new MessagingException("Exception thrown in getTo() parsing: " + addressList, e);
494         }
495         if (iaarray.length == 0) {
496             throw new MessagingException("Failed to initialize \"to\" list; empty <to> init parameter found.");
497         }
498 
499         return iaarray;
500     }
501 
502     /***
503      * Gets the <CODE>to</CODE> property,
504      * built dynamically using the original Mail object.
505      * Its outcome will be the the value the <I>TO:</I> header will be set to,
506      * that could be different from the real recipient (see {@link #getRecipients}).
507      * Is a "getX(Mail)" method.
508      *
509      * @return {@link #replaceInternetAddresses} on {@link #getRecipients()},
510      */
511     protected InternetAddress[] getTo(Mail originalMail) throws MessagingException {
512         InternetAddress[] apparentlyTo = (isStatic()) ? this.apparentlyTo : getTo();
513         if (apparentlyTo != null) {
514             if (   apparentlyTo.length == 1
515                 && (   apparentlyTo[0].equals(SpecialAddress.UNALTERED.toInternetAddress())
516                     || apparentlyTo[0].equals(SpecialAddress.TO.toInternetAddress())
517                     )) {
518                 apparentlyTo = null;
519             } else {
520                 Collection toList = new ArrayList(apparentlyTo.length);
521                 for (int i = 0; i < apparentlyTo.length; i++) {
522                     toList.add(apparentlyTo[i]);
523                 }
524                 /* IMPORTANT: setTo() treats null differently from a zero length array,
525                   so it's ok to get a zero length array from replaceSpecialAddresses
526                  */
527                 apparentlyTo = (InternetAddress[]) replaceInternetAddresses(originalMail, toList).toArray(new InternetAddress[0]);
528             }
529         }
530         
531         return apparentlyTo;
532     }
533 
534     /***
535      * Sets the "To:" header of <I>newMail</I> to <I>to</I>.
536      * If the requested value is null does nothing.
537      * Is a "setX(Mail, Tx, Mail)" method.
538      */
539     protected void setTo(Mail newMail, InternetAddress[] to, Mail originalMail) throws MessagingException {
540         if (to != null) {
541             newMail.getMessage().setRecipients(Message.RecipientType.TO, to);
542             if (isDebug) {
543                 log("apparentlyTo set to: " + arrayToString(to));
544             }
545         }
546     }
547 
548     /***
549      * Gets the <CODE>replyto</CODE> property.
550      * Returns the Reply-To address of the new message,
551      * or null if no change is requested.
552      * Is a "getX()" method.
553      *
554      * @return the <CODE>replyto</CODE> init parameter
555      * or the postmaster address
556      * or <CODE>SpecialAddress.SENDER</CODE>
557      * or <CODE>SpecialAddress.UNALTERED</CODE>
558      * or <CODE>SpecialAddress.NULL</CODE>
559      * or <CODE>null</CODE> if missing
560      */
561     protected MailAddress getReplyTo() throws MessagingException {
562         String addressString = getInitParameter("replyTo",getInitParameter("replyto"));
563 
564         if(addressString != null) {
565             MailAddress specialAddress = getSpecialAddress(addressString,
566                                             new String[] {"postmaster", "sender", "null", "unaltered"});
567             if (specialAddress != null) {
568                 return specialAddress;
569             }
570 
571             try {
572                 return new MailAddress(addressString);
573             } catch(Exception e) {
574                 throw new MessagingException("Exception thrown in getReplyTo() parsing: " + addressString, e);
575             }
576         }
577 
578         return null;
579     }
580 
581     /***
582      * Gets the <CODE>replyTo</CODE> property,
583      * built dynamically using the original Mail object.
584      * Is a "getX(Mail)" method.
585      *
586      * @return {@link #getReplyTo()}
587      * replacing <CODE>SpecialAddress.UNALTERED</CODE> if applicable with null
588      * and <CODE>SpecialAddress.SENDER</CODE> with the original mail sender
589      */
590     protected MailAddress getReplyTo(Mail originalMail) throws MessagingException {
591         MailAddress replyTo = (isStatic()) ? this.replyTo : getReplyTo();
592         if (replyTo != null) {
593             if (replyTo == SpecialAddress.UNALTERED) {
594                 replyTo = null;
595             } else if (replyTo == SpecialAddress.SENDER) {
596                 replyTo = originalMail.getSender();
597             }
598         }
599         return replyTo;
600     }
601 
602     /***
603      * <P>Sets the "Reply-To:" header of <I>newMail</I> to <I>replyTo</I>.</P>
604      * If the requested value is <CODE>SpecialAddress.NULL</CODE> will remove the "Reply-To:" header.
605      * If the requested value is null does nothing.</P>
606      * Is a "setX(Mail, Tx, Mail)" method.
607      */
608     protected void setReplyTo(Mail newMail, MailAddress replyTo, Mail originalMail) throws MessagingException {
609         if(replyTo != null) {
610             InternetAddress[] iart = null;
611             if (replyTo != SpecialAddress.NULL) {
612                 iart = new InternetAddress[1];
613                 iart[0] = replyTo.toInternetAddress();
614             }
615             
616             // Note: if iart is null will remove the header
617             newMail.getMessage().setReplyTo(iart);
618             
619             if (isDebug) {
620                 log("replyTo set to: " + replyTo);
621             }
622         }
623     }
624 
625     /***
626      * Gets the <CODE>reversePath</CODE> property.
627      * Returns the reverse-path of the new message,
628      * or null if no change is requested.
629      * Is a "getX()" method.
630      *
631      * @return the <CODE>reversePath</CODE> init parameter 
632      * or the postmaster address
633      * or <CODE>SpecialAddress.SENDER</CODE>
634      * or <CODE>SpecialAddress.NULL</CODE>
635      * or <CODE>SpecialAddress.UNALTERED</CODE>
636      * or <CODE>null</CODE> if missing
637      */
638     protected MailAddress getReversePath() throws MessagingException {
639         String addressString = getInitParameter("reversePath");
640         if(addressString != null) {
641             MailAddress specialAddress = getSpecialAddress(addressString,
642                                             new String[] {"postmaster", "sender", "null", "unaltered"});
643             if (specialAddress != null) {
644                 return specialAddress;
645             }
646 
647             try {
648                 return new MailAddress(addressString);
649             } catch(Exception e) {
650                 throw new MessagingException("Exception thrown in getReversePath() parsing: " + addressString, e);
651             }
652         }
653 
654         return null;
655     }
656 
657     /***
658      * Gets the <CODE>reversePath</CODE> property,
659      * built dynamically using the original Mail object.
660      * Is a "getX(Mail)" method.
661      *
662      * @return {@link #getReversePath()},
663      * replacing <CODE>SpecialAddress.SENDER</CODE> if applicable with null,
664      * replacing <CODE>SpecialAddress.UNALTERED</CODE>
665      * and <CODE>SpecialAddress.REVERSE_PATH</CODE> if applicable with null,
666      * but not replacing <CODE>SpecialAddress.NULL</CODE>
667      * that will be handled by {@link #setReversePath}
668      */
669     protected MailAddress getReversePath(Mail originalMail) throws MessagingException {
670         MailAddress reversePath = (isStatic()) ? this.reversePath : getReversePath();
671         if (reversePath != null) {
672             if (reversePath == SpecialAddress.UNALTERED || reversePath == SpecialAddress.REVERSE_PATH) {
673                 reversePath = null;
674             }
675             else if (reversePath == SpecialAddress.SENDER) {
676                 reversePath = null;
677             }
678         }
679         return reversePath;
680     }
681 
682     /***
683      * Sets the "reverse-path" of <I>newMail</I> to <I>reversePath</I>.
684      * If the requested value is <CODE>SpecialAddress.NULL</CODE> sets it to "<>".
685      * If the requested value is null does nothing.
686      * Is a "setX(Mail, Tx, Mail)" method.
687      */
688     protected void setReversePath(MailImpl newMail, MailAddress reversePath, Mail originalMail) throws MessagingException {
689         if(reversePath != null) {
690             if (reversePath == SpecialAddress.NULL) {
691                 reversePath = null;
692             }
693             newMail.setSender(reversePath);
694             if (isDebug) {
695                 log("reversePath set to: " + reversePath);
696             }
697         }
698     }
699 
700     /***
701      * Gets the <CODE>sender</CODE> property.
702      * Returns the new sender as a MailAddress,
703      * or null if no change is requested.
704      * Is a "getX()" method.
705      *
706      * @return the <CODE>sender</CODE> init parameter
707      * or the postmaster address
708      * or <CODE>SpecialAddress.SENDER</CODE>
709      * or <CODE>SpecialAddress.UNALTERED</CODE>
710      * or <CODE>null</CODE> if missing
711      */
712     protected MailAddress getSender() throws MessagingException {
713         String addressString = getInitParameter("sender");
714         if(addressString != null) {
715             MailAddress specialAddress = getSpecialAddress(addressString,
716                                             new String[] {"postmaster", "sender", "unaltered"});
717             if (specialAddress != null) {
718                 return specialAddress;
719             }
720 
721             try {
722                 return new MailAddress(addressString);
723             } catch(Exception e) {
724                 throw new MessagingException("Exception thrown in getSender() parsing: " + addressString, e);
725             }
726         }
727 
728         return null;
729     }
730 
731     /***
732      * Gets the <CODE>sender</CODE> property,
733      * built dynamically using the original Mail object.
734      * Is a "getX(Mail)" method.
735      *
736      * @return {@link #getSender()}
737      * replacing <CODE>SpecialAddress.UNALTERED</CODE>
738      * and <CODE>SpecialAddress.SENDER</CODE> if applicable with null
739      */
740     protected MailAddress getSender(Mail originalMail) throws MessagingException {
741         MailAddress sender = (isStatic()) ? this.sender : getSender();
742         if (sender != null) {
743             if (sender == SpecialAddress.UNALTERED || sender == SpecialAddress.SENDER) {
744                 sender = null;
745             }
746         }
747         return sender;
748     }
749 
750     /***
751      * Sets the "From:" header of <I>newMail</I> to <I>sender</I>.
752      * If the requested value is null does nothing.
753      * Is a "setX(Mail, Tx, Mail)" method.
754      */
755     protected void setSender(Mail newMail, MailAddress sender, Mail originalMail) throws MessagingException {
756         if (sender != null) {
757             newMail.getMessage().setFrom(sender.toInternetAddress());
758             
759             if (isDebug) {
760                 log("sender set to: " + sender);
761             }
762         }
763     }
764     
765     /***
766      * Gets the <CODE>subject</CODE> property.
767      * Returns a string for the new message subject.
768      * Is a "getX()" method.
769      *
770      * @return the <CODE>subject</CODE> init parameter or null if missing
771      */
772     protected String getSubject() throws MessagingException {
773         return getInitParameter("subject");
774     }
775 
776     /***
777      * Gets the <CODE>subject</CODE> property,
778      * built dynamically using the original Mail object.
779      * Is a "getX(Mail)" method.
780      *
781      * @return {@link #getSubject()}
782      */
783     protected String getSubject(Mail originalMail) throws MessagingException {
784         return (isStatic()) ? this.subject : getSubject();
785     }
786 
787     /***
788      * Gets the <CODE>prefix</CODE> property.
789      * Returns a prefix for the new message subject.
790      * Is a "getX()" method.
791      *
792      * @return the <CODE>prefix</CODE> init parameter or an empty string if missing
793      */
794     protected String getSubjectPrefix() throws MessagingException {
795         return getInitParameter("prefix");
796     }
797 
798     /***
799      * Gets the <CODE>subjectPrefix</CODE> property,
800      * built dynamically using the original Mail object.
801      * Is a "getX(Mail)" method.
802      *
803      * @return {@link #getSubjectPrefix()}
804      */
805     protected String getSubjectPrefix(Mail originalMail) throws MessagingException {
806         return (isStatic()) ? this.subjectPrefix : getSubjectPrefix();
807     }
808 
809     /***
810      * Builds the subject of <I>newMail</I> appending the subject
811      * of <I>originalMail</I> to <I>subjectPrefix</I>.
812      * Is a "setX(Mail, Tx, Mail)" method.
813      */
814     protected void setSubjectPrefix(Mail newMail, String subjectPrefix, Mail originalMail) throws MessagingException {
815         String subject = getSubject(originalMail);
816         if ((subjectPrefix != null && subjectPrefix.length() > 0) || subject != null) {
817             if (subject == null) {
818                 subject = originalMail.getMessage().getSubject();
819             } else {
820                 // replacing the subject
821                 if (isDebug) {
822                     log("subject set to: " + subject);
823                 }
824             }
825             // Was null in original?
826             if (subject == null) {
827                 subject = "";
828             }
829             
830             if (subjectPrefix != null) {
831                 subject = subjectPrefix + subject;
832                 // adding a prefix
833                 if (isDebug) {
834                     log("subjectPrefix set to: " + subjectPrefix);
835                 }
836             }
837 //            newMail.getMessage().setSubject(subject);
838             changeSubject(newMail.getMessage(), subject);
839         }
840     }
841 
842     /***
843      * Gets the <CODE>attachError</CODE> property.
844      * Returns a boolean indicating whether to append a description of any error to the main body part
845      * of the new message, if getInlineType does not return "UNALTERED".
846      * Is a "getX()" method.
847      *
848      * @return the <CODE>attachError</CODE> init parameter; false if missing
849      */
850     protected boolean attachError() throws MessagingException {
851         return new Boolean(getInitParameter("attachError")).booleanValue();
852     }
853 
854     /***
855      * Gets the <CODE>attachError</CODE> property,
856      * built dynamically using the original Mail object.
857      * Is a "getX(Mail)" method.
858      *
859      * @return {@link #attachError()}
860      */
861     protected boolean attachError(Mail originalMail) throws MessagingException {
862         return (isStatic()) ? this.attachError : attachError();
863     }
864 
865     /***
866      * Gets the <CODE>isReply</CODE> property.
867      * Returns a boolean indicating whether the new message must be considered
868      * a reply to the original message, setting the IN_REPLY_TO header of the new
869      * message to the id of the original message.
870      * Is a "getX()" method.
871      *
872      * @return the <CODE>isReply</CODE> init parameter; false if missing
873      */
874     protected boolean isReply() throws MessagingException {
875         return new Boolean(getInitParameter("isReply")).booleanValue();
876     }
877 
878     /***
879      * Gets the <CODE>isReply</CODE> property,
880      * built dynamically using the original Mail object.
881      * Is a "getX(Mail)" method.
882      *
883      * @return {@link #isReply()}
884      */
885     protected boolean isReply(Mail originalMail) throws MessagingException {
886         return (isStatic()) ? this.isReply : isReply();
887     }
888 
889     /***
890      * Sets the "In-Reply-To:" header of <I>newMail</I> to the "Message-Id:" of
891      * <I>originalMail</I>, if <I>isReply</I> is true.
892      */
893     protected void setIsReply(Mail newMail, boolean isReply, Mail originalMail) throws MessagingException {
894         if (isReply) {
895             String messageId = originalMail.getMessage().getMessageID();
896             if (messageId != null) {
897                 newMail.getMessage().setHeader(RFC2822Headers.IN_REPLY_TO, messageId);
898                 if (isDebug) {
899                     log("IN_REPLY_TO set to: " + messageId);
900                 }
901             }
902         }
903     }
904 
905     /* ******************************************************************** */
906     /* ******************* End of getX and setX methods ******************* */
907     /* ******************************************************************** */
908 
909     /***
910      * Mailet initialization routine.
911      * Will setup static values for each "x" initialization parameter in config.xml,
912      * using getX(), if {@link #isStatic()} returns true.
913      */
914     public void init() throws MessagingException {
915         isDebug = new Boolean(getInitParameter("debug","false")).booleanValue();
916 
917         isStatic = new Boolean(getInitParameter("static","false")).booleanValue();
918 
919         if (isDebug) {
920             log("Initializing");
921         }
922         
923         // check that all init parameters have been declared in allowedInitParameters
924         checkInitParameters(getAllowedInitParameters());
925         
926         if(isStatic()) {
927             passThrough         = getPassThrough();
928             fakeDomainCheck     = getFakeDomainCheck();
929             attachmentType      = getAttachmentType();
930             inLineType          = getInLineType();
931             messageText         = getMessage();
932             recipients          = getRecipients();
933             replyTo             = getReplyTo();
934             reversePath         = getReversePath();
935             sender              = getSender();
936             subject             = getSubject();
937             subjectPrefix       = getSubjectPrefix();
938             apparentlyTo        = getTo();
939             attachError         = attachError();
940             isReply             = isReply();
941             if (isDebug) {
942                 StringBuffer logBuffer =
943                     new StringBuffer(1024)
944                             .append("static")
945                             .append(", passThrough=").append(passThrough)
946                             .append(", fakeDomainCheck=").append(fakeDomainCheck)
947                             .append(", sender=").append(sender)
948                             .append(", replyTo=").append(replyTo)
949                             .append(", reversePath=").append(reversePath)
950                             .append(", message=").append(messageText)
951                             .append(", recipients=").append(arrayToString(recipients == null ? null : recipients.toArray()))
952                             .append(", subject=").append(subject)
953                             .append(", subjectPrefix=").append(subjectPrefix)
954                             .append(", apparentlyTo=").append(arrayToString(apparentlyTo))
955                             .append(", attachError=").append(attachError)
956                             .append(", isReply=").append(isReply)
957                             .append(", attachmentType=").append(attachmentType)
958                             .append(", inLineType=").append(inLineType)
959                             .append(" ");
960                 log(logBuffer.toString());
961             }
962         }
963     }
964 
965     /***
966      * Service does the hard work,and redirects the originalMail in the form specified.
967      *
968      * @param originalMail the mail to process and redirect
969      * @throws MessagingException if a problem arises formulating the redirected mail
970      */
971     public void service(Mail originalMail) throws MessagingException {
972 
973         boolean keepMessageId = false;
974 
975         // duplicates the Mail object, to be able to modify the new mail keeping the original untouched
976         MailImpl newMail = new MailImpl(originalMail,newName(originalMail));
977         try {
978             // We don't need to use the original Remote Address and Host,
979             // and doing so would likely cause a loop with spam detecting
980             // matchers.
981             try {
982                 newMail.setRemoteAddr(java.net.InetAddress.getLocalHost().getHostAddress());
983                 newMail.setRemoteHost(java.net.InetAddress.getLocalHost().getHostName());
984             } catch (java.net.UnknownHostException _) {
985                 newMail.setRemoteAddr("127.0.0.1");
986                 newMail.setRemoteHost("localhost");
987             }
988     
989             if (isDebug) {
990                 log("New mail - sender: " + newMail.getSender()
991                            + ", recipients: " + arrayToString(newMail.getRecipients().toArray())
992                            + ", name: " + newMail.getName()
993                            + ", remoteHost: " + newMail.getRemoteHost()
994                            + ", remoteAddr: " + newMail.getRemoteAddr()
995                            + ", state: " + newMail.getState()
996                            + ", lastUpdated: " + newMail.getLastUpdated()
997                            + ", errorMessage: " + newMail.getErrorMessage());
998             }
999     
1000             //Create the message
1001             if(getInLineType(originalMail) != UNALTERED) {
1002                 if (isDebug) {
1003                     log("Alter message");
1004                 }
1005                 newMail.setMessage(new MimeMessage(Session.getDefaultInstance(System.getProperties(),
1006                                                                    null)));
1007     
1008                 // handle the new message if altered
1009                 buildAlteredMessage(newMail, originalMail);
1010     
1011             } else {
1012                 // if we need the original, create a copy of this message to redirect
1013                 if (getPassThrough(originalMail)) {
1014                     newMail.setMessage(new MimeMessage(originalMail.getMessage()) {
1015                         protected void updateHeaders() throws MessagingException {
1016                             if (getMessageID() == null) super.updateHeaders();
1017                             else {
1018                                 modified = false;
1019                             }
1020                         }
1021                     });
1022                 }
1023                 if (isDebug) {
1024                     log("Message resent unaltered.");
1025                 }
1026                 keepMessageId = true;
1027             }
1028     
1029             //Set additional headers
1030     
1031             setRecipients(newMail, getRecipients(originalMail), originalMail);
1032     
1033             setTo(newMail, getTo(originalMail), originalMail);
1034     
1035             setSubjectPrefix(newMail, getSubjectPrefix(originalMail), originalMail);
1036     
1037             if(newMail.getMessage().getHeader(RFC2822Headers.DATE) == null) {
1038                 newMail.getMessage().setHeader(RFC2822Headers.DATE, rfc822DateFormat.format(new Date()));
1039             }
1040     
1041             setReplyTo(newMail, getReplyTo(originalMail), originalMail);
1042     
1043             setReversePath(newMail, getReversePath(originalMail), originalMail);
1044     
1045             setSender(newMail, getSender(originalMail), originalMail);
1046     
1047             setIsReply(newMail, isReply(originalMail), originalMail);
1048     
1049             newMail.getMessage().saveChanges();
1050     
1051             if (keepMessageId) {
1052                 setMessageId(newMail, originalMail);
1053             }
1054     
1055             if (senderDomainIsValid(newMail)) {
1056                 //Send it off...
1057                 getMailetContext().sendMail(newMail);
1058             } else {
1059                 StringBuffer logBuffer = new StringBuffer(256)
1060                                         .append(getMailetName())
1061                                         .append(" mailet cannot forward ")
1062                                         .append(originalMail.getName())
1063                                         .append(". Invalid sender domain for ")
1064                                         .append(newMail.getSender())
1065                                         .append(". Consider using the Resend mailet ")
1066                                         .append("using a different sender.");
1067                 throw new MessagingException(logBuffer.toString());
1068             }
1069     
1070         } finally {
1071             newMail.dispose();
1072         }
1073         
1074         if(!getPassThrough(originalMail)) {
1075             originalMail.setState(Mail.GHOST);
1076         }
1077     }
1078 
1079     private static final java.util.Random random = new java.util.Random();  // Used to generate new mail names
1080 
1081     /***
1082      * Create a unique new primary key name.
1083      *
1084      * @param mail the mail to use as the basis for the new mail name
1085      * @return a new name
1086      */
1087     private String newName(Mail mail) throws MessagingException {
1088         String oldName = mail.getName();
1089         
1090         // Checking if the original mail name is too long, perhaps because of a
1091         // loop caused by a configuration error.
1092         // it could cause a "null pointer exception" in AvalonMailRepository much
1093         // harder to understand.
1094         if (oldName.length() > 76) {
1095             int count = 0;
1096             int index = 0;
1097             while ((index = oldName.indexOf('!', index + 1)) >= 0) {
1098                 count++;
1099             }
1100             // It looks like a configuration loop. It's better to stop.
1101             if (count > 7) {
1102                 throw new MessagingException("Unable to create a new message name: too long."
1103                                              + " Possible loop in config.xml.");
1104             }
1105             else {
1106                 oldName = oldName.substring(0, 76);
1107             }
1108         }
1109         
1110         StringBuffer nameBuffer =
1111                                  new StringBuffer(64)
1112                                  .append(oldName)
1113                                  .append("-!")
1114                                  .append(random.nextInt(1048576));
1115         return nameBuffer.toString();
1116     }
1117 
1118     /***
1119      * A private method to convert types from string to int.
1120      *
1121      * @param param the string type
1122      * @return the corresponding int enumeration
1123      */
1124     protected int getTypeCode(String param) {
1125         param = param.toLowerCase(Locale.US);
1126         if(param.compareTo("unaltered") == 0) {
1127             return UNALTERED;
1128         }
1129         if(param.compareTo("heads") == 0) {
1130             return HEADS;
1131         }
1132         if(param.compareTo("body") == 0) {
1133             return BODY;
1134         }
1135         if(param.compareTo("all") == 0) {
1136             return ALL;
1137         }
1138         if(param.compareTo("none") == 0) {
1139             return NONE;
1140         }
1141         if(param.compareTo("message") == 0) {
1142             return MESSAGE;
1143         }
1144         return NONE;
1145     }
1146 
1147     /***
1148      * Utility method for obtaining a string representation of an array of Objects.
1149      */
1150     private String arrayToString(Object[] array) {
1151         if (array == null) {
1152             return "null";
1153         }
1154         StringBuffer sb = new StringBuffer(1024);
1155         sb.append("[");
1156         for (int i = 0; i < array.length; i++) {
1157             if (i > 0) {
1158                 sb.append(",");
1159             }
1160             sb.append(array[i]);
1161         }
1162         sb.append("]");
1163         return sb.toString();
1164     }
1165 
1166     /***
1167      * Utility method for obtaining a string representation of a
1168      * Message's headers
1169      */
1170     protected String getMessageHeaders(MimeMessage message) throws MessagingException {
1171         Enumeration heads = message.getAllHeaderLines();
1172         StringBuffer headBuffer = new StringBuffer(1024);
1173         while(heads.hasMoreElements()) {
1174             headBuffer.append(heads.nextElement().toString()).append("\r\n");
1175         }
1176         return headBuffer.toString();
1177     }
1178 
1179     /***
1180      * Utility method for obtaining a string representation of a
1181      * Message's body
1182      */
1183     private String getMessageBody(MimeMessage message) throws Exception {
1184         java.io.ByteArrayOutputStream bodyOs = new java.io.ByteArrayOutputStream();
1185         MimeMessageUtil.writeMessageBodyTo(message,bodyOs);
1186         return bodyOs.toString();
1187     }
1188 
1189     /***
1190      * Builds the message of the newMail in case it has to be altered.
1191      *
1192      * @param originalMail the original Mail object
1193      * @param newMail the Mail object to build
1194      */
1195     protected void buildAlteredMessage(Mail newMail, Mail originalMail) throws MessagingException {
1196 
1197         MimeMessage originalMessage = originalMail.getMessage();
1198         MimeMessage newMessage = newMail.getMessage();
1199 
1200         // Copy the relevant headers
1201         String[] relevantHeaderNames =
1202             {RFC2822Headers.DATE,
1203              RFC2822Headers.FROM,
1204              RFC2822Headers.REPLY_TO,
1205              RFC2822Headers.TO,
1206              RFC2822Headers.SUBJECT,
1207              RFC2822Headers.RETURN_PATH};
1208         Enumeration headerEnum = originalMessage.getMatchingHeaderLines(relevantHeaderNames);
1209         while (headerEnum.hasMoreElements()) {
1210             newMessage.addHeaderLine((String) headerEnum.nextElement());
1211         }
1212 
1213         StringWriter sout = new StringWriter();
1214         PrintWriter out   = new PrintWriter(sout, true);
1215         String head = getMessageHeaders(originalMessage);
1216         boolean all = false;
1217 
1218         String messageText = getMessage(originalMail);
1219         if(messageText != null) {
1220             out.println(messageText);
1221         }
1222 
1223         if (isDebug) {
1224             log("inline:" + getInLineType(originalMail));
1225         }
1226         switch(getInLineType(originalMail)) {
1227             case ALL: //ALL:
1228                 all = true;
1229             case HEADS: //HEADS:
1230                 out.println("Message Headers:");
1231                 out.println(head);
1232                 if(!all) {
1233                     break;
1234                 }
1235             case BODY: //BODY:
1236                 out.println("Message:");
1237                 try {
1238                     out.println(getMessageBody(originalMessage));
1239                 } catch(Exception e) {
1240                     out.println("body unavailable");
1241                 }
1242                 break;
1243             default:
1244             case NONE: //NONE:
1245                 break;
1246         }
1247 
1248         try {
1249             //Create the message body
1250             MimeMultipart multipart = new MimeMultipart("mixed");
1251 
1252             // Create the message
1253             MimeMultipart mpContent = new MimeMultipart("alternative");
1254             MimeBodyPart contentPartRoot = new MimeBodyPart();
1255             contentPartRoot.setContent(mpContent);
1256 
1257             multipart.addBodyPart(contentPartRoot);
1258 
1259             MimeBodyPart part = new MimeBodyPart();
1260             part.setText(sout.toString());
1261             part.setDisposition("inline");
1262             mpContent.addBodyPart(part);
1263             if (isDebug) {
1264                 log("attachmentType:" + getAttachmentType(originalMail));
1265             }
1266             if(getAttachmentType(originalMail) != NONE) {
1267                 part = new MimeBodyPart();
1268                 switch(getAttachmentType(originalMail)) {
1269                     case HEADS: //HEADS:
1270                         part.setText(head);
1271                         break;
1272                     case BODY: //BODY:
1273                         try {
1274                             part.setText(getMessageBody(originalMessage));
1275                         } catch(Exception e) {
1276                             part.setText("body unavailable");
1277                         }
1278                         break;
1279                     case ALL: //ALL:
1280                         StringBuffer textBuffer =
1281                             new StringBuffer(1024)
1282                                 .append(head)
1283                                 .append("\r\nMessage:\r\n")
1284                                 .append(getMessageBody(originalMessage));
1285                         part.setText(textBuffer.toString());
1286                         break;
1287                     case MESSAGE: //MESSAGE:
1288                         part.setContent(originalMessage, "message/rfc822");
1289                         break;
1290                 }
1291                 if ((originalMessage.getSubject() != null) && (originalMessage.getSubject().trim().length() > 0)) {
1292                     part.setFileName(originalMessage.getSubject().trim());
1293                 } else {
1294                     part.setFileName("No Subject");
1295                 }
1296                 part.setDisposition("Attachment");
1297                 multipart.addBodyPart(part);
1298             }
1299             //if set, attach the original mail's error message
1300             if (attachError(originalMail) && originalMail.getErrorMessage() != null) {
1301                 part = new MimeBodyPart();
1302                 part.setContent(originalMail.getErrorMessage(), "text/plain");
1303                 part.setHeader(RFC2822Headers.CONTENT_TYPE, "text/plain");
1304                 part.setFileName("Reasons");
1305                 part.setDisposition(javax.mail.Part.ATTACHMENT);
1306                 multipart.addBodyPart(part);
1307             }
1308             newMail.getMessage().setContent(multipart);
1309             newMail.getMessage().setHeader(RFC2822Headers.CONTENT_TYPE, multipart.getContentType());
1310 
1311         } catch (Exception ioe) {
1312             throw new MessagingException("Unable to create multipart body", ioe);
1313         }
1314     }
1315 
1316     /***
1317      * Sets the message id of originalMail into newMail.
1318      */
1319     private void setMessageId(Mail newMail, Mail originalMail) throws MessagingException {
1320         String messageId = originalMail.getMessage().getMessageID();
1321         if (messageId != null) {
1322             newMail.getMessage().setHeader(RFC2822Headers.MESSAGE_ID, messageId);
1323             if (isDebug) {
1324                 log("MESSAGE_ID restored to: " + messageId);
1325             }
1326         }
1327     }
1328 
1329     /***
1330      * Returns the {@link SpecialAddress} that corresponds to an init parameter value.
1331      * The init parameter value is checked against a String[] of allowed values.
1332      * The checks are case insensitive.
1333      *
1334      * @param addressString the string to check if is a special address
1335      * @param allowedSpecials a String[] with the allowed special addresses
1336      * @return a SpecialAddress if found, null if not found or addressString is null
1337      * @throws MessagingException if is a special address not in the allowedSpecials array
1338      */
1339     protected final MailAddress getSpecialAddress(String addressString, String[] allowedSpecials) throws MessagingException {
1340         if (addressString == null) {
1341             return null;
1342         }
1343 
1344         addressString = addressString.toLowerCase(Locale.US);
1345         addressString = addressString.trim();
1346 
1347         MailAddress specialAddress = null;
1348 
1349         if(addressString.compareTo("postmaster") == 0) {
1350             specialAddress = getMailetContext().getPostmaster();
1351         }
1352         if(addressString.compareTo("sender") == 0) {
1353             specialAddress = SpecialAddress.SENDER;
1354         }
1355         if(addressString.compareTo("reversepath") == 0) {
1356             specialAddress = SpecialAddress.REVERSE_PATH;
1357         }
1358         if(addressString.compareTo("from") == 0) {
1359             specialAddress = SpecialAddress.FROM;
1360         }
1361         if(addressString.compareTo("replyto") == 0) {
1362             specialAddress = SpecialAddress.REPLY_TO;
1363         }
1364         if(addressString.compareTo("to") == 0) {
1365             specialAddress = SpecialAddress.TO;
1366         }
1367         if(addressString.compareTo("recipients") == 0) {
1368             specialAddress = SpecialAddress.RECIPIENTS;
1369         }
1370         if(addressString.compareTo("delete") == 0) {
1371             specialAddress = SpecialAddress.DELETE;
1372         }
1373         if(addressString.compareTo("unaltered") == 0) {
1374             specialAddress = SpecialAddress.UNALTERED;
1375         }
1376         if(addressString.compareTo("null") == 0) {
1377             specialAddress = SpecialAddress.NULL;
1378         }
1379 
1380         // if is a special address, must be in the allowedSpecials array
1381         if (specialAddress != null) {
1382             // check if is an allowed special
1383             boolean allowed = false;
1384             for (int i = 0; i < allowedSpecials.length; i++) {
1385                 String allowedSpecial = allowedSpecials[i];
1386                 allowedSpecial = allowedSpecial.toLowerCase(Locale.US);
1387                 allowedSpecial = allowedSpecial.trim();
1388                 if(addressString.compareTo(allowedSpecial) == 0) {
1389                     allowed = true;
1390                     break;
1391                 }
1392             }
1393             if (!allowed) {
1394                 throw new MessagingException("Special (\"magic\") address found not allowed: " + addressString +
1395                                              ", allowed values are \"" + arrayToString(allowedSpecials) + "\"");
1396             }
1397         }
1398 
1399         return specialAddress;
1400     }
1401 
1402     /***
1403      * <P>Checks if a sender domain of <I>mail</I> is valid.</P>
1404      * <P>If we do not do this check, and someone uses a redirection mailet in a
1405      * processor initiated by SenderInFakeDomain, then a fake
1406      * sender domain will cause an infinite loop (the forwarded
1407      * e-mail still appears to come from a fake domain).<BR>
1408      * Although this can be viewed as a configuration error, the
1409      * consequences of such a mis-configuration are severe enough
1410      * to warrant protecting against the infinite loop.</P>
1411      * <P>This check can be skipped if {@link #getFakeDomainCheck(Mail)} returns true.</P> 
1412      *
1413      * @param mail the mail object to check
1414      * @return true if the if the sender is null or
1415      * {@link org.apache.mailet.MailetContext#getMailServers} returns true for
1416      * the sender host part
1417      */
1418     protected final boolean senderDomainIsValid(Mail mail) throws MessagingException {
1419         if (getFakeDomainCheck(mail)) {
1420             return mail.getSender() == null || getMailetContext().getMailServers(mail.getSender().getHost()).size() != 0;
1421         } else return true;
1422     }
1423     
1424     /***
1425      * Checks if there are unallowed init parameters specified in the configuration file
1426      * against the String[] allowedInitParameters.
1427      */
1428     private void checkInitParameters(String[] allowedArray) throws MessagingException {
1429         // if null then no check is requested
1430         if (allowedArray == null) {
1431             return;
1432         }
1433         
1434         Collection allowed = new HashSet();
1435         Collection bad = new ArrayList();
1436         
1437         for (int i = 0; i < allowedArray.length; i++) {
1438             allowed.add(allowedArray[i]);
1439         }
1440         
1441         Iterator iterator = getInitParameterNames();
1442         while (iterator.hasNext()) {
1443             String parameter = (String) iterator.next();
1444             if (!allowed.contains(parameter)) {
1445                 bad.add(parameter);
1446             }
1447         }
1448         
1449         if (bad.size() > 0) {
1450             throw new MessagingException("Unexpected init parameters found: "
1451                                          + arrayToString(bad.toArray()));
1452         }
1453     }
1454 
1455     /***
1456      * It changes the subject of the supplied message to to supplied value 
1457      * but it also tries to preserve the original charset information.<BR>
1458      * 
1459      * This method was needed to avoid sending the subject using a charset
1460      * (usually the default charset on the server) which doesn't contain
1461      * the characters in the subject, resulting in the loss of these characters. 
1462      * The most simple method would be to either send it in ASCII unencoded 
1463      * or in UTF-8 if non-ASCII characters are present but unfortunately UTF-8 
1464      * is not yet a MIME standard and not all email clients 
1465      * are supporting it. The optimal method would be to determine the best 
1466      * charset by analyzing the actual characters. That would require much 
1467      * more work (exept if an open source library already exists for this). 
1468      * However there is nothing to stop somebody to add a detection algorithm
1469      * for a specific charset. <BR>
1470      * 
1471      * The current algorithm works correctly if only ASCII characters are 
1472      * added to an existing subject.<BR>
1473      * 
1474      * If the new value is ASCII only, then it doesn't apply any encoding to
1475      * the subject header. (This is provided by MimeMessage.setSubject()).<BR>
1476      * 
1477      * Possible enhancement:  under java 1.4 java.nio the system can determine if the
1478      * suggested charset fits or not (if there is untranslatable
1479      * characters). If the charset doesn't fit the new value, it
1480      * can fall back to UTF-8.<BR>
1481      * 
1482      * @param message the message of which subject is changed 
1483      * @param newValue the new (unencoded) value of the subject. It must
1484      *   not be null.
1485      * @throws MessagingException - according to the JavaMail doc most likely
1486      *    this is never thrown
1487      */
1488     public static void changeSubject(MimeMessage message, String newValue)
1489             throws MessagingException
1490     {
1491         String rawSubject = message.getHeader(RFC2822Headers.SUBJECT, null);
1492         String mimeCharset = determineMailHeaderEncodingCharset(rawSubject);
1493         if (mimeCharset == null) { // most likely ASCII
1494             // it uses the system charset or the value of the
1495             // mail.mime.charset property if set  
1496             message.setSubject(newValue);
1497             return;
1498         } else { // original charset determined 
1499             String javaCharset = javax.mail.internet.MimeUtility.javaCharset(mimeCharset);
1500             try {
1501                 message.setSubject(newValue, javaCharset);
1502             } catch (MessagingException e) {
1503                 // known, but unsupported encoding
1504                 // this should be logged, the admin may setup a more i18n
1505                 // capable JRE, but the log API cannot be accessed from here  
1506                 //if (charset != null) log(charset + 
1507                 //      " charset unsupported by the JRE, email subject may be damaged");
1508                 message.setSubject(newValue); // recover
1509             }
1510         }
1511     }
1512      
1513     /***
1514      * It attempts to determine the charset used to encode an "unstructured" 
1515      * RFC 822 header (like Subject). The encoding is specified in RFC 2047.
1516      * If it cannot determine or the the text is not encoded then it returns null.
1517      *
1518      * Here is an example raw text: 
1519      * Subject: =?iso-8859-2?Q?leg=FAjabb_pr=F3ba_l=F5elemmel?=
1520      *
1521      * @param rawText the raw (not decoded) value of the header. Null means
1522      *   that the header was not present (in this case it always return null).
1523      * @return the MIME charset name or null if no encoding applied
1524      */
1525     static private String determineMailHeaderEncodingCharset(String rawText)
1526     {
1527         if (rawText == null) return null;
1528         int iEncodingPrefix = rawText.indexOf("=?");
1529         if (iEncodingPrefix == -1) return null;
1530         int iCharsetBegin = iEncodingPrefix + 2; 
1531         int iSecondQuestionMark = rawText.indexOf('?', iCharsetBegin);
1532         if (iSecondQuestionMark == -1) return null;
1533         // safety checks
1534         if (iSecondQuestionMark == iCharsetBegin) return null; // empty charset? impossible
1535         int iThirdQuestionMark = rawText.indexOf('?', iSecondQuestionMark + 1);
1536         if (iThirdQuestionMark == -1) return null; // there must be one after encoding
1537         if (-1 == rawText.indexOf("?=", iThirdQuestionMark + 1)) return null; // closing tag
1538         String mimeCharset = rawText.substring(iCharsetBegin, iSecondQuestionMark);
1539         return mimeCharset;
1540     }
1541     
1542     /***
1543      * Returns a new Collection built over <I>list</I> replacing special addresses
1544      * with real <CODE>MailAddress</CODE>-es.<BR>
1545      * Manages <CODE>SpecialAddress.SENDER</CODE>, <CODE>SpecialAddress.REVERSE_PATH</CODE>,
1546      * <CODE>SpecialAddress.FROM</CODE>, <CODE>SpecialAddress.REPLY_TO</CODE>, 
1547      * <CODE>SpecialAddress.RECIPIENTS</CODE>, <CODE>SpecialAddress.TO</CODE>, 
1548      * <CODE>SpecialAddress.NULL</CODE> and <CODE>SpecialAddress.UNALTERED</CODE>.<BR>
1549      * <CODE>SpecialAddress.FROM</CODE> is made equivalent to <CODE>SpecialAddress.SENDER</CODE>;
1550      * <CODE>SpecialAddress.TO</CODE> is made equivalent to <CODE>SpecialAddress.RECIPIENTS</CODE>.<BR>
1551      * <CODE>SpecialAddress.REPLY_TO</CODE> uses the ReplyTo header if available, otherwise the
1552      * From header if available, otherwise the Sender header if available, otherwise the return-path.<BR>
1553      * <CODE>SpecialAddress.NULL</CODE> and <CODE>SpecialAddress.UNALTERED</CODE> are ignored.<BR>
1554      * Any other address is not replaced.
1555      */
1556     protected Collection replaceMailAddresses(Mail mail, Collection list) {
1557         Collection newList = new HashSet(list.size());
1558         Iterator iterator = list.iterator();
1559         while (iterator.hasNext()) {
1560             MailAddress mailAddress = (MailAddress) iterator.next();
1561             if (!mailAddress.getHost().equalsIgnoreCase("address.marker")) {
1562                 newList.add(mailAddress);
1563             } else if (mailAddress == SpecialAddress.SENDER || mailAddress == SpecialAddress.FROM) {
1564                 MailAddress sender = mail.getSender();
1565                 if (sender != null) {
1566                     newList.add(sender);
1567                 }
1568             } else if (mailAddress == SpecialAddress.REPLY_TO) {
1569                 int parsedAddressCount = 0;
1570                 try {
1571                     InternetAddress[] replyToArray = (InternetAddress[]) mail.getMessage().getReplyTo();
1572                     if (replyToArray != null) {
1573                         for (int i = 0; i < replyToArray.length; i++) {
1574                             try {
1575                                 newList.add(new MailAddress(replyToArray[i]));
1576                                 parsedAddressCount++;
1577                             } catch (ParseException pe) {
1578                                 log("Unable to parse a \"REPLY_TO\" header address in the original message: " + replyToArray[i] + "; ignoring.");
1579                             }
1580                         }
1581                     }
1582                 } catch (MessagingException ae) {
1583                     log("Unable to parse the \"REPLY_TO\" header in the original message; ignoring.");
1584                 }
1585                 // no address was parsed?
1586                 if (parsedAddressCount == 0) {
1587                     MailAddress sender = mail.getSender();
1588                     if (sender != null) {
1589                         newList.add(sender);
1590                     }
1591                 }
1592             } else if (mailAddress == SpecialAddress.REVERSE_PATH) {
1593                 MailAddress reversePath = mail.getSender();
1594                 if (reversePath != null) {
1595                     newList.add(reversePath);
1596                 }
1597             } else if (mailAddress == SpecialAddress.RECIPIENTS || mailAddress == SpecialAddress.TO) {
1598                 newList.addAll(mail.getRecipients());
1599             } else if (mailAddress == SpecialAddress.UNALTERED) {
1600                 continue;
1601             } else if (mailAddress == SpecialAddress.NULL) {
1602                 continue;
1603             } else {
1604                 newList.add(mailAddress);
1605             }
1606         }
1607         return newList;
1608     }
1609 
1610     /***
1611      * Returns a new Collection built over <I>list</I> replacing special addresses
1612      * with real <CODE>InternetAddress</CODE>-es.<BR>
1613      * Manages <CODE>SpecialAddress.SENDER</CODE>, <CODE>SpecialAddress.REVERSE_PATH</CODE>,
1614      * <CODE>SpecialAddress.FROM</CODE>, <CODE>SpecialAddress.REPLY_TO</CODE>,
1615      * <CODE>SpecialAddress.RECIPIENTS</CODE>, <CODE>SpecialAddress.TO</CODE>, 
1616      * <CODE>SpecialAddress.NULL</CODE> and <CODE>SpecialAddress.UNALTERED</CODE>.<BR>
1617      * <CODE>SpecialAddress.RECIPIENTS</CODE> is made equivalent to <CODE>SpecialAddress.TO</CODE>.<BR>
1618      * <CODE>SpecialAddress.FROM</CODE> uses the From header if available, otherwise the Sender header if available,
1619      * otherwise the return-path.<BR>
1620      * <CODE>SpecialAddress.REPLY_TO</CODE> uses the ReplyTo header if available, otherwise the
1621      * From header if available, otherwise the Sender header if available, otherwise the return-path.<BR>
1622      * <CODE>SpecialAddress.UNALTERED</CODE> is ignored.<BR>
1623      * Any other address is not replaced.<BR>
1624      */
1625     protected Collection replaceInternetAddresses(Mail mail, Collection list) throws MessagingException {
1626         Collection newList = new HashSet(list.size());
1627         Iterator iterator = list.iterator();
1628         while (iterator.hasNext()) {
1629             InternetAddress internetAddress = (InternetAddress) iterator.next();
1630             MailAddress mailAddress = new MailAddress(internetAddress);
1631             if (!mailAddress.getHost().equalsIgnoreCase("address.marker")) {
1632                 newList.add(internetAddress);
1633             } else if (internetAddress.equals(SpecialAddress.SENDER.toInternetAddress())) {
1634                 MailAddress sender = mail.getSender();
1635                 if (sender != null) {
1636                     newList.add(sender.toInternetAddress());
1637                 }
1638             } else if (internetAddress.equals(SpecialAddress.REVERSE_PATH.toInternetAddress())) {
1639                 MailAddress reversePath = mail.getSender();
1640                 if (reversePath != null) {
1641                     newList.add(reversePath.toInternetAddress());
1642                 }
1643             } else if (internetAddress.equals(SpecialAddress.FROM.toInternetAddress())) {
1644                 try {
1645                     InternetAddress[] fromArray = (InternetAddress[]) mail.getMessage().getFrom();
1646                     if (fromArray != null) {
1647                         for (int i = 0; i < fromArray.length; i++) {
1648                             newList.add(fromArray[i]);
1649                         }
1650                     } else {
1651                         MailAddress reversePath = mail.getSender();
1652                         if (reversePath != null) {
1653                             newList.add(reversePath.toInternetAddress());
1654                         }
1655                     }
1656                 } catch (MessagingException me) {
1657                     log("Unable to parse the \"FROM\" header in the original message; ignoring.");
1658                 }
1659             } else if (internetAddress.equals(SpecialAddress.REPLY_TO.toInternetAddress())) {
1660                 try {
1661                     InternetAddress[] replyToArray = (InternetAddress[]) mail.getMessage().getReplyTo();
1662                     if (replyToArray != null) {
1663                         for (int i = 0; i < replyToArray.length; i++) {
1664                             newList.add(replyToArray[i]);
1665                         }
1666                     } else {
1667                         MailAddress reversePath = mail.getSender();
1668                         if (reversePath != null) {
1669                             newList.add(reversePath.toInternetAddress());
1670                         }
1671                     }
1672                 } catch (MessagingException me) {
1673                     log("Unable to parse the \"REPLY_TO\" header in the original message; ignoring.");
1674                 }
1675             } else if (internetAddress.equals(SpecialAddress.TO.toInternetAddress())
1676                        || internetAddress.equals(SpecialAddress.RECIPIENTS.toInternetAddress())) {
1677                 try {
1678                     String[] toHeaders = mail.getMessage().getHeader(RFC2822Headers.TO);
1679                     if (toHeaders != null) {
1680                         for (int i = 0; i < toHeaders.length; i++) {
1681                             try {
1682                                 InternetAddress[] originalToInternetAddresses = InternetAddress.parse(toHeaders[i], false);
1683                                 for (int j = 0; j < originalToInternetAddresses.length; j++) {
1684                                     newList.add(originalToInternetAddresses[j]);
1685                                 }
1686                             } catch (MessagingException ae) {
1687                                 log("Unable to parse a \"TO\" header address in the original message: " + toHeaders[i] + "; ignoring.");
1688                             }
1689                         }
1690                     }
1691                 } catch (MessagingException ae) {
1692                     log("Unable to parse the \"TO\" header  in the original message; ignoring.");
1693                 }
1694             } else if (internetAddress.equals(SpecialAddress.UNALTERED.toInternetAddress())) {
1695                 continue;
1696             } else if (internetAddress.equals(SpecialAddress.NULL.toInternetAddress())) {
1697                 continue;
1698             } else {
1699                 newList.add(internetAddress);
1700             }
1701         }
1702         return newList;
1703     }
1704 
1705 }