19 package org.sleuthkit.autopsy.thunderbirdparser;
21 import java.io.BufferedReader;
23 import java.io.FileOutputStream;
24 import java.io.FileWriter;
25 import java.io.IOException;
26 import java.util.ArrayList;
27 import java.util.List;
28 import java.util.UUID;
29 import java.util.logging.Level;
30 import org.apache.james.mime4j.dom.BinaryBody;
31 import org.apache.james.mime4j.dom.Body;
32 import org.apache.james.mime4j.dom.Entity;
33 import org.apache.james.mime4j.dom.Message;
34 import org.apache.james.mime4j.dom.Multipart;
35 import org.apache.james.mime4j.dom.SingleBody;
36 import org.apache.james.mime4j.dom.TextBody;
37 import org.apache.james.mime4j.dom.address.AddressList;
38 import org.apache.james.mime4j.dom.address.Mailbox;
39 import org.apache.james.mime4j.dom.address.MailboxList;
40 import org.apache.james.mime4j.dom.field.ContentDispositionField;
41 import org.apache.james.mime4j.dom.field.ContentTypeField;
42 import org.apache.james.mime4j.message.DefaultMessageBuilder;
43 import org.apache.james.mime4j.stream.Field;
44 import org.apache.james.mime4j.stream.MimeConfig;
45 import org.openide.util.NbBundle;
55 class MimeJ4MessageParser {
57 private static final Logger logger = Logger.getLogger(MimeJ4MessageParser.class.getName());
62 private static final String HTML_TYPE =
"text/html";
63 private DefaultMessageBuilder messageBuilder = null;
64 private final List<String> errorList =
new ArrayList<>();
69 private String localPath;
71 DefaultMessageBuilder getMessageBuilder() {
72 if (messageBuilder == null) {
73 messageBuilder =
new DefaultMessageBuilder();
74 MimeConfig config = MimeConfig.custom().setMaxLineLen(-1).setMaxHeaderLen(-1).setMaxHeaderCount(-1).build();
76 messageBuilder.setMimeEntityConfig(config);
79 return messageBuilder;
87 final void setLocalPath(String localPath) {
88 this.localPath = localPath;
96 String getLocalPath() {
108 for (String msg : errorList) {
109 result +=
"<li>" + msg +
"</li>";
119 void addErrorMessage(String msg) {
131 EmailMessage extractEmail(Message msg, String localPath,
long sourceFileID) {
132 EmailMessage email =
new EmailMessage();
134 email.setSender(getAddresses(msg.getFrom()));
135 email.setRecipients(getAddresses(msg.getTo()));
136 email.setBcc(getAddresses(msg.getBcc()));
137 email.setCc(getAddresses(msg.getCc()));
138 email.setSubject(msg.getSubject());
139 email.setSentDate(msg.getDate());
140 email.setLocalPath(localPath);
141 email.setMessageID(msg.getMessageId());
143 Field field = msg.getHeader().getField(
"in-reply-to");
144 String inReplyTo = null;
147 inReplyTo = field.getBody();
148 email.setInReplyToID(inReplyTo);
151 field = msg.getHeader().getField(
"references");
153 List<String> references =
new ArrayList<>();
154 for (String
id : field.getBody().split(
">")) {
155 references.add(
id.trim() +
">");
158 if (!references.contains(inReplyTo)) {
159 references.add(inReplyTo);
162 email.setReferences(references);
166 if (msg.isMultipart()) {
167 handleMultipart(email, (Multipart) msg.getBody(), sourceFileID);
169 handleTextBody(email, (TextBody) msg.getBody(), msg.getMimeType(), msg.getHeader().getFields());
183 EmailMessage extractPartialEmail(Message msg) {
184 EmailMessage email =
new EmailMessage();
185 email.setSubject(msg.getSubject());
186 email.setMessageID(msg.getMessageId());
188 Field field = msg.getHeader().getField(
"in-reply-to");
189 String inReplyTo = null;
192 inReplyTo = field.getBody();
193 email.setInReplyToID(inReplyTo);
196 field = msg.getHeader().getField(
"references");
198 List<String> references =
new ArrayList<>();
199 for (String
id : field.getBody().split(
">")) {
200 references.add(
id.trim() +
">");
203 if (!references.contains(inReplyTo)) {
204 references.add(inReplyTo);
207 email.setReferences(references);
221 private void handleMultipart(EmailMessage email, Multipart multi,
long fileID) {
222 List<Entity> entities = multi.getBodyParts();
223 for (
int index = 0; index < entities.size(); index++) {
224 Entity e = entities.get(index);
225 if (e.isMultipart()) {
226 handleMultipart(email, (Multipart) e.getBody(), fileID);
227 }
else if (e.getDispositionType() != null
228 && e.getDispositionType().equals(ContentDispositionField.DISPOSITION_TYPE_ATTACHMENT)) {
229 handleAttachment(email, e, fileID, index);
230 }
else if (e.getMimeType().equals(HTML_TYPE)
231 || e.getMimeType().equals(ContentTypeField.TYPE_TEXT_PLAIN)) {
232 handleTextBody(email, (TextBody) e.getBody(), e.getMimeType(), e.getHeader().getFields());
249 private void handleTextBody(EmailMessage email, TextBody tb, String type, List<Field> fields) {
252 r =
new BufferedReader(tb.getReader());
253 StringBuilder bodyString =
new StringBuilder();
254 StringBuilder headersString =
new StringBuilder();
256 while ((line = r.readLine()) != null) {
257 bodyString.append(line).append(
"\n");
260 headersString.append(
"\n-----HEADERS-----\n");
261 for (Field field : fields) {
262 String nextLine = field.getName() +
": " + field.getBody();
263 headersString.append(
"\n").append(nextLine);
265 headersString.append(
"\n\n---END HEADERS--\n\n");
267 email.setHeaders(headersString.toString());
270 case ContentTypeField.TYPE_TEXT_PLAIN:
271 email.setTextBody(bodyString.toString());
274 email.setHtmlBody(bodyString.toString());
280 }
catch (IOException ex) {
281 logger.log(Level.WARNING,
"Error getting text body of mbox message", ex);
292 @NbBundle.Messages({
"MimeJ4MessageParser.handleAttch.noOpenCase.errMsg=Exception while getting open case."})
293 private static void handleAttachment(EmailMessage email, Entity e,
long fileID,
int index) {
294 String outputDirPath;
295 String relModuleOutputPath;
297 outputDirPath = ThunderbirdMboxFileIngestModule.getModuleOutputPath() + File.separator;
298 relModuleOutputPath = ThunderbirdMboxFileIngestModule.getRelModuleOutputPath() + File.separator;
299 }
catch (NoCurrentCaseException ex) {
300 logger.log(Level.SEVERE, Bundle.MimeJ4MessageParser_handleAttch_noOpenCase_errMsg(), ex);
303 String filename = e.getFilename();
305 if (filename == null) {
306 filename =
"attachment" + e.hashCode();
307 logger.log(Level.WARNING, String.format(
"Attachment has no file name using '%s'", filename));
310 filename = FileUtil.escapeFileName(filename);
314 if (filename.length() > 64) {
315 filename = UUID.randomUUID().toString();
318 String uniqueFilename = fileID +
"-" + index +
"-" + email.getSentDate() +
"-" + filename;
319 String outPath = outputDirPath + uniqueFilename;
321 Body body = e.getBody();
322 if (body instanceof SingleBody) {
323 try (EncodedFileOutputStream fos =
new EncodedFileOutputStream(
new FileOutputStream(outPath), TskData.EncodingType.XOR1)) {
324 ((SingleBody) body).writeTo(fos);
325 }
catch (IOException ex) {
326 logger.log(Level.WARNING,
"Failed to create file output stream for: " + outPath, ex);
330 EmailMessage.Attachment attach =
new EmailMessage.Attachment();
331 attach.setName(filename);
332 attach.setLocalPath(relModuleOutputPath + uniqueFilename);
333 attach.setSize(
new File(outPath).length());
334 attach.setEncodingType(TskData.EncodingType.XOR1);
335 email.addAttachment(attach);
350 private static String getAddresses(MailboxList mailboxList) {
351 if (mailboxList == null) {
354 StringBuilder addresses =
new StringBuilder();
355 for (Mailbox m : mailboxList) {
356 addresses.append(m.toString()).append(
"; ");
358 return addresses.toString();
370 private static String getAddresses(AddressList addressList) {
371 return (addressList == null) ?
"" : getAddresses(addressList.flatten());