Autopsy  4.14.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
MimeJ4MessageParser.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2019 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.thunderbirdparser;
20 
21 import java.io.BufferedReader;
22 import java.io.File;
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;
49 import org.sleuthkit.datamodel.EncodedFileOutputStream;
50 import org.sleuthkit.datamodel.TskData;
51 
55 class MimeJ4MessageParser {
56 
57  private static final Logger logger = Logger.getLogger(MimeJ4MessageParser.class.getName());
58 
62  private static final String HTML_TYPE = "text/html"; //NON-NLS
63  private DefaultMessageBuilder messageBuilder = null;
64  private final List<String> errorList = new ArrayList<>();
65 
69  private String localPath;
70 
71  DefaultMessageBuilder getMessageBuilder() {
72  if (messageBuilder == null) {
73  messageBuilder = new DefaultMessageBuilder();
74  MimeConfig config = MimeConfig.custom().setMaxLineLen(-1).setMaxHeaderLen(-1).setMaxHeaderCount(-1).build();
75  // disable line length checks.
76  messageBuilder.setMimeEntityConfig(config);
77  }
78 
79  return messageBuilder;
80  }
81 
87  final void setLocalPath(String localPath) {
88  this.localPath = localPath;
89  }
90 
96  String getLocalPath() {
97  return localPath;
98  }
99 
106  String getErrors() {
107  String result = "";
108  for (String msg : errorList) {
109  result += "<li>" + msg + "</li>";
110  }
111  return result;
112  }
113 
119  void addErrorMessage(String msg) {
120  errorList.add(msg);
121  }
122 
131  EmailMessage extractEmail(Message msg, String localPath, long sourceFileID) {
132  EmailMessage email = new EmailMessage();
133  // Basic Info
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());
142 
143  Field field = msg.getHeader().getField("in-reply-to"); //NON-NLS
144  String inReplyTo = null;
145 
146  if (field != null) {
147  inReplyTo = field.getBody();
148  email.setInReplyToID(inReplyTo);
149  }
150 
151  field = msg.getHeader().getField("references");
152  if (field != null) {
153  List<String> references = new ArrayList<>();
154  for (String id : field.getBody().split(">")) {
155  references.add(id.trim() + ">");
156  }
157 
158  if (!references.contains(inReplyTo)) {
159  references.add(inReplyTo);
160  }
161 
162  email.setReferences(references);
163  }
164 
165  // Body
166  if (msg.isMultipart()) {
167  handleMultipart(email, (Multipart) msg.getBody(), sourceFileID);
168  } else {
169  handleTextBody(email, (TextBody) msg.getBody(), msg.getMimeType(), msg.getHeader().getFields());
170  }
171 
172  return email;
173  }
174 
183  EmailMessage extractPartialEmail(Message msg) {
184  EmailMessage email = new EmailMessage();
185  email.setSubject(msg.getSubject());
186  email.setMessageID(msg.getMessageId());
187 
188  Field field = msg.getHeader().getField("in-reply-to"); //NON-NLS
189  String inReplyTo = null;
190 
191  if (field != null) {
192  inReplyTo = field.getBody();
193  email.setInReplyToID(inReplyTo);
194  }
195 
196  field = msg.getHeader().getField("references");
197  if (field != null) {
198  List<String> references = new ArrayList<>();
199  for (String id : field.getBody().split(">")) {
200  references.add(id.trim() + ">");
201  }
202 
203  if (!references.contains(inReplyTo)) {
204  references.add(inReplyTo);
205  }
206 
207  email.setReferences(references);
208  }
209 
210  return email;
211  }
212 
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());
233  } else {
234  // Ignore other types.
235  }
236  }
237  }
238 
249  private void handleTextBody(EmailMessage email, TextBody tb, String type, List<Field> fields) {
250  BufferedReader r;
251  try {
252  r = new BufferedReader(tb.getReader());
253  StringBuilder bodyString = new StringBuilder();
254  StringBuilder headersString = new StringBuilder();
255  String line;
256  while ((line = r.readLine()) != null) {
257  bodyString.append(line).append("\n");
258  }
259 
260  headersString.append("\n-----HEADERS-----\n");
261  for (Field field : fields) {
262  String nextLine = field.getName() + ": " + field.getBody();
263  headersString.append("\n").append(nextLine);
264  }
265  headersString.append("\n\n---END HEADERS--\n\n");
266 
267  email.setHeaders(headersString.toString());
268 
269  switch (type) {
270  case ContentTypeField.TYPE_TEXT_PLAIN:
271  email.setTextBody(bodyString.toString());
272  break;
273  case HTML_TYPE:
274  email.setHtmlBody(bodyString.toString());
275  break;
276  default:
277  // Not interested in other text types.
278  break;
279  }
280  } catch (IOException ex) {
281  logger.log(Level.WARNING, "Error getting text body of mbox message", ex); //NON-NLS
282  }
283  }
284 
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;
296  try {
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); //NON-NLS
301  return;
302  }
303  String filename = e.getFilename();
304 
305  if (filename == null) {
306  filename = "attachment" + e.hashCode();
307  logger.log(Level.WARNING, String.format("Attachment has no file name using '%s'", filename));
308  }
309 
310  filename = FileUtil.escapeFileName(filename);
311 
312  // also had some crazy long names, so make random one if we get those.
313  // also from Japanese image that had encoded name
314  if (filename.length() > 64) {
315  filename = UUID.randomUUID().toString();
316  }
317 
318  String uniqueFilename = fileID + "-" + index + "-" + email.getSentDate() + "-" + filename;
319  String outPath = outputDirPath + uniqueFilename;
320 
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); //NON-NLS
327  return;
328  }
329 
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);
336  }
337 
338 
339  }
340 
350  private static String getAddresses(MailboxList mailboxList) {
351  if (mailboxList == null) {
352  return "";
353  }
354  StringBuilder addresses = new StringBuilder();
355  for (Mailbox m : mailboxList) {
356  addresses.append(m.toString()).append("; ");
357  }
358  return addresses.toString();
359  }
360 
370  private static String getAddresses(AddressList addressList) {
371  return (addressList == null) ? "" : getAddresses(addressList.flatten());
372  }
373 }

Copyright © 2012-2020 Basis Technology. Generated on: Wed Apr 8 2020
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.