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