View Javadoc

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 org.apache.mailet.RFC2822Headers;
21  import org.apache.mailet.GenericMailet;
22  import org.apache.mailet.Mail;
23  import org.apache.mailet.MailAddress;
24  import org.apache.mailet.MailetException;
25  
26  import javax.mail.MessagingException;
27  import javax.mail.internet.MimeMessage;
28  import javax.mail.internet.MimeMultipart;
29  import java.io.IOException;
30  import java.util.Collection;
31  import java.util.Vector;
32  
33  /***
34   * An abstract implementation of a listserv.  The underlying implementation must define
35   * various settings, and can vary in their individual configuration.  Supports restricting
36   * to members only, allowing attachments or not, sending replies back to the list, and an
37   * optional subject prefix.
38   */
39  public abstract class GenericListserv extends GenericMailet {
40  
41      /***
42       * Returns a Collection of MailAddress objects of members to receive this email
43       */
44      public abstract Collection getMembers() throws MessagingException;
45  
46      /***
47       * Returns whether this list should restrict to senders only
48       */
49      public abstract boolean isMembersOnly() throws MessagingException;
50  
51      /***
52       * Returns whether this listserv allow attachments
53       */
54      public abstract boolean isAttachmentsAllowed() throws MessagingException;
55  
56      /***
57       * Returns whether listserv should add reply-to header
58       */
59      public abstract boolean isReplyToList() throws MessagingException;
60  
61      /***
62       * The email address that this listserv processes on.  If returns null, will use the
63       * recipient of the message, which hopefully will be the correct email address assuming
64       * the matcher was properly specified.
65       */
66      public MailAddress getListservAddress() throws MessagingException {
67          return null;
68      }
69  
70      /***
71       * An optional subject prefix.
72       */
73      public abstract String getSubjectPrefix() throws MessagingException;
74  
75      /***
76       * Should the subject prefix be automatically surrounded by [].
77       *
78       * @return whether the subject prefix will be surrounded by []
79       *
80       * @throws MessagingException never, for this implementation
81       */
82      public boolean isPrefixAutoBracketed() throws MessagingException {
83          return true; // preserve old behavior unless subclass overrides.
84      }
85  
86      /***
87       * <p>This takes the subject string and reduces (normailzes) it.
88       * Multiple "Re:" entries are reduced to one, and capitalized.  The
89       * prefix is always moved/placed at the beginning of the line, and
90       * extra blanks are reduced, so that the output is always of the
91       * form:</p>
92       * <code>
93       * &lt;prefix&gt; + &lt;one-optional-"Re:"*gt; + &lt;remaining subject&gt;
94       * </code>
95       * <p>I have done extensive testing of this routine with a standalone
96       * driver, and am leaving the commented out debug messages so that
97       * when someone decides to enhance this method, it can be yanked it
98       * from this file, embedded it with a test driver, and the comments
99       * enabled.</p>
100      */
101     static private String normalizeSubject(final String subj, final String prefix) {
102         // JDK IMPLEMENTATION NOTE!  When we require JDK 1.4+, all
103         // occurrences of subject.toString.().indexOf(...) can be
104         // replaced by subject.indexOf(...).
105 
106         StringBuffer subject = new StringBuffer(subj);
107         int prefixLength = prefix.length();
108 
109         // System.err.println("In:  " + subject);
110 
111         // If the "prefix" is not at the beginning the subject line, remove it
112         int index = subject.toString().indexOf(prefix);
113         if (index != 0) {
114             // System.err.println("(p) index: " + index + ", subject: " + subject);
115             if (index > 0) {
116                 subject.delete(index, index + prefixLength);
117             }
118             subject.insert(0, prefix); // insert prefix at the front
119         }
120 
121         // Replace Re: with RE:
122         String match = "Re:";
123         index = subject.toString().indexOf(match, prefixLength);
124 
125         while(index > -1) {
126             // System.err.println("(a) index: " + index + ", subject: " + subject);
127             subject.replace(index, index + match.length(), "RE:");
128             index = subject.toString().indexOf(match, prefixLength);
129             // System.err.println("(b) index: " + index + ", subject: " + subject);
130         }
131 
132         // Reduce them to one at the beginning
133         match ="RE:";
134         int indexRE = subject.toString().indexOf(match, prefixLength) + match.length();
135         index = subject.toString().indexOf(match, indexRE);
136         while(index > 0) {
137             // System.err.println("(c) index: " + index + ", subject: " + subject);
138             subject.delete(index, index + match.length());
139             index = subject.toString().indexOf(match, indexRE);
140             // System.err.println("(d) index: " + index + ", subject: " + subject);
141         }
142 
143         // Reduce blanks
144         match = "  ";
145         index = subject.toString().indexOf(match, prefixLength);
146         while(index > -1) {
147             // System.err.println("(e) index: " + index + ", subject: " + subject);
148             subject.replace(index, index + match.length(), " ");
149             index = subject.toString().indexOf(match, prefixLength);
150             // System.err.println("(f) index: " + index + ", subject: " + subject);
151         }
152 
153 
154         // System.err.println("Out: " + subject);
155 
156         return subject.toString();
157     }
158     
159     /***
160      * Processes the message.  Assumes it is the only recipient of this forked message.
161      */
162     public final void service(Mail mail) throws MessagingException {
163         try {
164             Collection members = getMembers();
165 
166             //Check for members only flag....
167             if (isMembersOnly() && !members.contains(mail.getSender())) {
168                 //Need to bounce the message to say they can't send to this list
169                 getMailetContext().bounce(mail, "Only members of this listserv are allowed to send a message to this address.");
170                 mail.setState(Mail.GHOST);
171                 return;
172             }
173 
174             //Check for no attachments
175             if (!isAttachmentsAllowed() && mail.getMessage().getContent() instanceof MimeMultipart) {
176                 getMailetContext().bounce(mail, "You cannot send attachments to this listserv.");
177                 mail.setState(Mail.GHOST);
178                 return;
179             }
180 
181             //Create a copy of this message to send out
182             MimeMessage message = new MimeMessage(mail.getMessage());
183             //We need to remove this header from the copy we're sending around
184             message.removeHeader(RFC2822Headers.RETURN_PATH);
185 
186             //Figure out the listserv address.
187             MailAddress listservAddr = getListservAddress();
188             if (listservAddr == null) {
189                 //Use the recipient
190                 listservAddr = (MailAddress)mail.getRecipients().iterator().next();
191             }
192 
193             //Check if the X-been-there header is set to the listserv's name
194             //  (the address).  If it has, this means it's a message from this
195             //  listserv that's getting bounced back, so we need to swallow it
196             if (listservAddr.equals(message.getHeader("X-been-there"))) {
197                 mail.setState(Mail.GHOST);
198                 return;
199             }
200 
201             //Set the subject if set
202             String prefix = getSubjectPrefix();
203             if (prefix != null) {
204                 if (isPrefixAutoBracketed()) {
205                     StringBuffer prefixBuffer =
206                         new StringBuffer(64)
207                             .append("[")
208                             .append(prefix)
209                             .append("] ");
210                     prefix = prefixBuffer.toString();
211                 }
212                 String subj = message.getSubject();
213                 if (subj == null) {
214                     subj = "";
215                 }
216                 subj = normalizeSubject(subj, prefix);
217                 AbstractRedirect.changeSubject(message, subj);
218             }
219 
220             //If replies should go to this list, we need to set the header
221             if (isReplyToList()) {
222                 message.setHeader(RFC2822Headers.REPLY_TO, listservAddr.toString());
223             }
224             //We're going to set this special header to avoid bounces
225             //  getting sent back out to the list
226             message.setHeader("X-been-there", listservAddr.toString());
227 
228             //Send the message to the list members
229             //We set the postmaster as the sender for now so bounces go to him/her
230             getMailetContext().sendMail(getMailetContext().getPostmaster(), members, message);
231 
232             //Kill the old message
233             mail.setState(Mail.GHOST);
234         } catch (IOException ioe) {
235             throw new MailetException("Error creating listserv message", ioe);
236         }
237     }
238 }