19 package org.sleuthkit.autopsy.thunderbirdparser;
21 import com.pff.PSTAttachment;
22 import com.pff.PSTException;
23 import com.pff.PSTFile;
24 import com.pff.PSTFolder;
25 import com.pff.PSTMessage;
27 import java.io.FileOutputStream;
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.nio.ByteBuffer;
31 import java.util.ArrayList;
32 import java.util.List;
33 import java.util.Scanner;
34 import java.util.logging.Level;
36 import org.openide.util.NbBundle;
53 private static final Logger logger = Logger.getLogger(PstParser.class.getName());
57 private static int PST_HEADER = 0x2142444E;
58 private IngestServices services;
63 private List<EmailMessage> results;
64 private StringBuilder errors;
66 PstParser(IngestServices services) {
67 results =
new ArrayList<>();
68 this.services = services;
69 errors =
new StringBuilder();
85 ParseResult parse(File file,
long fileID) {
89 pstFile =
new PSTFile(file);
90 failures = processFolder(pstFile.getRootFolder(),
"\\",
true, fileID);
93 NbBundle.getMessage(
this.getClass(),
"PstParser.parse.errMsg.failedToParseNMsgs", failures));
95 return ParseResult.OK;
96 }
catch (PSTException | IOException ex) {
97 String msg = file.getName() +
": Failed to create internal java-libpst PST file to parse:\n" + ex.getMessage();
98 logger.log(Level.WARNING, msg);
99 return ParseResult.ERROR;
100 }
catch (IllegalArgumentException ex) {
101 logger.log(Level.INFO,
"Found encrypted PST file.");
102 return ParseResult.ENCRYPT;
111 List<EmailMessage> getResults() {
116 return errors.toString();
131 private long processFolder(PSTFolder folder, String path,
boolean root,
long fileID) {
132 String newPath = (root ? path : path +
"\\" + folder.getDisplayName());
134 if (folder.hasSubfolders()) {
135 List<PSTFolder> subFolders;
137 subFolders = folder.getSubFolders();
138 }
catch (PSTException | IOException ex) {
139 subFolders =
new ArrayList<>();
140 logger.log(Level.INFO,
"java-libpst exception while getting subfolders: {0}", ex.getMessage());
143 for (PSTFolder f : subFolders) {
144 failCount += processFolder(f, newPath,
false, fileID);
148 if (folder.getContentCount() != 0) {
152 while ((email = (PSTMessage) folder.getNextChild()) != null) {
153 results.add(extractEmailMessage(email, newPath, fileID));
155 }
catch (PSTException | IOException ex) {
157 logger.log(Level.INFO,
"java-libpst exception while getting emails from a folder: {0}", ex.getMessage());
172 private EmailMessage extractEmailMessage(PSTMessage msg, String localPath,
long fileID) {
173 EmailMessage email =
new EmailMessage();
174 email.setRecipients(msg.getDisplayTo());
175 email.setCc(msg.getDisplayCC());
176 email.setBcc(msg.getDisplayBCC());
177 email.setSender(getSender(msg.getSenderName(), msg.getSenderEmailAddress()));
178 email.setSentDate(msg.getMessageDeliveryTime());
179 email.setTextBody(msg.getBody());
180 if(
false == msg.getTransportMessageHeaders().isEmpty()) {
181 email.setHeaders(
"\n-----HEADERS-----\n\n" + msg.getTransportMessageHeaders() +
"\n\n---END HEADERS--\n\n");
184 email.setHtmlBody(msg.getBodyHTML());
187 rtf = msg.getRTFBody();
188 }
catch (PSTException | IOException ex) {
189 logger.log(Level.INFO,
"Failed to get RTF content from pst email.");
191 email.setRtfBody(rtf);
192 email.setLocalPath(localPath);
193 email.setSubject(msg.getSubject());
194 email.setId(msg.getDescriptorNodeId());
195 email.setMessageID(msg.getInternetMessageId());
197 String inReplyToID = msg.getInReplyToId();
198 email.setInReplyToID(inReplyToID);
200 if (msg.hasAttachments()) {
201 extractAttachments(email, msg, fileID);
204 List<String> references = extractReferences(msg.getTransportMessageHeaders());
205 if (inReplyToID != null && !inReplyToID.isEmpty()) {
206 if (references == null) {
207 references =
new ArrayList<>();
208 references.add(inReplyToID);
209 }
else if (!references.contains(inReplyToID)) {
210 references.add(inReplyToID);
213 email.setReferences(references);
224 @NbBundle.Messages({
"PstParser.noOpenCase.errMsg=Exception while getting open case."})
225 private void extractAttachments(EmailMessage email, PSTMessage msg,
long fileID) {
226 int numberOfAttachments = msg.getNumberOfAttachments();
227 String outputDirPath;
229 outputDirPath = ThunderbirdMboxFileIngestModule.getModuleOutputPath() + File.separator;
230 }
catch (NoCurrentCaseException ex) {
231 logger.log(Level.SEVERE,
"Exception while getting open case.", ex);
234 for (
int x = 0; x < numberOfAttachments; x++) {
235 String filename =
"";
237 PSTAttachment attach = msg.getAttachment(x);
238 long size = attach.getAttachSize();
239 long freeSpace = services.getFreeDiskSpace();
240 if ((freeSpace != IngestMonitor.DISK_FREE_SPACE_UNKNOWN) && (size >= freeSpace)) {
244 filename = attach.getLongFilename();
245 if (filename.isEmpty()) {
246 filename = attach.getFilename();
248 String uniqueFilename = fileID +
"-" + msg.getDescriptorNodeId() +
"-" + attach.getContentId() +
"-" + filename;
249 String outPath = outputDirPath + uniqueFilename;
250 saveAttachmentToDisk(attach, outPath);
252 EmailMessage.Attachment attachment =
new EmailMessage.Attachment();
254 long crTime = attach.getCreationTime() != null ? attach.getCreationTime().getTime() / 1000 : 0;
255 long mTime = attach.getModificationTime() != null ? attach.getModificationTime().getTime() / 1000 : 0;
256 String relPath = getRelModuleOutputPath() + File.separator + uniqueFilename;
257 attachment.setName(filename);
258 attachment.setCrTime(crTime);
259 attachment.setmTime(mTime);
260 attachment.setLocalPath(relPath);
261 attachment.setSize(attach.getFilesize());
262 attachment.setEncodingType(TskData.EncodingType.XOR1);
263 email.addAttachment(attachment);
264 }
catch (PSTException | IOException | NullPointerException ex) {
270 NbBundle.getMessage(
this.getClass(),
"PstParser.extractAttch.errMsg.failedToExtractToDisk",
272 logger.log(Level.WARNING,
"Failed to extract attachment from pst file.", ex);
273 }
catch (NoCurrentCaseException ex) {
274 addErrorMessage(Bundle.PstParser_noOpenCase_errMsg());
275 logger.log(Level.SEVERE, Bundle.PstParser_noOpenCase_errMsg(), ex);
291 private void saveAttachmentToDisk(PSTAttachment attach, String outPath)
throws IOException, PSTException {
292 try (InputStream attachmentStream = attach.getFileInputStream();
293 EncodedFileOutputStream out =
new EncodedFileOutputStream(
new FileOutputStream(outPath), TskData.EncodingType.XOR1)) {
295 int bufferSize = 8176;
296 byte[] buffer =
new byte[bufferSize];
297 int count = attachmentStream.read(buffer);
300 throw new IOException(
"attachmentStream invalid (read() fails). File " + attach.getLongFilename() +
" skipped");
303 while (count == bufferSize) {
305 count = attachmentStream.read(buffer);
308 byte[] endBuffer =
new byte[count];
309 System.arraycopy(buffer, 0, endBuffer, 0, count);
310 out.write(endBuffer);
323 private String getSender(String name, String addr) {
324 if (name.isEmpty() && addr.isEmpty()) {
326 }
else if (name.isEmpty()) {
328 }
else if (addr.isEmpty()) {
331 return name +
": " + addr;
342 public static boolean isPstFile(AbstractFile file) {
343 byte[] buffer =
new byte[4];
345 int read = file.read(buffer, 0, 4);
349 ByteBuffer bb = ByteBuffer.wrap(buffer);
350 return bb.getInt() == PST_HEADER;
351 }
catch (TskCoreException ex) {
352 logger.log(Level.WARNING,
"Exception while detecting if a file is a pst file.");
357 private void addErrorMessage(String msg) {
358 errors.append(
"<li>").append(msg).append(
"</li>");
368 private List<String> extractReferences(String emailHeader) {
369 Scanner scanner =
new Scanner(emailHeader);
370 StringBuilder buffer = null;
371 while (scanner.hasNextLine()) {
372 String token = scanner.nextLine();
374 if (token.matches(
"^References:.*")) {
375 buffer =
new StringBuilder();
376 buffer.append((token.substring(token.indexOf(
':') + 1)).trim());
377 }
else if (buffer != null) {
378 if (token.matches(
"^\\w+:.*$")) {
379 List<String> references =
new ArrayList<>();
380 for (String
id : buffer.toString().split(
">")) {
381 references.add(
id.trim() +
">");
385 buffer.append(token.trim());