Autopsy  4.18.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-2020 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.MessageWriter;
33 import org.apache.james.mime4j.dom.Multipart;
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.message.DefaultMessageWriter;
42 import org.apache.james.mime4j.stream.Field;
43 import org.apache.james.mime4j.stream.MimeConfig;
44 import org.openide.util.NbBundle;
48 import org.sleuthkit.datamodel.EncodedFileOutputStream;
49 import org.sleuthkit.datamodel.TskData;
50 
54 class MimeJ4MessageParser implements AutoCloseable{
55 
56  private static final Logger logger = Logger.getLogger(MimeJ4MessageParser.class.getName());
57 
61  private static final String HTML_TYPE = "text/html"; //NON-NLS
62  private DefaultMessageBuilder messageBuilder = null;
63  private final List<String> errorList = new ArrayList<>();
64 
68  private String localPath;
69 
70  DefaultMessageBuilder getMessageBuilder() {
71  if (messageBuilder == null) {
72  messageBuilder = new DefaultMessageBuilder();
73  MimeConfig config = MimeConfig.custom().setMaxLineLen(-1).setMaxHeaderLen(-1).setMaxHeaderCount(-1).build();
74  // disable line length checks.
75  messageBuilder.setMimeEntityConfig(config);
76  }
77 
78  return messageBuilder;
79  }
80 
86  final void setLocalPath(String localPath) {
87  this.localPath = localPath;
88  }
89 
95  String getLocalPath() {
96  return localPath;
97  }
98 
105  String getErrors() {
106  String result = "";
107  for (String msg : errorList) {
108  result += "<li>" + msg + "</li>";
109  }
110  return result;
111  }
112 
118  void addErrorMessage(String msg) {
119  errorList.add(msg);
120  }
121 
130  EmailMessage extractEmail(Message msg, String localPath, long sourceFileID) {
131  EmailMessage email = new EmailMessage();
132  // Basic Info
133  email.setSender(getAddresses(msg.getFrom()));
134  email.setRecipients(getAddresses(msg.getTo()));
135  email.setBcc(getAddresses(msg.getBcc()));
136  email.setCc(getAddresses(msg.getCc()));
137  email.setSubject(msg.getSubject());
138  email.setSentDate(msg.getDate());
139  email.setLocalPath(localPath);
140  email.setMessageID(msg.getMessageId());
141 
142  Field field = msg.getHeader().getField("in-reply-to"); //NON-NLS
143  String inReplyTo = null;
144 
145  if (field != null) {
146  inReplyTo = field.getBody();
147  email.setInReplyToID(inReplyTo);
148  }
149 
150  field = msg.getHeader().getField("references");
151  if (field != null) {
152  List<String> references = new ArrayList<>();
153  for (String id : field.getBody().split(">")) {
154  references.add(id.trim() + ">");
155  }
156 
157  if (!references.contains(inReplyTo)) {
158  references.add(inReplyTo);
159  }
160 
161  email.setReferences(references);
162  }
163 
164  // Body
165  if (msg.isMultipart()) {
166  handleMultipart(email, (Multipart) msg.getBody(), sourceFileID);
167  } else {
168  if(msg.getBody() instanceof TextBody) {
169  handleTextBody(email, (TextBody) msg.getBody(), msg.getMimeType(), msg.getHeader().getFields());
170  } else {
171  handleAttachment(email, msg, sourceFileID, 1);
172  }
173  }
174 
175  return email;
176  }
177 
186  EmailMessage extractPartialEmail(Message msg) {
187  EmailMessage email = new EmailMessage();
188  email.setSubject(msg.getSubject());
189  email.setMessageID(msg.getMessageId());
190 
191  Field field = msg.getHeader().getField("in-reply-to"); //NON-NLS
192  String inReplyTo = null;
193 
194  if (field != null) {
195  inReplyTo = field.getBody();
196  email.setInReplyToID(inReplyTo);
197  }
198 
199  field = msg.getHeader().getField("references");
200  if (field != null) {
201  List<String> references = new ArrayList<>();
202  for (String id : field.getBody().split(">")) {
203  references.add(id.trim() + ">");
204  }
205 
206  if (!references.contains(inReplyTo)) {
207  references.add(inReplyTo);
208  }
209 
210  email.setReferences(references);
211  }
212 
213  return email;
214  }
215 
224  private void handleMultipart(EmailMessage email, Multipart multi, long fileID) {
225  List<Entity> entities = multi.getBodyParts();
226  for (int index = 0; index < entities.size(); index++) {
227  Entity e = entities.get(index);
228  if (e.isMultipart()) {
229  handleMultipart(email, (Multipart) e.getBody(), fileID);
230  } else if (e.getDispositionType() != null
231  && e.getDispositionType().equals(ContentDispositionField.DISPOSITION_TYPE_ATTACHMENT)) {
232  handleAttachment(email, e, fileID, index);
233  } else if ((e.getMimeType().equals(HTML_TYPE) && (email.getHtmlBody() == null || email.getHtmlBody().isEmpty()))
234  || (e.getMimeType().equals(ContentTypeField.TYPE_TEXT_PLAIN) && (email.getTextBody() == null || email.getTextBody().isEmpty()))) {
235  handleTextBody(email, (TextBody) e.getBody(), e.getMimeType(), e.getHeader().getFields());
236  } else {
237  handleAttachment(email, e, fileID, index);
238  }
239  }
240  }
241 
252  private void handleTextBody(EmailMessage email, TextBody tb, String type, List<Field> fields) {
253  BufferedReader r;
254  try {
255  r = new BufferedReader(tb.getReader());
256  StringBuilder bodyString = new StringBuilder();
257  StringBuilder headersString = new StringBuilder();
258  String line;
259  while ((line = r.readLine()) != null) {
260  bodyString.append(line).append("\n");
261  }
262 
263  headersString.append("\n-----HEADERS-----\n");
264  for (Field field : fields) {
265  String nextLine = field.getName() + ": " + field.getBody();
266  headersString.append("\n").append(nextLine);
267  }
268  headersString.append("\n\n---END HEADERS--\n\n");
269 
270  email.setHeaders(headersString.toString());
271 
272  switch (type) {
273  case ContentTypeField.TYPE_TEXT_PLAIN:
274  email.setTextBody(bodyString.toString());
275  break;
276  case HTML_TYPE:
277  email.setHtmlBody(bodyString.toString());
278  break;
279  default:
280  // Not interested in other text types.
281  break;
282  }
283  } catch (IOException ex) {
284  logger.log(Level.WARNING, "Error getting text body of mbox message", ex); //NON-NLS
285  }
286  }
287 
295  @NbBundle.Messages({"MimeJ4MessageParser.handleAttch.noOpenCase.errMsg=Exception while getting open case."})
296  private void handleAttachment(EmailMessage email, Entity e, long fileID, int index) {
297  String outputDirPath;
298  String relModuleOutputPath;
299  try {
300  outputDirPath = ThunderbirdMboxFileIngestModule.getModuleOutputPath() + File.separator;
301  relModuleOutputPath = ThunderbirdMboxFileIngestModule.getRelModuleOutputPath() + File.separator;
302  } catch (NoCurrentCaseException ex) {
303  logger.log(Level.SEVERE, Bundle.MimeJ4MessageParser_handleAttch_noOpenCase_errMsg(), ex); //NON-NLS
304  return;
305  }
306  String filename = e.getFilename();
307 
308  if (filename == null) {
309  filename = "attachment" + e.hashCode();
310  logger.log(Level.WARNING, String.format("Attachment has no file name using '%s'", filename));
311  }
312 
313  filename = FileUtil.escapeFileName(filename);
314 
315  // also had some crazy long names, so make random one if we get those.
316  // also from Japanese image that had encoded name
317  if (filename.length() > 64) {
318  filename = UUID.randomUUID().toString();
319  }
320 
321  String uniqueFilename = fileID + "-" + index + "-" + email.getSentDate() + "-" + filename;
322  String outPath = outputDirPath + uniqueFilename;
323 
324  Body body = e.getBody();
325  if (body != null) {
326  long fileLength;
327  try (EncodedFileOutputStream fos = new EncodedFileOutputStream(new FileOutputStream(outPath), TskData.EncodingType.XOR1)) {
328 
329  EmailMessage.Attachment attach;
330  MessageWriter msgWriter = new DefaultMessageWriter();
331 
332  if(body instanceof Message) {
333  msgWriter.writeMessage((Message)body, fos);
334  attach = new EmailMessage.AttachedEmailMessage(extractEmail((Message)body, email.getLocalPath(), fileID));
335  } else {
336  msgWriter.writeBody(body, fos);
337  attach = new EmailMessage.Attachment();
338  }
339  fileLength = fos.getBytesWritten();
340  attach.setName(filename);
341  attach.setLocalPath(relModuleOutputPath + uniqueFilename);
342  attach.setSize(fileLength);
343  attach.setEncodingType(TskData.EncodingType.XOR1);
344  email.addAttachment(attach);
345 
346  } catch (IOException ex) {
347  logger.log(Level.WARNING, "Failed to create file output stream for: " + outPath, ex); //NON-NLS
348  }
349  }
350  }
351 
361  private static String getAddresses(MailboxList mailboxList) {
362  if (mailboxList == null) {
363  return "";
364  }
365  StringBuilder addresses = new StringBuilder();
366  for (Mailbox m : mailboxList) {
367  addresses.append(m.toString()).append("; ");
368  }
369  return addresses.toString();
370  }
371 
381  private static String getAddresses(AddressList addressList) {
382  return (addressList == null) ? "" : getAddresses(addressList.flatten());
383  }
384 
385  @Override
386  public void close() throws IOException{
387 
388  }
389 }

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