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 }