Autopsy  4.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
PstParser.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2011-2014 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.thunderbirdparser;
20 
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;
26 import java.io.File;
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;
39 import org.sleuthkit.datamodel.AbstractFile;
40 import org.sleuthkit.datamodel.TskCoreException;
41 
47 class PstParser {
48 
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;
61 
62  PstParser(IngestServices services) {
63  results = new ArrayList<>();
64  this.services = services;
65  errors = new StringBuilder();
66  }
67 
68  enum ParseResult {
69 
70  OK, ERROR, ENCRYPT;
71  }
72 
81  ParseResult parse(File file, long fileID) {
82  PSTFile pstFile;
83  long failures;
84  try {
85  pstFile = new PSTFile(file);
86  failures = processFolder(pstFile.getRootFolder(), "\\", true, fileID);
87  if (failures > 0) {
88  addErrorMessage(
89  NbBundle.getMessage(this.getClass(), "PstParser.parse.errMsg.failedToParseNMsgs", failures));
90  }
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(); //NON-NLS
94  logger.log(Level.WARNING, msg);
95  return ParseResult.ERROR;
96  } catch (IllegalArgumentException ex) {
97  logger.log(Level.INFO, "Found encrypted PST file."); //NON-NLS
98  return ParseResult.ENCRYPT;
99  }
100  }
101 
107  List<EmailMessage> getResults() {
108  return results;
109  }
110 
111  String getErrors() {
112  return errors.toString();
113  }
114 
127  private long processFolder(PSTFolder folder, String path, boolean root, long fileID) {
128  String newPath = (root ? path : path + "\\" + folder.getDisplayName());
129  long failCount = 0L; // Number of emails that failed
130  if (folder.hasSubfolders()) {
131  List<PSTFolder> subFolders;
132  try {
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()); //NON-NLS
137  }
138 
139  for (PSTFolder f : subFolders) {
140  failCount += processFolder(f, newPath, false, fileID);
141  }
142  }
143 
144  if (folder.getContentCount() != 0) {
145  PSTMessage email;
146  // A folder's children are always emails, never other folders.
147  try {
148  while ((email = (PSTMessage) folder.getNextChild()) != null) {
149  results.add(extractEmailMessage(email, newPath, fileID));
150  }
151  } catch (PSTException | IOException ex) {
152  failCount++;
153  logger.log(Level.INFO, "java-libpst exception while getting emails from a folder: {0}", ex.getMessage()); //NON-NLS
154  }
155  }
156 
157  return failCount;
158  }
159 
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());
177  } else {
178  email.setTextBody(msg.getBody() + "\n-----HEADERS-----\n\n" + msg.getTransportMessageHeaders() + "\n\n---END HEADERS--\n\n");
179  }
180 
181  email.setHtmlBody(msg.getBodyHTML());
182  String rtf = "";
183  try {
184  rtf = msg.getRTFBody();
185  } catch (PSTException | IOException ex) {
186  logger.log(Level.INFO, "Failed to get RTF content from pst email."); //NON-NLS
187  }
188  email.setRtfBody(rtf);
189  email.setLocalPath(localPath);
190  email.setSubject(msg.getSubject());
191  email.setId(msg.getDescriptorNodeId());
192 
193  if (msg.hasAttachments()) {
194  extractAttachments(email, msg, fileID);
195  }
196 
197  return email;
198  }
199 
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 = "";
211  try {
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)) {
216  continue;
217  }
218  // both long and short filenames can be used for attachments
219  filename = attach.getLongFilename();
220  if (filename.isEmpty()) {
221  filename = attach.getFilename();
222  }
223  String uniqueFilename = fileID + "-" + msg.getDescriptorNodeId() + "-" + attach.getContentId() + "-" + filename;
224  String outPath = outputDirPath + uniqueFilename;
225  saveAttachmentToDisk(attach, outPath);
226 
227  EmailMessage.Attachment attachment = new EmailMessage.Attachment();
228 
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) {
243  addErrorMessage(
244  NbBundle.getMessage(this.getClass(), "PstParser.extractAttch.errMsg.failedToExtractToDisk",
245  filename));
246  logger.log(Level.WARNING, "Failed to extract attachment from pst file.", ex); //NON-NLS
247  }
248  }
249  }
250 
262  private void saveAttachmentToDisk(PSTAttachment attach, String outPath) throws IOException, PSTException {
263  try (InputStream attachmentStream = attach.getFileInputStream(); FileOutputStream out = new FileOutputStream(outPath)) {
264  // 8176 is the block size used internally and should give the best performance
265  int bufferSize = 8176;
266  byte[] buffer = new byte[bufferSize];
267  int count = attachmentStream.read(buffer);
268 
269  if (count == -1) {
270  throw new IOException("attachmentStream invalid (read() fails). File " + attach.getLongFilename() + " skipped");
271  }
272 
273  while (count == bufferSize) {
274  out.write(buffer);
275  count = attachmentStream.read(buffer);
276  }
277  if (count != -1) {
278  byte[] endBuffer = new byte[count];
279  System.arraycopy(buffer, 0, endBuffer, 0, count);
280  out.write(endBuffer);
281  }
282  }
283  }
284 
293  private String getSender(String name, String addr) {
294  if (name.isEmpty() && addr.isEmpty()) {
295  return "";
296  } else if (name.isEmpty()) {
297  return addr;
298  } else if (addr.isEmpty()) {
299  return name;
300  } else {
301  return name + ": " + addr;
302  }
303  }
304 
312  public static boolean isPstFile(AbstractFile file) {
313  byte[] buffer = new byte[4];
314  try {
315  int read = file.read(buffer, 0, 4);
316  if (read != 4) {
317  return false;
318  }
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."); //NON-NLS
323  return false;
324  }
325  }
326 
327  private void addErrorMessage(String msg) {
328  errors.append("<li>").append(msg).append("</li>"); //NON-NLS
329  }
330 }

Copyright © 2012-2015 Basis Technology. Generated on: Wed Apr 6 2016
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.