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()