19 package org.sleuthkit.autopsy.thunderbirdparser;
21 import java.io.BufferedReader;
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;
53 class MimeJ4MessageParser {
55 private static final Logger logger = Logger.getLogger(MimeJ4MessageParser.class.getName());
60 private static final String HTML_TYPE =
"text/html";
61 private DefaultMessageBuilder messageBuilder = null;
62 private final List<String> errorList =
new ArrayList<>();
67 private String localPath;
69 DefaultMessageBuilder getMessageBuilder() {
70 if (messageBuilder == null) {
71 messageBuilder =
new DefaultMessageBuilder();
72 MimeConfig config = MimeConfig.custom().setMaxLineLen(-1).setMaxHeaderLen(-1).setMaxHeaderCount(-1).build();
74 messageBuilder.setMimeEntityConfig(config);
77 return messageBuilder;
85 final void setLocalPath(String localPath) {
86 this.localPath = localPath;
94 String getLocalPath() {
106 for (String msg : errorList) {
107 result +=
"<li>" + msg +
"</li>";
117 void addErrorMessage(String msg) {
129 EmailMessage extractEmail(Message msg, String localPath,
long sourceFileID) {
130 EmailMessage email =
new EmailMessage();
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());
141 Field field = msg.getHeader().getField(
"in-reply-to");
142 String inReplyTo = null;
145 inReplyTo = field.getBody();
146 email.setInReplyToID(inReplyTo);
149 field = msg.getHeader().getField(
"references");
151 List<String> references =
new ArrayList<>();
152 for (String
id : field.getBody().split(
">")) {
153 references.add(
id.trim() +
">");
156 if (!references.contains(inReplyTo)) {
157 references.add(inReplyTo);
160 email.setReferences(references);
164 if (msg.isMultipart()) {
165 handleMultipart(email, (Multipart) msg.getBody(), sourceFileID);
167 handleTextBody(email, (TextBody) msg.getBody(), msg.getMimeType(), msg.getHeader().getFields());
181 EmailMessage extractPartialEmail(Message msg) {
182 EmailMessage email =
new EmailMessage();
183 email.setSubject(msg.getSubject());
184 email.setMessageID(msg.getMessageId());
186 Field field = msg.getHeader().getField(
"in-reply-to");
187 String inReplyTo = null;
190 inReplyTo = field.getBody();
191 email.setInReplyToID(inReplyTo);
194 field = msg.getHeader().getField(
"references");
196 List<String> references =
new ArrayList<>();
197 for (String
id : field.getBody().split(
">")) {
198 references.add(
id.trim() +
">");
201 if (!references.contains(inReplyTo)) {
202 references.add(inReplyTo);
205 email.setReferences(references);
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());
232 handleAttachment(email, e, fileID, index);
247 private void handleTextBody(EmailMessage email, TextBody tb, String type, List<Field> fields) {
250 r =
new BufferedReader(tb.getReader());
251 StringBuilder bodyString =
new StringBuilder();
252 StringBuilder headersString =
new StringBuilder();
254 while ((line = r.readLine()) != null) {
255 bodyString.append(line).append(
"\n");
258 headersString.append(
"\n-----HEADERS-----\n");
259 for (Field field : fields) {
260 String nextLine = field.getName() +
": " + field.getBody();
261 headersString.append(
"\n").append(nextLine);
263 headersString.append(
"\n\n---END HEADERS--\n\n");
265 email.setHeaders(headersString.toString());
268 case ContentTypeField.TYPE_TEXT_PLAIN:
269 email.setTextBody(bodyString.toString());
272 email.setHtmlBody(bodyString.toString());
278 }
catch (IOException ex) {
279 logger.log(Level.WARNING,
"Error getting text body of mbox message", ex);
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;
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);
301 String filename = e.getFilename();
303 if (filename == null) {
304 filename =
"attachment" + e.hashCode();
305 logger.log(Level.WARNING, String.format(
"Attachment has no file name using '%s'", filename));
308 filename = FileUtil.escapeFileName(filename);
312 if (filename.length() > 64) {
313 filename = UUID.randomUUID().toString();
316 String uniqueFilename = fileID +
"-" + index +
"-" + email.getSentDate() +
"-" + filename;
317 String outPath = outputDirPath + uniqueFilename;
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);
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);
348 private static String getAddresses(MailboxList mailboxList) {
349 if (mailboxList == null) {
352 StringBuilder addresses =
new StringBuilder();
353 for (Mailbox m : mailboxList) {
354 addresses.append(m.toString()).append(
"; ");
356 return addresses.toString();
368 private static String getAddresses(AddressList addressList) {
369 return (addressList == null) ?
"" : getAddresses(addressList.flatten());