Autopsy  4.1
Graphical digital forensics platform for The Sleuth Kit and other tools.
SevenZipExtractor.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2013-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.modules.embeddedfileextractor;
20 
21 import java.io.File;
22 import java.io.FileOutputStream;
23 import java.io.IOException;
24 import java.io.OutputStream;
25 import java.nio.file.Files;
26 import java.nio.file.Paths;
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.Date;
30 import java.util.List;
31 import java.util.logging.Level;
32 import net.sf.sevenzipjbinding.ArchiveFormat;
33 import static net.sf.sevenzipjbinding.ArchiveFormat.RAR;
34 import net.sf.sevenzipjbinding.ISequentialOutStream;
35 import net.sf.sevenzipjbinding.ISevenZipInArchive;
36 import net.sf.sevenzipjbinding.SevenZip;
37 import net.sf.sevenzipjbinding.SevenZipException;
38 import net.sf.sevenzipjbinding.SevenZipNativeInitializationException;
39 import net.sf.sevenzipjbinding.simple.ISimpleInArchive;
40 import net.sf.sevenzipjbinding.simple.ISimpleInArchiveItem;
41 import org.netbeans.api.progress.ProgressHandle;
42 import org.openide.util.NbBundle;
43 import org.openide.util.NbBundle.Messages;
65 
66 class SevenZipExtractor {
67 
68  private static final Logger logger = Logger.getLogger(SevenZipExtractor.class.getName());
69  private IngestServices services = IngestServices.getInstance();
70  private final IngestJobContext context;
71  private final FileTypeDetector fileTypeDetector;
72  static final String[] SUPPORTED_EXTENSIONS = {"zip", "rar", "arj", "7z", "7zip", "gzip", "gz", "bzip2", "tar", "tgz",}; // NON-NLS
73  //encryption type strings
74  private static final String ENCRYPTION_FILE_LEVEL = NbBundle.getMessage(EmbeddedFileExtractorIngestModule.class,
75  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.encryptionFileLevel");
76  private static final String ENCRYPTION_FULL = NbBundle.getMessage(EmbeddedFileExtractorIngestModule.class,
77  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.encryptionFull");
78  //zip bomb detection
79  private static final int MAX_DEPTH = 4;
80  private static final int MAX_COMPRESSION_RATIO = 600;
81  private static final long MIN_COMPRESSION_RATIO_SIZE = 500 * 1000000L;
82  private static final long MIN_FREE_DISK_SPACE = 1 * 1000 * 1000000L; //1GB
83  //counts archive depth
84  private ArchiveDepthCountTree archiveDepthCountTree;
85 
86  private String moduleDirRelative;
87  private String moduleDirAbsolute;
88 
89  private Blackboard blackboard;
90 
91  private String getLocalRootAbsPath(String uniqueArchiveFileName) {
92  return moduleDirAbsolute + File.separator + uniqueArchiveFileName;
93  }
94 
99 
100  ZIP("application/zip"), //NON-NLS
101  SEVENZ("application/x-7z-compressed"), //NON-NLS
102  GZIP("application/gzip"), //NON-NLS
103  XGZIP("application/x-gzip"), //NON-NLS
104  XBZIP2("application/x-bzip2"), //NON-NLS
105  XTAR("application/x-tar"), //NON-NLS
106  XGTAR("application/x-gtar"),
107  XRAR("application/x-rar-compressed"); //NON-NLS
108 
109  private final String mimeType;
110 
111  SupportedArchiveExtractionFormats(final String mimeType) {
112  this.mimeType = mimeType;
113  }
114 
115  @Override
116  public String toString() {
117  return this.mimeType;
118  }
119  // TODO Expand to support more formats after upgrading Tika
120  }
121 
122  SevenZipExtractor(IngestJobContext context, FileTypeDetector fileTypeDetector, String moduleDirRelative, String moduleDirAbsolute) throws SevenZipNativeInitializationException {
123  if (!SevenZip.isInitializedSuccessfully() && (SevenZip.getLastInitializationException() == null)) {
124  SevenZip.initSevenZipFromPlatformJAR();
125  }
126  this.context = context;
127  this.fileTypeDetector = fileTypeDetector;
128  this.moduleDirRelative = moduleDirRelative;
129  this.moduleDirAbsolute = moduleDirAbsolute;
130  this.archiveDepthCountTree = new ArchiveDepthCountTree();
131  }
132 
143  boolean isSevenZipExtractionSupported(AbstractFile abstractFile) {
144  try {
145  String abstractFileMimeType = fileTypeDetector.getFileType(abstractFile);
146  for (SupportedArchiveExtractionFormats s : SupportedArchiveExtractionFormats.values()) {
147  if (s.toString().equals(abstractFileMimeType)) {
148  return true;
149  }
150  }
151  return false;
152  } catch (TskCoreException ex) {
153  logger.log(Level.WARNING, "Error executing FileTypeDetector.getFileType()", ex); // NON-NLS
154  }
155 
156  // attempt extension matching
157  final String extension = abstractFile.getNameExtension();
158  for (String supportedExtension : SUPPORTED_EXTENSIONS) {
159  if (extension.equals(supportedExtension)) {
160  return true;
161  }
162  }
163 
164  return false;
165  }
166 
179  private boolean isZipBombArchiveItemCheck(AbstractFile archiveFile, ISimpleInArchiveItem archiveFileItem) {
180  try {
181  final Long archiveItemSize = archiveFileItem.getSize();
182 
183  //skip the check for small files
184  if (archiveItemSize == null || archiveItemSize < MIN_COMPRESSION_RATIO_SIZE) {
185  return false;
186  }
187 
188  final Long archiveItemPackedSize = archiveFileItem.getPackedSize();
189 
190  if (archiveItemPackedSize == null || archiveItemPackedSize <= 0) {
191  logger.log(Level.WARNING, "Cannot getting compression ratio, cannot detect if zipbomb: {0}, item: {1}", new Object[]{archiveFile.getName(), archiveFileItem.getPath()}); //NON-NLS
192  return false;
193  }
194 
195  int cRatio = (int) (archiveItemSize / archiveItemPackedSize);
196 
197  if (cRatio >= MAX_COMPRESSION_RATIO) {
198  String itemName = archiveFileItem.getPath();
199  logger.log(Level.INFO, "Possible zip bomb detected, compression ration: {0} for in archive item: {1}", new Object[]{cRatio, itemName}); //NON-NLS
200  String msg = NbBundle.getMessage(SevenZipExtractor.class,
201  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnMsg", archiveFile.getName(), itemName);
202  String path;
203  try {
204  path = archiveFile.getUniquePath();
205  } catch (TskCoreException ex) {
206  path = archiveFile.getParentPath() + archiveFile.getName();
207  }
208  String details = NbBundle.getMessage(SevenZipExtractor.class,
209  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnDetails", cRatio, path);
210  //MessageNotifyUtil.Notify.error(msg, details);
211  services.postMessage(IngestMessage.createWarningMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
212  return true;
213  } else {
214  return false;
215  }
216 
217  } catch (SevenZipException ex) {
218  logger.log(Level.WARNING, "Error getting archive item size and cannot detect if zipbomb. ", ex); //NON-NLS
219  return false;
220  }
221  }
222 
231  private ArchiveFormat get7ZipOptions(AbstractFile archiveFile) {
232  // try to get the file type from the BB
233  String detectedFormat = null;
234  detectedFormat = archiveFile.getMIMEType();
235 
236  if (detectedFormat == null) {
237  logger.log(Level.WARNING, "Could not detect format for file: {0}", archiveFile); //NON-NLS
238 
239  // if we don't have attribute info then use file extension
240  String extension = archiveFile.getNameExtension();
241  if ("rar".equals(extension)) //NON-NLS
242  {
243  // for RAR files we need to open them explicitly as RAR. Otherwise, if there is a ZIP archive inside RAR archive
244  // it will be opened incorrectly when using 7zip's built-in auto-detect functionality
245  return RAR;
246  }
247 
248  // Otherwise open the archive using 7zip's built-in auto-detect functionality
249  return null;
250  } else if (detectedFormat.contains("application/x-rar-compressed")) //NON-NLS
251  {
252  // for RAR files we need to open them explicitly as RAR. Otherwise, if there is a ZIP archive inside RAR archive
253  // it will be opened incorrectly when using 7zip's built-in auto-detect functionality
254  return RAR;
255  }
256 
257  // Otherwise open the archive using 7zip's built-in auto-detect functionality
258  return null;
259  }
260 
269  @Messages({"SevenZipExtractor.indexError.message=Failed to index encryption detected artifact for keyword search."})
270  void unpack(AbstractFile archiveFile) {
271  blackboard = Case.getCurrentCase().getServices().getBlackboard();
272  String archiveFilePath;
273  try {
274  archiveFilePath = archiveFile.getUniquePath();
275  } catch (TskCoreException ex) {
276  archiveFilePath = archiveFile.getParentPath() + archiveFile.getName();
277  }
278 
279  //check if already has derived files, skip
280  try {
281  if (archiveFile.hasChildren()) {
282  //check if local unpacked dir exists
283  if (new File(EmbeddedFileExtractorIngestModule.getUniqueName(archiveFile)).exists()) {
284  logger.log(Level.INFO, "File already has been processed as it has children and local unpacked file, skipping: {0}", archiveFilePath); //NON-NLS
285  return;
286  }
287  }
288  } catch (TskCoreException e) {
289  logger.log(Level.INFO, "Error checking if file already has been processed, skipping: {0}", archiveFilePath); //NON-NLS
290  return;
291  }
292 
293  List<AbstractFile> unpackedFiles = Collections.<AbstractFile>emptyList();
294 
295  //recursion depth check for zip bomb
296  final long archiveId = archiveFile.getId();
297  SevenZipExtractor.ArchiveDepthCountTree.Archive parentAr = archiveDepthCountTree.findArchive(archiveId);
298  if (parentAr == null) {
299  parentAr = archiveDepthCountTree.addArchive(null, archiveId);
300  } else if (parentAr.getDepth() == MAX_DEPTH) {
301  String msg = NbBundle.getMessage(SevenZipExtractor.class,
302  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnMsg.zipBomb", archiveFile.getName());
303  String details = NbBundle.getMessage(SevenZipExtractor.class,
304  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnDetails.zipBomb",
305  parentAr.getDepth(), archiveFilePath);
306  //MessageNotifyUtil.Notify.error(msg, details);
307  services.postMessage(IngestMessage.createWarningMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
308  return;
309  }
310 
311  boolean hasEncrypted = false;
312  boolean fullEncryption = true;
313 
314  ISevenZipInArchive inArchive = null;
315  SevenZipContentReadStream stream = null;
316 
317  final ProgressHandle progress = ProgressHandle.createHandle(Bundle.EmbeddedFileExtractorIngestModule_ArchiveExtractor_moduleName());
318  int processedItems = 0;
319 
320  boolean progressStarted = false;
321  try {
322  stream = new SevenZipContentReadStream(new ReadContentInputStream(archiveFile));
323 
324  // for RAR files we need to open them explicitly as RAR. Otherwise, if there is a ZIP archive inside RAR archive
325  // it will be opened incorrectly when using 7zip's built-in auto-detect functionality.
326  // All other archive formats are still opened using 7zip built-in auto-detect functionality.
327  ArchiveFormat options = get7ZipOptions(archiveFile);
328  inArchive = SevenZip.openInArchive(options, stream);
329 
330  int numItems = inArchive.getNumberOfItems();
331  logger.log(Level.INFO, "Count of items in archive: {0}: {1}", new Object[]{archiveFilePath, numItems}); //NON-NLS
332  progress.start(numItems);
333  progressStarted = true;
334 
335  final ISimpleInArchive simpleInArchive = inArchive.getSimpleInterface();
336 
337  //setup the archive local root folder
338  final String uniqueArchiveFileName = FileUtil.escapeFileName(EmbeddedFileExtractorIngestModule.getUniqueName(archiveFile));
339  final String localRootAbsPath = getLocalRootAbsPath(uniqueArchiveFileName);
340  final File localRoot = new File(localRootAbsPath);
341  if (!localRoot.exists()) {
342  try {
343  localRoot.mkdirs();
344  } catch (SecurityException e) {
345  logger.log(Level.SEVERE, "Error setting up output path for archive root: {0}", localRootAbsPath); //NON-NLS
346  //bail
347  return;
348  }
349  }
350 
351  //initialize tree hierarchy to keep track of unpacked file structure
352  SevenZipExtractor.UnpackedTree unpackedTree = new SevenZipExtractor.UnpackedTree(moduleDirRelative + "/" + uniqueArchiveFileName, archiveFile);
353 
354  long freeDiskSpace = services.getFreeDiskSpace();
355 
356  //unpack and process every item in archive
357  int itemNumber = 0;
358  for (ISimpleInArchiveItem item : simpleInArchive.getArchiveItems()) {
359  String pathInArchive = item.getPath();
360 
361  if (pathInArchive == null || pathInArchive.isEmpty()) {
362  //some formats (.tar.gz) may not be handled correctly -- file in archive has no name/path
363  //handle this for .tar.gz and tgz but assuming the child is tar,
364  //otherwise, unpack using itemNumber as name
365 
366  //TODO this should really be signature based, not extension based
367  String archName = archiveFile.getName();
368  int dotI = archName.lastIndexOf(".");
369  String useName = null;
370  if (dotI != -1) {
371  String base = archName.substring(0, dotI);
372  String ext = archName.substring(dotI);
373  int colonIndex = ext.lastIndexOf(":");
374  if (colonIndex != -1) {
375  // If alternate data stream is found, fix the name
376  // so Windows doesn't choke on the colon character.
377  ext = ext.substring(0, colonIndex);
378  }
379  switch (ext) {
380  case ".gz": //NON-NLS
381  useName = base;
382  break;
383  case ".tgz": //NON-NLS
384  useName = base + ".tar"; //NON-NLS
385  break;
386  case ".bz2": //NON-NLS
387  useName = base;
388  break;
389  }
390  }
391 
392  if (useName == null) {
393  pathInArchive = "/" + archName + "/" + Integer.toString(itemNumber);
394  } else {
395  pathInArchive = "/" + useName;
396  }
397 
398  String msg = NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.unknownPath.msg",
399  archiveFilePath, pathInArchive);
400  logger.log(Level.WARNING, msg);
401 
402  }
403  archiveFilePath = FileUtil.escapeFileName(archiveFilePath);
404  ++itemNumber;
405 
406  //check if possible zip bomb
407  if (isZipBombArchiveItemCheck(archiveFile, item)) {
408  continue; //skip the item
409  }
410 
411  //find this node in the hierarchy, create if needed
412  SevenZipExtractor.UnpackedTree.UnpackedNode unpackedNode = unpackedTree.addNode(pathInArchive);
413 
414  String fileName = unpackedNode.getFileName();
415 
416  //update progress bar
417  progress.progress(archiveFile.getName() + ": " + fileName, processedItems);
418 
419  final boolean isEncrypted = item.isEncrypted();
420  final boolean isDir = item.isFolder();
421 
422  if (isEncrypted) {
423  logger.log(Level.WARNING, "Skipping encrypted file in archive: {0}", pathInArchive); //NON-NLS
424  hasEncrypted = true;
425  continue;
426  } else {
427  fullEncryption = false;
428  }
429 
430  // NOTE: item.getSize() may return null in case of certain
431  // archiving formats. Eg: BZ2
432  Long size = item.getSize();
433 
434  //check if unpacking this file will result in out of disk space
435  //this is additional to zip bomb prevention mechanism
436  if (freeDiskSpace != IngestMonitor.DISK_FREE_SPACE_UNKNOWN && size != null && size > 0) { //if free space is known and file is not empty.
437  long newDiskSpace = freeDiskSpace - size;
438  if (newDiskSpace < MIN_FREE_DISK_SPACE) {
439  String msg = NbBundle.getMessage(SevenZipExtractor.class,
440  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.msg",
441  archiveFilePath, fileName);
442  String details = NbBundle.getMessage(SevenZipExtractor.class,
443  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.details");
444  //MessageNotifyUtil.Notify.error(msg, details);
445  services.postMessage(IngestMessage.createErrorMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
446  logger.log(Level.INFO, "Skipping archive item due to insufficient disk space: {0}, {1}", new Object[]{archiveFilePath, fileName}); //NON-NLS
447  logger.log(Level.INFO, "Available disk space: {0}", new Object[]{freeDiskSpace}); //NON-NLS
448  continue; //skip this file
449  } else {
450  //update est. disk space during this archive, so we don't need to poll for every file extracted
451  freeDiskSpace = newDiskSpace;
452  }
453  }
454 
455  final String uniqueExtractedName = FileUtil.escapeFileName(uniqueArchiveFileName + File.separator + (item.getItemIndex() / 1000) + File.separator + item.getItemIndex() + "_" + new File(pathInArchive).getName());
456 
457  //final String localRelPath = unpackDir + File.separator + localFileRelPath;
458  final String localRelPath = moduleDirRelative + File.separator + uniqueExtractedName;
459  final String localAbsPath = moduleDirAbsolute + File.separator + uniqueExtractedName;
460 
461  //create local dirs and empty files before extracted
462  File localFile = new java.io.File(localAbsPath);
463  //cannot rely on files in top-bottom order
464  if (!localFile.exists()) {
465  try {
466  if (isDir) {
467  localFile.mkdirs();
468  } else {
469  localFile.getParentFile().mkdirs();
470  try {
471  localFile.createNewFile();
472  } catch (IOException e) {
473  logger.log(Level.SEVERE, "Error creating extracted file: " + localFile.getAbsolutePath(), e); //NON-NLS
474  }
475  }
476  } catch (SecurityException e) {
477  logger.log(Level.SEVERE, "Error setting up output path for unpacked file: {0}", pathInArchive); //NON-NLS
478  //TODO consider bail out / msg to the user
479  }
480  }
481 
482  // skip the rest of this loop if we couldn't create the file
483  if (localFile.exists() == false) {
484  continue;
485  }
486 
487  final Date createTime = item.getCreationTime();
488  final Date accessTime = item.getLastAccessTime();
489  final Date writeTime = item.getLastWriteTime();
490  final long createtime = createTime == null ? 0L : createTime.getTime() / 1000;
491  final long modtime = writeTime == null ? 0L : writeTime.getTime() / 1000;
492  final long accesstime = accessTime == null ? 0L : accessTime.getTime() / 1000;
493 
494  //unpack locally if a file
495  SevenZipExtractor.UnpackStream unpackStream = null;
496  if (!isDir) {
497  try {
498  if (size != null) {
499  unpackStream = new SevenZipExtractor.KnownSizeUnpackStream(localAbsPath, size);
500  } else {
501  unpackStream = new SevenZipExtractor.UnknownSizeUnpackStream(localAbsPath, freeDiskSpace);
502  }
503  item.extractSlow(unpackStream);
504  } catch (Exception e) {
505  //could be something unexpected with this file, move on
506  logger.log(Level.WARNING, "Could not extract file from archive: " + localAbsPath, e); //NON-NLS
507  } finally {
508  if (unpackStream != null) {
509  //record derived data in unode, to be traversed later after unpacking the archive
510  unpackedNode.addDerivedInfo(unpackStream.getSize(), !isDir,
511  0L, createtime, accesstime, modtime, localRelPath);
512  unpackStream.close();
513  }
514  }
515  } else { // this is a directory, size is always 0
516  unpackedNode.addDerivedInfo(0, !isDir,
517  0L, createtime, accesstime, modtime, localRelPath);
518  }
519 
520  //update units for progress bar
521  ++processedItems;
522  }
523 
524  // add them to the DB. We wait until the end so that we have the metadata on all of the
525  // intermediate nodes since the order is not guaranteed
526  try {
527  unpackedTree.addDerivedFilesToCase();
528  unpackedFiles = unpackedTree.getAllFileObjects();
529 
530  //check if children are archives, update archive depth tracking
531  for (AbstractFile unpackedFile : unpackedFiles) {
532  if (isSevenZipExtractionSupported(unpackedFile)) {
533  archiveDepthCountTree.addArchive(parentAr, unpackedFile.getId());
534  }
535  }
536 
537  } catch (TskCoreException e) {
538  logger.log(Level.SEVERE, "Error populating complete derived file hierarchy from the unpacked dir structure"); //NON-NLS
539  //TODO decide if anything to cleanup, for now bailing
540  }
541 
542  } catch (SevenZipException ex) {
543  logger.log(Level.WARNING, "Error unpacking file: {0}", archiveFile); //NON-NLS
544  //inbox message
545 
546  // print a message if the file is allocated
547  if (archiveFile.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC)) {
548  String msg = NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.errUnpacking.msg",
549  archiveFile.getName());
550  String details = NbBundle.getMessage(SevenZipExtractor.class,
551  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.errUnpacking.details",
552  archiveFilePath, ex.getMessage());
553  services.postMessage(IngestMessage.createErrorMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
554  }
555  } finally {
556  if (inArchive != null) {
557  try {
558  inArchive.close();
559  } catch (SevenZipException e) {
560  logger.log(Level.SEVERE, "Error closing archive: " + archiveFile, e); //NON-NLS
561  }
562  }
563 
564  if (stream != null) {
565  try {
566  stream.close();
567  } catch (IOException ex) {
568  logger.log(Level.SEVERE, "Error closing stream after unpacking archive: " + archiveFile, ex); //NON-NLS
569  }
570  }
571 
572  //close progress bar
573  if (progressStarted) {
574  progress.finish();
575  }
576  }
577 
578  //create artifact and send user message
579  if (hasEncrypted) {
580  String encryptionType = fullEncryption ? ENCRYPTION_FULL : ENCRYPTION_FILE_LEVEL;
581  try {
582  BlackboardArtifact artifact = archiveFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED);
583  artifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME, EmbeddedFileExtractorModuleFactory.getModuleName(), encryptionType));
584 
585  try {
586  // index the artifact for keyword search
587  blackboard.indexArtifact(artifact);
588  } catch (Blackboard.BlackboardException ex) {
589  logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex); //NON-NLS
590  MessageNotifyUtil.Notify.error(
591  Bundle.SevenZipExtractor_indexError_message(), artifact.getDisplayName());
592  }
593 
594  services.fireModuleDataEvent(new ModuleDataEvent(EmbeddedFileExtractorModuleFactory.getModuleName(), BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED));
595  } catch (TskCoreException ex) {
596  logger.log(Level.SEVERE, "Error creating blackboard artifact for encryption detected for file: " + archiveFilePath, ex); //NON-NLS
597  }
598 
599  String msg = NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.encrFileDetected.msg");
600  String details = NbBundle.getMessage(SevenZipExtractor.class,
601  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.encrFileDetected.details",
602  archiveFile.getName(), EmbeddedFileExtractorModuleFactory.getModuleName());
603  services.postMessage(IngestMessage.createWarningMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
604  }
605 
606  // adding unpacked extracted derived files to the job after closing relevant resources.
607  if (!unpackedFiles.isEmpty()) {
608  //currently sending a single event for all new files
609  services.fireModuleContentEvent(new ModuleContentEvent(archiveFile));
610  context.addFilesToJob(unpackedFiles);
611  }
612  }
613 
617  private abstract static class UnpackStream implements ISequentialOutStream {
618 
619  private OutputStream output;
620  private String localAbsPath;
621 
622  UnpackStream(String localAbsPath) {
623  this.localAbsPath = localAbsPath;
624  try {
625  output = new EncodedFileOutputStream(new FileOutputStream(localAbsPath), TskData.EncodingType.XOR1);
626  } catch (IOException ex) {
627  logger.log(Level.SEVERE, "Error writing extracted file: " + localAbsPath, ex); //NON-NLS
628  }
629 
630  }
631 
632  public abstract long getSize();
633 
634  OutputStream getOutput() {
635  return output;
636  }
637 
638  String getLocalAbsPath() {
639  return localAbsPath;
640  }
641 
642  public void close() {
643  if (output != null) {
644  try {
645  output.flush();
646  output.close();
647  } catch (IOException e) {
648  logger.log(Level.SEVERE, "Error closing unpack stream for file: {0}", localAbsPath); //NON-NLS
649  }
650  }
651  }
652  }
653 
657  private static class UnknownSizeUnpackStream extends UnpackStream {
658 
659  private long freeDiskSpace;
660  private boolean outOfSpace = false;
661  private long bytesWritten = 0;
662 
663  UnknownSizeUnpackStream(String localAbsPath, long freeDiskSpace) {
664  super(localAbsPath);
665  this.freeDiskSpace = freeDiskSpace;
666  }
667 
668  @Override
669  public long getSize() {
670  return this.bytesWritten;
671  }
672 
673  @Override
674  public int write(byte[] bytes) throws SevenZipException {
675  try {
676  // If the content size is unknown, cautiously write to disk.
677  // Write only if byte array is less than 80% of the current
678  // free disk space.
679  if (freeDiskSpace == IngestMonitor.DISK_FREE_SPACE_UNKNOWN || bytes.length < 0.8 * freeDiskSpace) {
680  getOutput().write(bytes);
681  // NOTE: this method is called multiple times for a
682  // single extractSlow() call. Update bytesWritten and
683  // freeDiskSpace after every write operation.
684  this.bytesWritten += bytes.length;
685  this.freeDiskSpace -= bytes.length;
686  } else {
687  this.outOfSpace = true;
688  logger.log(Level.INFO, NbBundle.getMessage(
689  SevenZipExtractor.class,
690  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackStream.write.noSpace.msg"));
691  throw new SevenZipException(
692  NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackStream.write.noSpace.msg"));
693  }
694  } catch (IOException ex) {
695  throw new SevenZipException(
696  NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackStream.write.exception.msg",
697  getLocalAbsPath()), ex);
698  }
699  return bytes.length;
700  }
701 
702  @Override
703  public void close() {
704  if (getOutput() != null) {
705  try {
706  getOutput().flush();
707  getOutput().close();
708  if (this.outOfSpace) {
709  Files.delete(Paths.get(getLocalAbsPath()));
710  }
711  } catch (IOException e) {
712  logger.log(Level.SEVERE, "Error closing unpack stream for file: {0}", getLocalAbsPath()); //NON-NLS
713  }
714  }
715  }
716  }
717 
721  private static class KnownSizeUnpackStream extends UnpackStream {
722 
723  private long size;
724 
725  KnownSizeUnpackStream(String localAbsPath, long size) {
726  super(localAbsPath);
727  this.size = size;
728  }
729 
730  @Override
731  public long getSize() {
732  return this.size;
733  }
734 
735  @Override
736  public int write(byte[] bytes) throws SevenZipException {
737  try {
738  getOutput().write(bytes);
739  } catch (IOException ex) {
740  throw new SevenZipException(
741  NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackStream.write.exception.msg",
742  getLocalAbsPath()), ex);
743  }
744  return bytes.length;
745  }
746  }
747 
755  private class UnpackedTree {
756 
757  final UnpackedNode rootNode;
758 
766  UnpackedTree(String localPathRoot, AbstractFile archiveFile) {
767  this.rootNode = new UnpackedNode();
768  this.rootNode.setFile(archiveFile);
769  this.rootNode.setFileName(archiveFile.getName());
770  this.rootNode.localRelPath = localPathRoot;
771  }
772 
782  UnpackedNode addNode(String filePath) {
783  String[] toks = filePath.split("[\\/\\\\]");
784  List<String> tokens = new ArrayList<>();
785  for (int i = 0; i < toks.length; ++i) {
786  if (!toks[i].isEmpty()) {
787  tokens.add(toks[i]);
788  }
789  }
790  return addNode(rootNode, tokens);
791  }
792 
801  private UnpackedNode addNode(UnpackedNode parent, List<String> tokenPath) {
802  // we found all of the tokens
803  if (tokenPath.isEmpty()) {
804  return parent;
805  }
806 
807  // get the next name in the path and look it up
808  String childName = tokenPath.remove(0);
809  UnpackedNode child = parent.getChild(childName);
810  // create new node
811  if (child == null) {
812  child = new UnpackedNode(childName, parent);
813  }
814 
815  // go down one more level
816  return addNode(child, tokenPath);
817  }
818 
825  List<AbstractFile> getRootFileObjects() {
826  List<AbstractFile> ret = new ArrayList<>();
827  for (UnpackedNode child : rootNode.children) {
828  ret.add(child.getFile());
829  }
830  return ret;
831  }
832 
839  List<AbstractFile> getAllFileObjects() {
840  List<AbstractFile> ret = new ArrayList<>();
841  for (UnpackedNode child : rootNode.children) {
842  getAllFileObjectsRec(ret, child);
843  }
844  return ret;
845  }
846 
847  private void getAllFileObjectsRec(List<AbstractFile> list, UnpackedNode parent) {
848  list.add(parent.getFile());
849  for (UnpackedNode child : parent.children) {
850  getAllFileObjectsRec(list, child);
851  }
852  }
853 
858  void addDerivedFilesToCase() throws TskCoreException {
859  final FileManager fileManager = Case.getCurrentCase().getServices().getFileManager();
860  for (UnpackedNode child : rootNode.children) {
861  addDerivedFilesToCaseRec(child, fileManager);
862  }
863  }
864 
865  private void addDerivedFilesToCaseRec(UnpackedNode node, FileManager fileManager) throws TskCoreException {
866  final String fileName = node.getFileName();
867 
868  try {
869  DerivedFile df = fileManager.addDerivedFile(fileName, node.getLocalRelPath(), node.getSize(),
870  node.getCtime(), node.getCrtime(), node.getAtime(), node.getMtime(),
871  node.isIsFile(), node.getParent().getFile(), "", EmbeddedFileExtractorModuleFactory.getModuleName(),
872  "", "", TskData.EncodingType.XOR1);
873  node.setFile(df);
874 
875  } catch (TskCoreException ex) {
876  logger.log(Level.SEVERE, "Error adding a derived file to db:" + fileName, ex); //NON-NLS
877  throw new TskCoreException(
878  NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackedTree.exception.msg",
879  fileName), ex);
880  }
881 
882  //recurse
883  for (UnpackedNode child : node.children) {
884  addDerivedFilesToCaseRec(child, fileManager);
885  }
886  }
887 
891  private class UnpackedNode {
892 
893  private String fileName;
895  private List<UnpackedNode> children = new ArrayList<>();
896  private String localRelPath = "";
897  private long size;
898  private long ctime, crtime, atime, mtime;
899  private boolean isFile;
901 
902  //root constructor
903  UnpackedNode() {
904  }
905 
906  //child node constructor
907  UnpackedNode(String fileName, UnpackedNode parent) {
908  this.fileName = fileName;
909  this.parent = parent;
910  this.localRelPath = parent.localRelPath + File.separator + fileName;
911  //new child derived file will be set by unpack() method
912  parent.children.add(this);
913 
914  }
915 
916  public long getCtime() {
917  return ctime;
918  }
919 
920  public long getCrtime() {
921  return crtime;
922  }
923 
924  public long getAtime() {
925  return atime;
926  }
927 
928  public long getMtime() {
929  return mtime;
930  }
931 
932  public void setFileName(String fileName) {
933  this.fileName = fileName;
934  }
935 
936  UnpackedNode getParent() {
937  return parent;
938  }
939 
940  void addDerivedInfo(long size,
941  boolean isFile,
942  long ctime, long crtime, long atime, long mtime, String relLocalPath) {
943  this.size = size;
944  this.isFile = isFile;
945  this.ctime = ctime;
946  this.crtime = crtime;
947  this.atime = atime;
948  this.mtime = mtime;
949  this.localRelPath = relLocalPath;
950  }
951 
952  void setFile(AbstractFile file) {
953  this.file = file;
954  }
955 
963  UnpackedNode getChild(String childFileName) {
964  UnpackedNode ret = null;
965  for (UnpackedNode child : children) {
966  if (child.fileName.equals(childFileName)) {
967  ret = child;
968  break;
969  }
970  }
971  return ret;
972  }
973 
974  public String getFileName() {
975  return fileName;
976  }
977 
979  return file;
980  }
981 
982  public String getLocalRelPath() {
983  return localRelPath;
984  }
985 
986  public long getSize() {
987  return size;
988  }
989 
990  public boolean isIsFile() {
991  return isFile;
992  }
993  }
994  }
995 
999  private static class ArchiveDepthCountTree {
1000 
1001  //keeps all nodes refs for easy search
1002  private final List<Archive> archives = new ArrayList<>();
1003 
1011  Archive findArchive(long objectId) {
1012  for (Archive ar : archives) {
1013  if (ar.objectId == objectId) {
1014  return ar;
1015  }
1016  }
1017 
1018  return null;
1019  }
1020 
1029  Archive addArchive(Archive parent, long objectId) {
1030  Archive child = new Archive(parent, objectId);
1031  archives.add(child);
1032  return child;
1033  }
1034 
1035  private static class Archive {
1036 
1037  int depth;
1038  long objectId;
1039  Archive parent;
1040  List<Archive> children;
1041 
1042  Archive(Archive parent, long objectId) {
1043  this.parent = parent;
1044  this.objectId = objectId;
1045  children = new ArrayList<>();
1046  if (parent != null) {
1047  parent.children.add(this);
1048  this.depth = parent.depth + 1;
1049  } else {
1050  this.depth = 0;
1051  }
1052  }
1053 
1059  int getDepth() {
1060  return depth;
1061  }
1062  }
1063  }
1064 
1065 }
UnpackedNode addNode(UnpackedNode parent, List< String > tokenPath)
synchronized DerivedFile addDerivedFile(String fileName, String localPath, long size, long ctime, long crtime, long atime, long mtime, boolean isFile, AbstractFile parentFile, String rederiveDetails, String toolName, String toolVersion, String otherDetails, TskData.EncodingType encodingType)

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