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.logging.Level;
35 import org.openide.util.NbBundle;
49 private static final Logger logger = Logger.getLogger(PstParser.class.getName());
53 private static int PST_HEADER = 0x2142444E;
54 private IngestServices services;
59 private List<EmailMessage> results;
60 private StringBuilder errors;
62 PstParser(IngestServices services) {
63 results =
new ArrayList<>();
64 this.services = services;
65 errors =
new StringBuilder();
81 ParseResult parse(File file,
long fileID) {
85 pstFile =
new PSTFile(file);
86 failures = processFolder(pstFile.getRootFolder(),
"\\",
true, fileID);
89 NbBundle.getMessage(
this.getClass(),
"PstParser.parse.errMsg.failedToParseNMsgs", failures));
91 return ParseResult.OK;
92 }
catch (PSTException | IOException ex) {
93 String msg = file.getName() +
": Failed to create internal java-libpst PST file to parse:\n" + ex.getMessage();
94 logger.log(Level.WARNING, msg);
95 return ParseResult.ERROR;
96 }
catch (IllegalArgumentException ex) {
97 logger.log(Level.INFO,
"Found encrypted PST file.");
98 return ParseResult.ENCRYPT;
107 List<EmailMessage> getResults() {
112 return errors.toString();
127 private long processFolder(PSTFolder folder, String path,
boolean root,
long fileID) {
128 String newPath = (root ? path : path +
"\\" + folder.getDisplayName());
130 if (folder.hasSubfolders()) {
131 List<PSTFolder> subFolders;
133 subFolders = folder.getSubFolders();
134 }
catch (PSTException | IOException ex) {
135 subFolders =
new ArrayList<>();
136 logger.log(Level.INFO,
"java-libpst exception while getting subfolders: {0}", ex.getMessage());
139 for (PSTFolder f : subFolders) {
140 failCount += processFolder(f, newPath,
false, fileID);
144 if (folder.getContentCount() != 0) {
148 while ((email = (PSTMessage) folder.getNextChild()) != null) {
149 results.add(extractEmailMessage(email, newPath, fileID));
151 }
catch (PSTException | IOException ex) {
153 logger.log(Level.INFO,
"java-libpst exception while getting emails from a folder: {0}", ex.getMessage());
168 private EmailMessage extractEmailMessage(PSTMessage msg, String localPath,
long fileID) {
169 EmailMessage email =
new EmailMessage();
170 email.setRecipients(msg.getDisplayTo());
171 email.setCc(msg.getDisplayCC());
172 email.setBcc(msg.getDisplayBCC());
173 email.setSender(getSender(msg.getSenderName(), msg.getSenderEmailAddress()));
174 email.setSentDate(msg.getMessageDeliveryTime());
175 if(msg.getTransportMessageHeaders().isEmpty()) {
176 email.setTextBody(msg.getBody());
178 email.setTextBody(msg.getBody() +
"\n-----HEADERS-----\n\n" + msg.getTransportMessageHeaders() +
"\n\n---END HEADERS--\n\n");
181 email.setHtmlBody(msg.getBodyHTML());
184 rtf = msg.getRTFBody();
185 }
catch (PSTException | IOException ex) {
186 logger.log(Level.INFO,
"Failed to get RTF content from pst email.");
188 email.setRtfBody(rtf);
189 email.setLocalPath(localPath);
190 email.setSubject(msg.getSubject());
191 email.setId(msg.getDescriptorNodeId());
193 if (msg.hasAttachments()) {
194 extractAttachments(email, msg, fileID);
206 private void extractAttachments(EmailMessage email, PSTMessage msg,
long fileID) {
207 int numberOfAttachments = msg.getNumberOfAttachments();
208 String outputDirPath = ThunderbirdMboxFileIngestModule.getModuleOutputPath() + File.separator;
209 for (
int x = 0; x < numberOfAttachments; x++) {
210 String filename =
"";
212 PSTAttachment attach = msg.getAttachment(x);
213 long size = attach.getAttachSize();
214 long freeSpace = services.getFreeDiskSpace();
215 if ((freeSpace != IngestMonitor.DISK_FREE_SPACE_UNKNOWN) && (size >= freeSpace)) {
219 filename = attach.getLongFilename();
220 if (filename.isEmpty()) {
221 filename = attach.getFilename();
223 String uniqueFilename = fileID +
"-" + msg.getDescriptorNodeId() +
"-" + attach.getContentId() +
"-" + filename;
224 String outPath = outputDirPath + uniqueFilename;
225 saveAttachmentToDisk(attach, outPath);
227 EmailMessage.Attachment attachment =
new EmailMessage.Attachment();
229 long crTime = attach.getCreationTime() != null ? attach.getCreationTime().getTime() / 1000 : 0;
230 long mTime = attach.getModificationTime() != null ? attach.getModificationTime().getTime() / 1000 : 0;
231 String relPath = getRelModuleOutputPath() + File.separator + uniqueFilename;
232 attachment.setName(filename);
233 attachment.setCrTime(crTime);
234 attachment.setmTime(mTime);
235 attachment.setLocalPath(relPath);
236 attachment.setSize(attach.getFilesize());
237 email.addAttachment(attachment);
238 }
catch (PSTException | IOException | NullPointerException ex) {
244 NbBundle.getMessage(
this.getClass(),
"PstParser.extractAttch.errMsg.failedToExtractToDisk",
246 logger.log(Level.WARNING,
"Failed to extract attachment from pst file.", ex);
262 private void saveAttachmentToDisk(PSTAttachment attach, String outPath)
throws IOException, PSTException {
263 try (InputStream attachmentStream = attach.getFileInputStream(); FileOutputStream out =
new FileOutputStream(outPath)) {
265 int bufferSize = 8176;
266 byte[] buffer =
new byte[bufferSize];
267 int count = attachmentStream.read(buffer);
270 throw new IOException(
"attachmentStream invalid (read() fails). File " + attach.getLongFilename() +
" skipped");
273 while (count == bufferSize) {
275 count = attachmentStream.read(buffer);
278 byte[] endBuffer =
new byte[count];
279 System.arraycopy(buffer, 0, endBuffer, 0, count);
280 out.write(endBuffer);
293 private String getSender(String name, String addr) {
294 if (name.isEmpty() && addr.isEmpty()) {
296 }
else if (name.isEmpty()) {
298 }
else if (addr.isEmpty()) {
301 return name +
": " + addr;
312 public static boolean isPstFile(AbstractFile file) {
313 byte[] buffer =
new byte[4];
315 int read = file.read(buffer, 0, 4);
319 ByteBuffer bb = ByteBuffer.wrap(buffer);
320 return bb.getInt() == PST_HEADER;
321 }
catch (TskCoreException ex) {
322 logger.log(Level.WARNING,
"Exception while detecting if a file is a pst file.");
327 private void addErrorMessage(String msg) {
328 errors.append(
"<li>").append(msg).append(
"</li>");
static String getRelModuleOutputPath()