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