View Javadoc

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 org.apache.mailet.base.GenericMailet;
23  import org.apache.mailet.Mail;
24  import org.apache.mailet.MailetException;
25  
26  import javax.mail.Message;
27  import javax.mail.MessagingException;
28  import javax.mail.Multipart;
29  import javax.mail.Part;
30  import javax.mail.internet.MimeMessage;
31  import javax.mail.internet.MimeUtility;
32  
33  import java.io.BufferedInputStream;
34  import java.io.BufferedOutputStream;
35  import java.io.ByteArrayOutputStream;
36  import java.io.File;
37  import java.io.FileOutputStream;
38  import java.io.InputStream;
39  import java.io.OutputStream;
40  import java.util.ArrayList;
41  import java.util.Collection;
42  import java.util.LinkedHashMap;
43  import java.util.List;
44  import java.util.Map;
45  import java.util.regex.Pattern;
46  
47  /**
48   * <p>
49   * Remove attachments from a Message. Supports simple removal, storing to file,
50   * or storing to mail attributes.
51   * </p>
52   * <p>
53   * Configuration:
54   * </p>
55   * <p>
56   * 
57   * <pre>
58   *   &lt;mailet match=&quot;All&quot; class=&quot;StripAttachment&quot; &gt;
59   *     &lt;pattern &gt;.*\.xls &lt;/pattern&gt;  &lt;!-- The regular expression that must be matched -- &gt;
60   *     &lt;!-- notpattern &gt;.*\.xls &lt;/notpattern--&gt;  &lt;!-- The regular expression that must be matched -- &gt;
61   *     &lt;directory &gt;c:\temp\james_attach &lt;/directory&gt;   &lt;!-- The directory to save to -- &gt;
62   *     &lt;remove &gt;all &lt;/remove&gt;   &lt;!-- either &quot;no&quot;, &quot;matched&quot;, &quot;all&quot; -- &gt;
63   *     &lt;!-- attribute&gt;my.attribute.name&lt;/attribute --&gt;
64   *   &lt;/mailet &gt;
65   * </pre>
66   * 
67   * </p>
68   */
69  public class StripAttachment extends GenericMailet {
70  
71      public static final String PATTERN_PARAMETER_NAME = "pattern";
72  
73      public static final String NOTPATTERN_PARAMETER_NAME = "notpattern";
74  
75      public static final String ATTRIBUTE_PARAMETER_NAME = "attribute";
76  
77      public static final String DIRECTORY_PARAMETER_NAME = "directory";
78  
79      // Either "no", "matched", "all"
80      public static final String REMOVE_ATTACHMENT_PARAMETER_NAME = "remove"; 
81  
82      // Either "true", "false"
83      public static final String DECODE_FILENAME_PARAMETER_NAME = "decodeFilename";
84  
85      public static final String REPLACE_FILENAME_PATTERN_PARAMETER_NAME = "replaceFilenamePattern";
86  
87      public static final String REMOVE_NONE = "no";
88  
89      public static final String REMOVE_ALL = "all";
90  
91      public static final String REMOVE_MATCHED = "matched";
92  
93      public static final String REMOVED_ATTACHMENTS_ATTRIBUTE_KEY = "org.apache.james.transport.mailets.StripAttachment.removed";
94  
95      public static final String SAVED_ATTACHMENTS_ATTRIBUTE_KEY = "org.apache.james.transport.mailets.StripAttachment.saved";
96  
97      private String patternString = null;
98  
99      private String notpatternString = null;
100 
101     private String removeAttachments = null;
102 
103     private String directoryName = null;
104 
105     private String attributeName = null;
106 
107     private Pattern regExPattern = null;
108 
109     private Pattern notregExPattern = null;
110 
111     private boolean decodeFilename = false;
112 
113     private Pattern[] replaceFilenamePatterns = null;
114 
115     private String[] replaceFilenameSubstitutions = null;
116 
117     private Integer[] replaceFilenameFlags = null;
118 
119     private static boolean getBooleanParameter(String v, boolean def) {
120         return def ? !(v != null && (v.equalsIgnoreCase("false") || v
121                 .equalsIgnoreCase("no"))) : v != null
122                 && (v.equalsIgnoreCase("true") || v.equalsIgnoreCase("yes"));
123     }
124 
125     /**
126      * Checks if the mandatory parameters are present, creates the directory to
127      * save the files ni (if not present).
128      * 
129      * @throws MailetException
130      */
131     public void init() throws MailetException {
132         patternString = getInitParameter(PATTERN_PARAMETER_NAME);
133         notpatternString = getInitParameter(NOTPATTERN_PARAMETER_NAME);
134         if (patternString == null && notpatternString == null) {
135             throw new MailetException("No value for " + PATTERN_PARAMETER_NAME
136                     + " parameter was provided.");
137         }
138 
139         directoryName = getInitParameter(DIRECTORY_PARAMETER_NAME);
140         attributeName = getInitParameter(ATTRIBUTE_PARAMETER_NAME);
141 
142         removeAttachments = getInitParameter(REMOVE_ATTACHMENT_PARAMETER_NAME,
143                 REMOVE_NONE).toLowerCase();
144         if (!REMOVE_MATCHED.equals(removeAttachments)
145                 && !REMOVE_ALL.equals(removeAttachments)) {
146             removeAttachments = REMOVE_NONE;
147         }
148 
149         try {
150             // if (patternString != null) regExPattern = new
151             // Perl5Compiler().compile(patternString);
152             if (patternString != null)
153                 regExPattern = Pattern.compile(patternString);
154         } catch (Exception e) {
155             throw new MailetException("Could not compile regex ["
156                     + patternString + "].");
157         }
158         try {
159             // if (notpatternString != null) notregExPattern = new
160             // Perl5Compiler().compile(notpatternString);
161             if (notpatternString != null)
162                 notregExPattern = Pattern.compile(notpatternString);
163         } catch (Exception e) {
164             throw new MailetException("Could not compile regex ["
165                     + notpatternString + "].");
166         }
167 
168         if (directoryName != null) {
169             try {
170                 File saveDirectory = null;
171                 saveDirectory = new File(directoryName);
172                 if (!saveDirectory.exists()) {
173                     saveDirectory.mkdirs();
174                 }
175             } catch (Exception e) {
176                 throw new MailetException("Could not create directory ["
177                         + directoryName + "].", e);
178             }
179         }
180 
181         decodeFilename = getBooleanParameter(
182                 getInitParameter(DECODE_FILENAME_PARAMETER_NAME),
183                 decodeFilename);
184         if (getInitParameter(REPLACE_FILENAME_PATTERN_PARAMETER_NAME) != null) {
185             List[] pl = ReplaceContent
186                     .getPatternsFromString(getInitParameter(REPLACE_FILENAME_PATTERN_PARAMETER_NAME));
187             replaceFilenamePatterns = (Pattern[]) pl[0].toArray(new Pattern[0]);
188             replaceFilenameSubstitutions = (String[]) pl[1]
189                     .toArray(new String[0]);
190             replaceFilenameFlags = (Integer[]) pl[2].toArray(new Integer[0]);
191         }
192 
193         String toLog = "StripAttachment is initialised with regex pattern ["
194                 + patternString + " / " + notpatternString + "]";
195         if (directoryName != null) {
196             toLog += " and will save to directory [" + directoryName + "]";
197         }
198         if (attributeName != null) {
199             toLog += " and will store attachments to attribute ["
200                     + attributeName + "]";
201         }
202         log(toLog);
203     }
204 
205     /**
206      * Service the mail: scan it for attchemnts matching the pattern, store the
207      * content of a matchin attachment in the given directory.
208      * 
209      * @param mail
210      *            The mail to service
211      * @throws MailetException
212      *             Thrown when an error situation is encountered.
213      */
214     public void service(Mail mail) throws MailetException {
215         MimeMessage message = null;
216         try {
217             message = mail.getMessage();
218         } catch (MessagingException e) {
219             throw new MailetException(
220                     "Could not retrieve message from Mail object", e);
221         }
222         // All MIME messages with an attachment are multipart, so we do nothing
223         // if it is not mutlipart
224         try {
225             if (message.isMimeType("multipart/*")) {
226                 analyseMultipartPartMessage(message, mail);
227             }
228         } catch (MessagingException e) {
229             throw new MailetException(
230                     "Could not retrieve contenttype of message.", e);
231         } catch (Exception e) {
232             throw new MailetException("Could not analyse message.", e);
233         }
234     }
235 
236     /**
237      * returns a String describing this mailet.
238      * 
239      * @return A desciption of this mailet
240      */
241     public String getMailetInfo() {
242         return "StripAttachment";
243     }
244 
245     /**
246      * Checks every part in this part (if it is a Multipart) for having a
247      * filename that matches the pattern. If the name matches, the content of
248      * the part is stored (using its name) in te given diretcory.
249      * 
250      * Note: this method is recursive.
251      * 
252      * @param part
253      *            The part to analyse.
254      * @param mail
255      * @return
256      * @throws Exception
257      */
258     private boolean analyseMultipartPartMessage(Part part, Mail mail)
259             throws Exception {
260         if (part.isMimeType("multipart/*")) {
261             try {
262                 Multipart multipart = (Multipart) part.getContent();
263                 boolean atLeastOneRemoved = false;
264                 int numParts = multipart.getCount();
265                 for (int i = 0; i < numParts; i++) {
266                     Part p = multipart.getBodyPart(i);
267                     if (p.isMimeType("multipart/*")) {
268                         atLeastOneRemoved |= analyseMultipartPartMessage(p,
269                                 mail);
270                     } else {
271                         boolean removed = checkMessageRemoved(p, mail);
272                         if (removed) {
273                             multipart.removeBodyPart(i);
274                             atLeastOneRemoved = true;
275                             i--;
276                             numParts--;
277                         }
278                     }
279                 }
280                 if (atLeastOneRemoved) {
281                     part.setContent(multipart);
282                     if (part instanceof Message) {
283                         ((Message) part).saveChanges();
284                     }
285                 }
286                 return atLeastOneRemoved;
287             } catch (Exception e) {
288                 log("Could not analyse part.", e);
289             }
290         }
291         return false;
292     }
293 
294     private boolean checkMessageRemoved(Part part, Mail mail)
295             throws MessagingException, Exception {
296         String fileName = null;
297         fileName = part.getFileName();
298 
299         // filename or name of part can be null, so we have to be careful
300         boolean ret = false;
301 
302         if (fileName != null) {
303             if (decodeFilename)
304                 fileName = MimeUtility.decodeText(fileName);
305 
306             if (replaceFilenamePatterns != null)
307                 fileName = ReplaceContent.applyPatterns(
308                         replaceFilenamePatterns, replaceFilenameSubstitutions,
309                         replaceFilenameFlags, fileName, 0, this);
310 
311             if (fileNameMatches(fileName)) {
312                 if (directoryName != null) {
313                     String filename = saveAttachmentToFile(part, fileName);
314                     if (filename != null) {
315                         Collection c = (Collection) mail
316                                 .getAttribute(SAVED_ATTACHMENTS_ATTRIBUTE_KEY);
317                         if (c == null) {
318                             c = new ArrayList();
319                             mail.setAttribute(SAVED_ATTACHMENTS_ATTRIBUTE_KEY,
320                                     (ArrayList) c);
321                         }
322                         c.add(filename);
323                     }
324                 }
325                 if (attributeName != null) {
326                     Map m = (Map) mail.getAttribute(attributeName);
327                     if (m == null) {
328                         m = new LinkedHashMap();
329                         mail.setAttribute(attributeName, (LinkedHashMap) m);
330                     }
331                     ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
332                     OutputStream os = new BufferedOutputStream(
333                             byteArrayOutputStream);
334                     part.writeTo(os);
335                     m.put(fileName, byteArrayOutputStream.toByteArray());
336                 }
337                 if (removeAttachments.equals(REMOVE_MATCHED)) {
338                     ret = true;
339                 }
340             }
341             if (!ret) {
342                 ret = removeAttachments.equals(REMOVE_ALL);
343             }
344             if (ret) {
345                 Collection c = (Collection) mail
346                         .getAttribute(REMOVED_ATTACHMENTS_ATTRIBUTE_KEY);
347                 if (c == null) {
348                     c = new ArrayList();
349                     mail.setAttribute(REMOVED_ATTACHMENTS_ATTRIBUTE_KEY,
350                             (ArrayList) c);
351                 }
352                 c.add(fileName);
353             }
354         }
355         return ret;
356     }
357 
358     /**
359      * Checks if the given name matches the pattern.
360      * 
361      * @param name
362      *            The name to check for a match.
363      * @return True if a match is found, false otherwise.
364      */
365     private boolean fileNameMatches(String name) {
366         boolean result = true;
367         if (regExPattern != null)
368             result = regExPattern.matcher(name).matches();
369         if (result && notregExPattern != null)
370             result = !notregExPattern.matcher(name).matches();
371 
372         String log = "attachment " + name + " ";
373         if (!result)
374             log += "does not match";
375         else
376             log += "matches";
377         log(log);
378         return result;
379     }
380 
381     /**
382      * Saves the content of the part to a file in the given directoy, using the
383      * name of the part. If a file with that name already exists, it will
384      * 
385      * @param part
386      *            The MIME part to save.
387      * @return
388      * @throws Exception
389      */
390     private String saveAttachmentToFile(Part part, String fileName)
391             throws Exception {
392         BufferedOutputStream os = null;
393         InputStream is = null;
394         File f = null;
395         try {
396             if (fileName == null)
397                 fileName = part.getFileName();
398             int pos = -1;
399             if (fileName != null) {
400                 pos = fileName.lastIndexOf(".");
401             }
402             String prefix = pos > 0 ? (fileName.substring(0, pos)) : fileName;
403             String suffix = pos > 0 ? (fileName.substring(pos)) : ".bin";
404             while (prefix.length() < 3)
405                 prefix += "_";
406             if (suffix.length() == 0)
407                 suffix = ".bin";
408             f = File.createTempFile(prefix, suffix, new File(directoryName));
409             log("saving content of " + f.getName() + "...");
410             os = new BufferedOutputStream(new FileOutputStream(f));
411             is = part.getInputStream();
412             if (!(is instanceof BufferedInputStream)) {
413                 is = new BufferedInputStream(is);
414             }
415             int c;
416             while ((c = is.read()) != -1) {
417                 os.write(c);
418             }
419 
420             return f.getName();
421         } catch (Exception e) {
422             log("Error while saving contents of ["
423                     + (f != null ? f.getName() : (part != null ? part
424                             .getFileName() : "NULL")) + "].", e);
425             throw e;
426         } finally {
427             is.close();
428             os.close();
429         }
430     }
431 
432 }