1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package org.xwiki.mail

File ExtendedMimeMessage.java

 

Coverage histogram

../../../img/srcFileCovDistChart10.png
0% of files have more coverage

Code metrics

14
47
13
1
231
121
27
0.57
3.62
13
2.08

Classes

Class Line # Actions
ExtendedMimeMessage 38 47 0% 27 3
0.959459595.9%
 

Contributing tests

This file is covered by 44 tests. .

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package org.xwiki.mail;
21   
22    import java.io.InputStream;
23   
24    import javax.mail.MessagingException;
25    import javax.mail.Session;
26    import javax.mail.internet.InternetAddress;
27    import javax.mail.internet.MimeMessage;
28   
29    import org.bouncycastle.crypto.digests.SHA1Digest;
30    import org.bouncycastle.util.encoders.Base64;
31   
32    /**
33    * Extension of the {@link javax.mail.internet.MimeMessage} in order to support processing by this mail API.
34    *
35    * @version $Id: 3dc68f67016d8064f698487fffe4581062e33f73 $
36    * @since 7.4.1
37    */
 
38    public class ExtendedMimeMessage extends MimeMessage
39    {
40    private static final ThreadLocal<SHA1Digest> SHA1_DIGEST = new ThreadLocal<SHA1Digest>();
41   
42    private static final String MESSAGE_ID_HEADER = "Message-ID";
43    private static final String TO_HEADER = "To";
44    private static final String XMAIL_TYPE_HEADER = "X-MailType";
45   
46    private String uniqueMessageId;
47   
48    /**
49    * Create a new extended MimeMessage.
50    *
51    * Note: We don't care about supporting Session here ATM since it's not required. MimeMessages will be
52    * given a valid Session when it's deserialized from the mail content store for sending.
53    */
 
54  45 toggle public ExtendedMimeMessage()
55    {
56  45 super((Session) null);
57    }
58   
59    /**
60    * Constructs a MimeMessage by reading and parsing the data from the specified MIME InputStream.
61    *
62    * @param session Session object for this message
63    * @param is the message input stream
64    * @throws MessagingException on error
65    */
 
66  21 toggle public ExtendedMimeMessage(Session session, InputStream is) throws MessagingException
67    {
68  21 super(session, is);
69    }
70   
71    /**
72    * @param source see javadoc for {@link MimeMessage#MimeMessage(javax.mail.internet.MimeMessage)}
73    * @throws MessagingException see javadoc for {@link MimeMessage#MimeMessage(javax.mail.internet.MimeMessage)}
74    */
 
75  23 toggle public ExtendedMimeMessage(MimeMessage source) throws MessagingException
76    {
77  23 super(source);
78    }
79   
80    /**
81    * Helper method to wrap any {@link MimeMessage} into an {@link ExtendedMimeMessage}, without double wrapping.
82    *
83    * @param message the {@link MimeMessage} to wrap.
84    * @return the {@link ExtendedMimeMessage}.
85    * @throws RuntimeException if an error occurs during the conversion, which is unexpected if the initial message
86    * is a fully formed MimeMessage or already an ExtendedMimeMessage.
87    */
 
88  31 toggle public static ExtendedMimeMessage wrap(MimeMessage message)
89    {
90  31 if (message instanceof ExtendedMimeMessage) {
91  18 return (ExtendedMimeMessage) message;
92    } else {
93  13 try {
94  13 return new ExtendedMimeMessage(message);
95    } catch (MessagingException e) {
96    // We do not expect that an existing MimeMessage could not be wrapped in an extended one.
97  0 throw new RuntimeException("Unexpected exception while wrapping a MimeMessage into an extended one", e);
98    }
99    }
100    }
101   
102    /**
103    * @return true if no body content has been defined yet for this message or false otherwise
104    */
 
105  8 toggle public boolean isEmpty()
106    {
107  8 return this.dh == null;
108    }
109   
110    /**
111    * Specifies what type of email is being sent. This is useful for applications to specify a type when they send
112    * mail. This allows (for example) to filter these emails in the Mail Sender Status Admin UI.
113    *
114    * @param mailType the type of mail being sent (e.g "Watchlist", "Reset Password", "Send Page by Mail", etc)
115    */
 
116  18 toggle public void setType(String mailType)
117    {
118  18 try {
119  18 addHeader(XMAIL_TYPE_HEADER, mailType);
120    } catch (MessagingException e) {
121    // Very unlikely to happen since the default implementation does not throw anything
122    }
123    }
124   
125    /**
126    * Retrieve what type of email is being sent (see {@link #setType(String)}).
127    *
128    * @return the type of mail being sent (e.g "Watchlist", "Reset Password", "Send Page by Mail", etc)
129    */
 
130  45 toggle public String getType()
131    {
132  45 try {
133  45 return getHeader(XMAIL_TYPE_HEADER, null);
134    } catch (MessagingException e) {
135    // Very unlikely to happen since the default implementation does not throw anything
136  0 return null;
137    }
138    }
139   
140    /**
141    * Save the message and set the message-ID headers of the message to the provided value.
142    *
143    * @param messageId message identifier to be set on the message header.
144    */
 
145  5 toggle public void setMessageId(String messageId)
146    {
147  5 try {
148  5 ensureSaved();
149  5 setHeader(MESSAGE_ID_HEADER, messageId);
150    } catch (MessagingException e) {
151    // Very unlikely to happen since the default implementation does not throw anything
152    }
153    }
154   
155    /**
156    * Ensure that a message is saved to ensure the stability of its Message-ID header.
157    *
158    * @return true if the message has been saved, false if it was already saved.
159    * @throws MessagingException on error
160    */
 
161  25 toggle public boolean ensureSaved() throws MessagingException
162    {
163  25 if (!this.saved) {
164  4 this.saveChanges();
165  4 return true;
166    }
167  21 return false;
168    }
169   
 
170  145 toggle @Override
171    public void setHeader(String name, String value) throws MessagingException
172    {
173  145 if (uniqueMessageId != null && MESSAGE_ID_HEADER.equals(name) || TO_HEADER.equals(name)) {
174    // Clear cached unique messageId when the headers used to compute it are changed
175  28 uniqueMessageId = null;
176    }
177  145 super.setHeader(name, value);
178    }
179   
180    /**
181    * Compute a unique message identifier for this mime message. The unique identifier is based on the
182    * message-ID header and the recipients. If no message-ID is found, the message is first saved in order to
183    * generate a message-ID.
184    *
185    * @return a unique identifier for this message
186    */
 
187  114 toggle public String getUniqueMessageId()
188    {
189  114 if (uniqueMessageId == null) {
190  59 try {
191  59 StringBuilder sb = new StringBuilder(getNotNullMessageId());
192  59 String recipients = InternetAddress.toString(getAllRecipients());
193  59 if (recipients != null) {
194  38 sb.append(':').append(recipients);
195    }
196  59 uniqueMessageId = digest(sb.toString());
197    } catch (MessagingException e) {
198    // This should never happen since the implementation for getHeader() never throws an exception (even
199    // though the interface specifies it can) and similarly getAllRecipients() will also never throw an
200    // exception since the only reason would be if an address is malformed but there's a check when setting
201    // it already in the MimeMessage and thus in practice it cannot happen.
202  0 throw new RuntimeException("Unexpected exception while computing a unique id for a MimeMessage", e);
203    }
204    }
205  114 return uniqueMessageId;
206    }
207   
 
208  59 toggle private String getNotNullMessageId() throws MessagingException
209    {
210  59 String messageId = getMessageID();
211  59 if (messageId == null) {
212  8 saveChanges();
213  8 messageId = getMessageID();
214    }
215  59 return messageId;
216    }
217   
 
218  59 toggle private String digest(String data)
219    {
220  59 SHA1Digest digest = SHA1_DIGEST.get();
221  59 if (digest == null) {
222  21 digest = new SHA1Digest();
223  21 SHA1_DIGEST.set(new SHA1Digest());
224    }
225  59 byte[] bytes = data.getBytes();
226  59 digest.update(bytes, 0, bytes.length);
227  59 byte[] dig = new byte[digest.getDigestSize()];
228  59 digest.doFinal(dig, 0);
229  59 return Base64.toBase64String(dig);
230    }
231    }