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