Autopsy 4.22.1
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 */
19package org.sleuthkit.autopsy.thunderbirdparser;
20
21import java.io.BufferedReader;
22import java.io.File;
23import java.io.FileOutputStream;
24import java.io.IOException;
25import java.util.ArrayList;
26import java.util.List;
27import java.util.UUID;
28import java.util.logging.Level;
29import org.apache.james.mime4j.dom.Body;
30import org.apache.james.mime4j.dom.Entity;
31import org.apache.james.mime4j.dom.Message;
32import org.apache.james.mime4j.dom.MessageWriter;
33import org.apache.james.mime4j.dom.Multipart;
34import org.apache.james.mime4j.dom.TextBody;
35import org.apache.james.mime4j.dom.address.AddressList;
36import org.apache.james.mime4j.dom.address.Mailbox;
37import org.apache.james.mime4j.dom.address.MailboxList;
38import org.apache.james.mime4j.dom.field.ContentDispositionField;
39import org.apache.james.mime4j.dom.field.ContentTypeField;
40import org.apache.james.mime4j.message.DefaultMessageBuilder;
41import org.apache.james.mime4j.message.DefaultMessageWriter;
42import org.apache.james.mime4j.stream.Field;
43import org.apache.james.mime4j.stream.MimeConfig;
44import org.openide.util.NbBundle;
45import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
46import org.sleuthkit.autopsy.coreutils.FileUtil;
47import org.sleuthkit.autopsy.coreutils.Logger;
48import org.sleuthkit.datamodel.EncodedFileOutputStream;
49import org.sleuthkit.datamodel.TskData;
50
54class 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-2024 Sleuth Kit Labs. Generated on:
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.