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(al