Autopsy  4.16.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.IOException;
25 import java.util.ArrayList;
26 import java.util.List;
27 import java.util.UUID;
28 import java.util.logging.Level;
29 import org.apache.james.mime4j.dom.Body;
30 import org.apache.james.mime4j.dom.Entity;
31 import org.apache.james.mime4j.dom.Message;
32 import org.apache.james.mime4j.dom.Multipart;
33 import org.apache.james.mime4j.dom.SingleBody;
34 import org.apache.james.mime4j.dom.TextBody;
35 import org.apache.james.mime4j.dom.address.AddressList;
36 import org.apache.james.mime4j.dom.address.Mailbox;
37 import org.apache.james.mime4j.dom.address.MailboxList;
38 import org.apache.james.mime4j.dom.field.ContentDispositionField;
39 import org.apache.james.mime4j.dom.field.ContentTypeField;
40 import org.apache.james.mime4j.message.DefaultMessageBuilder;
41 import org.apache.james.mime4j.stream.Field;
42 import org.apache.james.mime4j.stream.MimeConfig;
43 import org.openide.util.NbBundle;
47 import org.sleuthkit.datamodel.EncodedFileOutputStream;
48 import org.sleuthkit.datamodel.TskData;
49 
53 class MimeJ4MessageParser {
54 
55  private static final Logger logger = Logger.getLogger(MimeJ4MessageParser.class.getName());
56 
60  private static final String HTML_TYPE = "text/html"; //NON-NLS
61  private DefaultMessageBuilder messageBuilder = null;
62  private final List<String> errorList = new ArrayList<>();
63 
67  private String localPath;
68 
69  DefaultMessageBuilder getMessageBuilder() {
70  if (messageBuilder == null) {
71  messageBuilder = new DefaultMessageBuilder();
72  MimeConfig config = MimeConfig.custom().setMaxLineLen(-1).setMaxHeaderLen(-1).setMaxHeaderCount(-1).build();
73  // disable line length checks.
74  messageBuilder.setMimeEntityConfig(config);
75  }
76 
77  return messageBuilder;
78  }
79 
85  final void setLocalPath(String localPath) {
86  this.localPath = localPath;
87  }
88 
94  String getLocalPath() {
95  return localPath;
96  }
97 
104  String getErrors() {
105  String result = "";
106  for (String msg : errorList) {
107  result += "<li>" + msg + "</li>";
108  }
109  return result;
110  }
111 
117  void addErrorMessage(String msg) {
118  errorList.add(msg);
119  }
120 
129  EmailMessage extractEmail(Message msg, String localPath, long sourceFileID) {
130  EmailMessage email = new EmailMessage();
131  // Basic Info
132  email.setSender(getAddresses(msg.getFrom()));
133  email.setRecipients(getAddresses(msg.getTo()));
134  email.setBcc(getAddresses(msg.getBcc()));
135  email.setCc(getAddresses(msg.getCc()));
136  email.setSubject(msg.getSubject());
137  email.setSentDate(msg.getDate());
138  email.setLocalPath(localPath);
139  email.setMessageID(msg.getMessageId());
140 
141  Field field = msg.getHeader().getField("in-reply-to"); //NON-NLS
142  String inReplyTo = null;
143 
144  if (field != null) {
145  inReplyTo = field.getBody();
146  email.setInReplyToID(inReplyTo);
147  }
148 
149  field = msg.getHeader().getField("references");
150  if (field != null) {
151  List<String> references = new ArrayList<>();
152  for (String id : field.getBody().split(">")) {
153  references.add(id.trim() + ">");
154  }
155 
156  if (!references.contains(inReplyTo)) {
157  references.add(inReplyTo);
158  }
159 
160  email.setReferences(references);
161  }
162 
163  // Body
164  if (msg.isMultipart()) {
165  handleMultipart(email, (Multipart) msg.getBody(), sourceFileID);
166  } else {
167  handleTextBody(email, (TextBody) msg.getBody(), msg.getMimeType(), msg.getHeader().getFields());
168  }
169 
170  return email;
171  }
172 
181  EmailMessage extractPartialEmail(Message msg) {
182  EmailMessage email = new EmailMessage();
183  email.setSubject(msg.getSubject());
184  email.setMessageID(msg.getMessageId());
185 
186  Field field = msg.getHeader().getField("in-reply-to"); //NON-NLS
187  String inReplyTo = null;
188 
189  if (field != null) {
190  inReplyTo = field.getBody();
191  email.setInReplyToID(inReplyTo);
192  }
193 
194  field = msg.getHeader().getField("references");
195  if (field != null) {
196  List<String> references = new ArrayList<>();
197  for (String id : field.getBody().split(">")) {
198  references.add(id.trim() + ">");
199  }
200 
201  if (!references.contains(inReplyTo)) {
202  references.add(inReplyTo);
203  }
204 
205  email.setReferences(references);
206  }
207 
208  return email;
209  }
210 
219  private void handleMultipart(EmailMessage email, Multipart multi, long fileID) {
220  List<Entity> entities = multi.getBodyParts();
221  for (int index = 0; index < entities.size(); index++) {
222  Entity e = entities.get(index);
223  if (e.isMultipart()) {
224  handleMultipart(email, (Multipart) e.getBody(), fileID);
225  } else if (e.getDispositionType() != null
226  && e.getDispositionType().equals(ContentDispositionField.DISPOSITION_TYPE_ATTACHMENT)) {
227  handleAttachment(email, e, fileID, index);
228  } else if ((e.getMimeType().equals(HTML_TYPE) && (email.getHtmlBody() == null || email.getHtmlBody().isEmpty()))
229  || (e.getMimeType().equals(ContentTypeField.TYPE_TEXT_PLAIN) && (email.getTextBody() == null || email.getTextBody().isEmpty()))) {
230  handleTextBody(email, (TextBody) e.getBody(), e.getMimeType(), e.getHeader().getFields());
231  } else {
232  handleAttachment(email, e, fileID, index);
233  }
234  }
235  }
236 
247  private void handleTextBody(EmailMessage email, TextBody tb, String type, List<Field> fields) {
248  BufferedReader r;
249  try {
250  r = new BufferedReader(tb.getReader());
251  StringBuilder bodyString = new StringBuilder();
252  StringBuilder headersString = new StringBuilder();
253  String line;
254  while ((line = r.readLine()) != null) {
255  bodyString.append(line).append("\n");
256  }
257 
258  headersString.append("\n-----HEADERS-----\n");
259  for (Field field : fields) {
260  String nextLine = field.getName() + ": " + field.getBody();
261  headersString.append("\n").append(nextLine);
262  }
263  headersString.append("\n\n---END HEADERS--\n\n");
264 
265  email.setHeaders(headersString.toString());
266 
267  switch (type) {
268  case ContentTypeField.TYPE_TEXT_PLAIN:
269  email.setTextBody(bodyString.toString());
270  break;
271  case HTML_TYPE:
272  email.setHtmlBody(bodyString.toString());
273  break;
274  default:
275  // Not interested in other text types.
276  break;
277  }
278  } catch (IOException ex) {
279  logger.log(Level.WARNING, "Error getting text body of mbox message", ex); //NON-NLS
280  }
281  }
282 
290  @NbBundle.Messages({"MimeJ4MessageParser.handleAttch.noOpenCase.errMsg=Exception while getting open case."})
291  private static void handleAttachment(EmailMessage email, Entity e, long fileID, int index) {
292  String outputDirPath;
293  String relModuleOutputPath;
294  try {
295  outputDirPath = ThunderbirdMboxFileIngestModule.getModuleOutputPath() + File.separator;
296  relModuleOutputPath = ThunderbirdMboxFileIngestModule.getRelModuleOutputPath() + File.separator;
297  } catch (NoCurrentCaseException ex) {
298  logger.log(Level.SEVERE, Bundle.MimeJ4MessageParser_handleAttch_noOpenCase_errMsg(), ex); //NON-NLS
299  return;
300  }
301  String filename = e.getFilename();
302 
303  if (filename == null) {
304  filename = "attachment" + e.hashCode();
305  logger.log(Level.WARNING, String.format("Attachment has no file name using '%s'", filename));
306  }
307 
308  filename = FileUtil.escapeFileName(filename);
309 
310  // also had some crazy long names, so make random one if we get those.
311  // also from Japanese image that had encoded name
312  if (filename.length() > 64) {
313  filename = UUID.randomUUID().toString();
314  }
315 
316  String uniqueFilename = fileID + "-" + index + "-" + email.getSentDate() + "-" + filename;
317  String outPath = outputDirPath + uniqueFilename;
318 
319  Body body = e.getBody();
320  if (body instanceof SingleBody) {
321  try (EncodedFileOutputStream fos = new EncodedFileOutputStream(new FileOutputStream(outPath), TskData.EncodingType.XOR1)) {
322  ((SingleBody) body).writeTo(fos);
323  } catch (IOException ex) {
324  logger.log(Level.WARNING, "Failed to create file output stream for: " + outPath, ex); //NON-NLS
325  return;
326  }
327 
328  EmailMessage.Attachment attach = new EmailMessage.Attachment();
329  attach.setName(filename);
330  attach.setLocalPath(relModuleOutputPath + uniqueFilename);
331  attach.setSize(new File(outPath).length());
332  attach.setEncodingType(TskData.EncodingType.XOR1);
333  email.addAttachment(attach);
334  }
335 
336 
337  }
338 
348  private static String getAddresses(MailboxList mailboxList) {
349  if (mailboxList == null) {
350  return "";
351  }
352  StringBuilder addresses = new StringBuilder();
353  for (Mailbox m : mailboxList) {
354  addresses.append(m.toString()).append("; ");
355  }
356  return addresses.toString();
357  }
358 
368  private static String getAddresses(AddressList addressList) {
369  return (addressList == null) ? "" : getAddresses(addressList.flatten());
370  }
371 }

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