View Javadoc

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