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;
51 private static final Logger logger = Logger.getLogger(PstParser.class.getName());
55 private static int PST_HEADER = 0x2142444E;
56 private IngestServices services;
61 private List<EmailMessage> results;
62 private StringBuilder errors;
64 PstParser(IngestServices services) {
65 results =
new ArrayList<>();
66 this.services = services;
67 errors =
new StringBuilder();
83 ParseResult parse(File file,
long fileID) {
87 pstFile =
new PSTFile(file);
88 failures = processFolder(pstFile.getRootFolder(),
"\\",
true, fileID);
91 NbBundle.getMessage(
this.getClass(),
"PstParser.parse.errMsg.failedToParseNMsgs", failures));
93 return ParseResult.OK;
94 }
catch (PSTException | IOException ex) {
95 String msg = file.getName() +
": Failed to create internal java-libpst PST file to parse:\n" + ex.getMessage();
96 logger.log(Level.WARNING, msg);
97 return ParseResult.ERROR;
98 }
catch (IllegalArgumentException ex) {
99 logger.log(Level.INFO,
"Found encrypted PST file.");
100 return ParseResult.ENCRYPT;
109 List<EmailMessage> getResults() {
114 return errors.toString();
129 private long processFolder(PSTFolder folder, String path,
boolean root,
long fileID) {
130 String newPath = (root ? path : path +
"\\" + folder.getDisplayName());
132 if (folder.hasSubfolders()) {
133 List<PSTFolder> subFolders;
135 subFolders = folder.getSubFolders();
136 }
catch (PSTException | IOException ex) {
137 subFolders =
new ArrayList<>();
138 logger.log(Level.INFO,
"java-libpst exception while getting subfolders: {0}", ex.getMessage());
141 for (PSTFolder f : subFolders) {
142 failCount += processFolder(f, newPath,
false, fileID);
146 if (folder.getContentCount() != 0) {
150 while ((email = (PSTMessage) folder.getNextChild()) != null) {
151 results.add(extractEmailMessage(email, newPath, fileID));
153 }
catch (PSTException | IOException ex) {
155 logger.log(Level.INFO,
"java-libpst exception while getting emails from a folder: {0}", ex.getMessage());
170 private EmailMessage extractEmailMessage(PSTMessage msg, String localPath,
long fileID) {
171 EmailMessage email =
new EmailMessage();
172 email.setRecipients(msg.getDisplayTo());
173 email.setCc(msg.getDisplayCC());
174 email.setBcc(msg.getDisplayBCC());
175 email.setSender(getSender(msg.getSenderName(), msg.getSenderEmailAddress()));
176 email.setSentDate(msg.getMessageDeliveryTime());
177 if(msg.getTransportMessageHeaders().isEmpty()) {
178 email.setTextBody(msg.getBody());
180 email.setTextBody(msg.getBody() +
"\n-----HEADERS-----\n\n" + msg.getTransportMessageHeaders() +
"\n\n---END HEADERS--\n\n");
183 email.setHtmlBody(msg.getBodyHTML());
186 rtf = msg.getRTFBody();
187 }
catch (PSTException | IOException ex) {
188 logger.log(Level.INFO,
"Failed to get RTF content from pst email.");
190 email.setRtfBody(rtf);
191 email.setLocalPath(localPath);
192 email.setSubject(msg.getSubject());
193 email.setId(msg.getDescriptorNodeId());
195 if (msg.hasAttachments()) {
196 extractAttachments(email, msg, fileID);
208 private void extractAttachments(EmailMessage email, PSTMessage msg,
long fileID) {
209 int numberOfAttachments = msg.getNumberOfAttachments();
210 String outputDirPath = ThunderbirdMboxFileIngestModule.getModuleOutputPath() + File.separator;
211 for (
int x = 0; x < numberOfAttachments; x++) {
212 String filename =
"";
214 PSTAttachment attach = msg.getAttachment(x);
215 long size = attach.getAttachSize();
216 long freeSpace = services.getFreeDiskSpace();
217 if ((freeSpace != IngestMonitor.DISK_FREE_SPACE_UNKNOWN) && (size >= freeSpace)) {
221 filename = attach.getLongFilename();
222 if (filename.isEmpty()) {
223 filename = attach.getFilename();
225 String uniqueFilename = fileID +
"-" + msg.getDescriptorNodeId() +
"-" + attach.getContentId() +
"-" + filename;
226 String outPath = outputDirPath + uniqueFilename;
227 saveAttachmentToDisk(attach, outPath);
229 EmailMessage.Attachment attachment =
new EmailMessage.Attachment();
231 long crTime = attach.getCreationTime() != null ? attach.getCreationTime().getTime() / 1000 : 0;
232 long mTime = attach.getModificationTime() != null ? attach.getModificationTime().getTime() / 1000 : 0;
233 String relPath = getRelModuleOutputPath() + File.separator + uniqueFilename;
234 attachment.setName(filename);
235 attachment.setCrTime(crTime);
236 attachment.setmTime(mTime);
237 attachment.setLocalPath(relPath);
238 attachment.setSize(attach.getFilesize());
239 attachment.setEncodingType(TskData.EncodingType.XOR1);
240 email.addAttachment(attachment);
241 }
catch (PSTException | IOException | NullPointerException ex) {
247 NbBundle.getMessage(
this.getClass(),
"PstParser.extractAttch.errMsg.failedToExtractToDisk",
249 logger.log(Level.WARNING,
"Failed to extract attachment from pst file.", ex);
265 private void saveAttachmentToDisk(PSTAttachment attach, String outPath)
throws IOException, PSTException {
266 try (InputStream attachmentStream = attach.getFileInputStream();
267 EncodedFileOutputStream out =
new EncodedFileOutputStream(
new FileOutputStream(outPath), TskData.EncodingType.XOR1)) {
269 int bufferSize = 8176;
270 byte[] buffer =
new byte[bufferSize];
271 int count = attachmentStream.read(buffer);
274 throw new IOException(
"attachmentStream invalid (read() fails). File " + attach.getLongFilename() +
" skipped");
277 while (count == bufferSize) {
279 count = attachmentStream.read(buffer);
282 byte[] endBuffer =
new byte[count];
283 System.arraycopy(buffer, 0, endBuffer, 0, count);
284 out.write(endBuffer);
297 private String getSender(String name, String addr) {
298 if (name.isEmpty() && addr.isEmpty()) {
300 }
else if (name.isEmpty()) {
302 }
else if (addr.isEmpty()) {
305 return name +
": " + addr;
316 public static boolean isPstFile(AbstractFile file) {
317 byte[] buffer =
new byte[4];
319 int read = file.read(buffer, 0, 4);
323 ByteBuffer bb = ByteBuffer.wrap(buffer);
324 return bb.getInt() == PST_HEADER;
325 }
catch (TskCoreException ex) {
326 logger.log(Level.WARNING,
"Exception while detecting if a file is a pst file.");
331 private void addErrorMessage(String msg) {
332 errors.append(
"<li>").append(msg).append(
"</li>");
static String getRelModuleOutputPath()