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.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;
54 class MimeJ4MessageParser
implements AutoCloseable{
56 private static final Logger logger = Logger.getLogger(MimeJ4MessageParser.class.getName());
61 private static final String HTML_TYPE =
"text/html";
62 private DefaultMessageBuilder messageBuilder = null;
63 private final List<String> errorList =
new ArrayList<>();
68 private String localPath;
70 DefaultMessageBuilder getMessageBuilder() {
71 if (messageBuilder == null) {
72 messageBuilder =
new DefaultMessageBuilder();
73 MimeConfig config = MimeConfig.custom().setMaxLineLen(-1).setMaxHeaderLen(-1).setMaxHeaderCount(-1).build();
75 messageBuilder.setMimeEntityConfig(config);
78 return messageBuilder;
86 final void setLocalPath(String localPath) {
87 this.localPath = localPath;
95 String getLocalPath() {
107 for (String msg : errorList) {
108 result +=
"<li>" + msg +
"</li>";
118 void addErrorMessage(String msg) {
130 EmailMessage extractEmail(Message msg, String localPath,
long sourceFileID) {
131 EmailMessage email =
new EmailMessage();
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());
142 Field field = msg.getHeader().getField(
"in-reply-to");
143 String inReplyTo = null;
146 inReplyTo = field.getBody();
147 email.setInReplyToID(inReplyTo);
150 field = msg.getHeader().getField(
"references");
152 List<String> references =
new ArrayList<>();
153 for (String
id : field.getBody().split(
">")) {
154 references.add(
id.trim() +
">");
157 if (!references.contains(inReplyTo)) {
158 references.add(inReplyTo);
161 email.setReferences(references);
165 if (msg.isMultipart()) {
166 handleMultipart(email, (Multipart) msg.getBody(), sourceFileID);
168 if(msg.getBody() instanceof TextBody) {
169 handleTextBody(email, (TextBody) msg.getBody(), msg.getMimeType(), msg.getHeader().getFields());
171 handleAttachment(email, msg, sourceFileID, 1);
186 EmailMessage extractPartialEmail(Message msg) {
187 EmailMessage email =
new EmailMessage();
188 email.setSubject(msg.getSubject());
189 email.setMessageID(msg.getMessageId());
191 Field field = msg.getHeader().getField(
"in-reply-to");
192 String inReplyTo = null;
195 inReplyTo = field.getBody();
196 email.setInReplyToID(inReplyTo);
199 field = msg.getHeader().getField(
"references");
201 List<String> references =
new ArrayList<>();
202 for (String
id : field.getBody().split(
">")) {
203 references.add(
id.trim() +
">");
206 if (!references.contains(inReplyTo)) {
207 references.add(inReplyTo);
210 email.setReferences(references);
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());
237 handleAttachment(email, e, fileID, index);
252 private void handleTextBody(EmailMessage email, TextBody tb, String type, List<Field> fields) {
255 r =
new BufferedReader(tb.getReader());
256 StringBuilder bodyString =
new StringBuilder();
257 StringBuilder headersString =
new StringBuilder();
259 while ((line = r.readLine()) != null) {
260 bodyString.append(line).append(
"\n");
263 headersString.append(
"\n-----HEADERS-----\n");
264 for (Field field : fields) {
265 String nextLine = field.getName() +
": " + field.getBody();
266 headersString.append(
"\n").append(nextLine);
268 headersString.append(
"\n\n---END HEADERS--\n\n");
270 email.setHeaders(headersString.toString());
273 case ContentTypeField.TYPE_TEXT_PLAIN:
274 email.setTextBody(bodyString.toString());
277 email.setHtmlBody(bodyString.toString());
283 }
catch (IOException ex) {
284 logger.log(Level.WARNING,
"Error getting text body of mbox message", ex);
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;
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);
306 String filename = e.getFilename();
308 if (filename == null) {
309 filename =
"attachment" + e.hashCode();
310 logger.log(Level.WARNING, String.format(
"Attachment has no file name using '%s'", filename));
313 filename = FileUtil.escapeFileName(filename);
317 if (filename.length() > 64) {
318 filename = UUID.randomUUID().toString();
321 String uniqueFilename = fileID +
"-" + index +
"-" + email.getSentDate() +
"-" + filename;
322 String outPath = outputDirPath + uniqueFilename;
324 Body body = e.getBody();
327 try (EncodedFileOutputStream fos =
new EncodedFileOutputStream(
new FileOutputStream(outPath), TskData.EncodingType.XOR1)) {
329 EmailMessage.Attachment attach;
330 MessageWriter msgWriter =
new DefaultMessageWriter();
332 if(body instanceof Message) {
333 msgWriter.writeMessage((Message)body, fos);
334 attach =
new EmailMessage.AttachedEmailMessage(extractEmail((Message)body, email.getLocalPath(), fileID));
336 msgWriter.writeBody(body, fos);
337 attach =
new EmailMessage.Attachment();
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);
346 }
catch (IOException ex) {
347 logger.log(Level.WARNING,
"Failed to create file output stream for: " + outPath, ex);
361 private static String getAddresses(MailboxList mailboxList) {
362 if (mailboxList == null) {
365 StringBuilder addresses =
new StringBuilder();
366 for (Mailbox m : mailboxList) {
367 addresses.append(m.toString()).append(
"; ");
369 return addresses.toString();
381 private static String getAddresses(AddressList addressList) {
382 return (addressList == null) ?
"" : getAddresses(addressList.flatten());
386 public void close() throws IOException{