Autopsy  4.12.0
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-2019 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.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.Collection;
27 import java.util.Collections;
28 import java.util.Date;
29 import java.util.HashMap;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.concurrent.ConcurrentHashMap;
33 import java.util.logging.Level;
34 import net.sf.sevenzipjbinding.ArchiveFormat;
35 import static net.sf.sevenzipjbinding.ArchiveFormat.RAR;
36 import net.sf.sevenzipjbinding.ExtractAskMode;
37 import net.sf.sevenzipjbinding.ExtractOperationResult;
38 import net.sf.sevenzipjbinding.IArchiveExtractCallback;
39 import net.sf.sevenzipjbinding.ICryptoGetTextPassword;
40 import net.sf.sevenzipjbinding.ISequentialOutStream;
41 import net.sf.sevenzipjbinding.ISevenZipInArchive;
42 import net.sf.sevenzipjbinding.PropID;
43 import net.sf.sevenzipjbinding.SevenZip;
44 import net.sf.sevenzipjbinding.SevenZipException;
45 import net.sf.sevenzipjbinding.SevenZipNativeInitializationException;
46 import org.netbeans.api.progress.ProgressHandle;
47 import org.openide.util.NbBundle;
48 import org.openide.util.NbBundle.Messages;
61 import org.sleuthkit.datamodel.AbstractFile;
62 import org.sleuthkit.datamodel.Blackboard;
63 import org.sleuthkit.datamodel.BlackboardArtifact;
64 import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT;
65 import org.sleuthkit.datamodel.BlackboardAttribute;
66 import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT;
67 import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DESCRIPTION;
68 import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME;
69 import org.sleuthkit.datamodel.Content;
70 import org.sleuthkit.datamodel.DerivedFile;
71 import org.sleuthkit.datamodel.EncodedFileOutputStream;
72 import org.sleuthkit.datamodel.ReadContentInputStream;
73 import org.sleuthkit.datamodel.TskCoreException;
74 import org.sleuthkit.datamodel.TskData;
75 
76 class SevenZipExtractor {
77 
78  private static final Logger logger = Logger.getLogger(SevenZipExtractor.class.getName());
79  private static final String MODULE_NAME = EmbeddedFileExtractorModuleFactory.getModuleName();
80 
81  //encryption type strings
82  private static final String ENCRYPTION_FILE_LEVEL = NbBundle.getMessage(EmbeddedFileExtractorIngestModule.class,
83  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.encryptionFileLevel");
84  private static final String ENCRYPTION_FULL = NbBundle.getMessage(EmbeddedFileExtractorIngestModule.class,
85  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.encryptionFull");
86 
87  //zip bomb detection
88  private static final int MAX_DEPTH = 4;
89  private static final int MAX_COMPRESSION_RATIO = 600;
90  private static final long MIN_COMPRESSION_RATIO_SIZE = 500 * 1000000L;
91  private static final long MIN_FREE_DISK_SPACE = 1 * 1000 * 1000000L; //1GB
92 
93  private IngestServices services = IngestServices.getInstance();
94  private final IngestJobContext context;
95  private final FileTypeDetector fileTypeDetector;
96 
97  private String moduleDirRelative;
98  private String moduleDirAbsolute;
99 
100  private Blackboard blackboard;
101 
102  private ProgressHandle progress;
103  private int numItems;
104  private String currentArchiveName;
105 
106  private String getLocalRootAbsPath(String uniqueArchiveFileName) {
107  return moduleDirAbsolute + File.separator + uniqueArchiveFileName;
108  }
109 
114 
115  ZIP("application/zip"), //NON-NLS
116  SEVENZ("application/x-7z-compressed"), //NON-NLS
117  GZIP("application/gzip"), //NON-NLS
118  XGZIP("application/x-gzip"), //NON-NLS
119  XBZIP2("application/x-bzip2"), //NON-NLS
120  XTAR("application/x-tar"), //NON-NLS
121  XGTAR("application/x-gtar"),
122  XRAR("application/x-rar-compressed"); //NON-NLS
123 
124  private final String mimeType;
125 
126  SupportedArchiveExtractionFormats(final String mimeType) {
127  this.mimeType = mimeType;
128  }
129 
130  @Override
131  public String toString() {
132  return this.mimeType;
133  }
134  // TODO Expand to support more formats after upgrading Tika
135  }
136 
137  SevenZipExtractor(IngestJobContext context, FileTypeDetector fileTypeDetector, String moduleDirRelative, String moduleDirAbsolute) throws SevenZipNativeInitializationException {
138  if (!SevenZip.isInitializedSuccessfully() && (SevenZip.getLastInitializationException() == null)) {
139  SevenZip.initSevenZipFromPlatformJAR();
140  }
141  this.context = context;
142  this.fileTypeDetector = fileTypeDetector;
143  this.moduleDirRelative = moduleDirRelative;
144  this.moduleDirAbsolute = moduleDirAbsolute;
145  }
146 
155  boolean isSevenZipExtractionSupported(AbstractFile file) {
156  String fileMimeType = fileTypeDetector.getMIMEType(file);
157  for (SupportedArchiveExtractionFormats mimeType : SupportedArchiveExtractionFormats.values()) {
158  if (mimeType.toString().equals(fileMimeType)) {
159  return true;
160  }
161  }
162  return false;
163  }
164 
188  private boolean isZipBombArchiveItemCheck(AbstractFile archiveFile, ISevenZipInArchive inArchive, int inArchiveItemIndex, ConcurrentHashMap<Long, Archive> depthMap, String escapedFilePath) {
189  //If a file is corrupted as a result of reconstructing it from unallocated space, then
190  //7zip does a poor job estimating the original uncompressed file size.
191  //As a result, many corrupted files have wonky compression ratios and could flood the UI
192  //with false zip bomb notifications. The decision was made to skip compression ratio checks
193  //for unallocated zip files. Instead, we let the depth be an indicator of a zip bomb.
194  //Gzip archives compress a single file. They may have a sparse file,
195  //and that file could be much larger, however it won't be the exponential growth seen with more dangerous zip bombs.
196  //In addition a fair number of browser cache files will be gzip archives,
197  //and their file sizes are frequently retrieved incorrectly so ignoring gzip files is a reasonable decision.
198  if (archiveFile.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.UNALLOC) || archiveFile.getMIMEType().equalsIgnoreCase(SupportedArchiveExtractionFormats.XGZIP.toString())) {
199  return false;
200  }
201 
202  try {
203  final Long archiveItemSize = (Long) inArchive.getProperty(
204  inArchiveItemIndex, PropID.SIZE);
205 
206  //skip the check for small files
207  if (archiveItemSize == null || archiveItemSize < MIN_COMPRESSION_RATIO_SIZE) {
208  return false;
209  }
210 
211  final Long archiveItemPackedSize = (Long) inArchive.getProperty(
212  inArchiveItemIndex, PropID.PACKED_SIZE);
213 
214  if (archiveItemPackedSize == null || archiveItemPackedSize <= 0) {
215  logger.log(Level.WARNING, "Cannot getting compression ratio, cannot detect if zipbomb: {0}, item: {1}", //NON-NLS
216  new Object[]{archiveFile.getName(), (String) inArchive.getProperty(inArchiveItemIndex, PropID.PATH)}); //NON-NLS
217  return false;
218  }
219 
220  int cRatio = (int) (archiveItemSize / archiveItemPackedSize);
221 
222  if (cRatio >= MAX_COMPRESSION_RATIO) {
223  Archive rootArchive = depthMap.get(depthMap.get(archiveFile.getId()).getRootArchiveId());
224  String details = NbBundle.getMessage(SevenZipExtractor.class,
225  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnDetails",
226  cRatio, FileUtil.escapeFileName(getArchiveFilePath(rootArchive.getArchiveFile())));
227 
228  flagRootArchiveAsZipBomb(rootArchive, archiveFile, details, escapedFilePath);
229  return true;
230  } else {
231  return false;
232  }
233 
234  } catch (SevenZipException ex) {
235  logger.log(Level.WARNING, "Error getting archive item size and cannot detect if zipbomb. ", ex); //NON-NLS
236  return false;
237  }
238  }
239 
251  private void flagRootArchiveAsZipBomb(Archive rootArchive, AbstractFile archiveFile, String details, String escapedFilePath) {
252  rootArchive.flagAsZipBomb();
253  logger.log(Level.INFO, details);
254  try {
255  Collection<BlackboardAttribute> attributes = Arrays.asList(
256  new BlackboardAttribute(
257  TSK_SET_NAME, MODULE_NAME,
258  "Possible Zip Bomb"),
259  new BlackboardAttribute(
260  TSK_DESCRIPTION, MODULE_NAME,
261  Bundle.SevenZipExtractor_zipBombArtifactCreation_text(archiveFile.getName())),
262  new BlackboardAttribute(
263  TSK_COMMENT, MODULE_NAME,
264  details));
265 
266  if (!blackboard.artifactExists(archiveFile, TSK_INTERESTING_FILE_HIT, attributes)) {
267  BlackboardArtifact artifact = rootArchive.getArchiveFile().newArtifact(TSK_INTERESTING_FILE_HIT);
268  artifact.addAttributes(attributes);
269  try {
270  /*
271  * post the artifact which will index the artifact for
272  * keyword search, and fire an event to notify UI of this
273  * new artifact
274  */
275  blackboard.postArtifact(artifact, MODULE_NAME);
276 
277  String msg = NbBundle.getMessage(SevenZipExtractor.class,
278  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnMsg", archiveFile.getName(), escapedFilePath);//NON-NLS
279 
280  services.postMessage(IngestMessage.createWarningMessage(MODULE_NAME, msg, details));
281 
282  } catch (Blackboard.BlackboardException ex) {
283  logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex); //NON-NLS
284  MessageNotifyUtil.Notify.error(
285  Bundle.SevenZipExtractor_indexError_message(), artifact.getDisplayName());
286  }
287  }
288  } catch (TskCoreException ex) {
289  logger.log(Level.SEVERE, "Error creating blackboard artifact for Zip Bomb Detection for file: " + escapedFilePath, ex); //NON-NLS
290  }
291  }
292 
301  private ArchiveFormat get7ZipOptions(AbstractFile archiveFile) {
302  // try to get the file type from the BB
303  String detectedFormat;
304  detectedFormat = archiveFile.getMIMEType();
305 
306  if (detectedFormat == null) {
307  logger.log(Level.WARNING, "Could not detect format for file: {0}", archiveFile); //NON-NLS
308 
309  // if we don't have attribute info then use file extension
310  String extension = archiveFile.getNameExtension();
311  if ("rar".equals(extension)) //NON-NLS
312  {
313  // for RAR files we need to open them explicitly as RAR. Otherwise, if there is a ZIP archive inside RAR archive
314  // it will be opened incorrectly when using 7zip's built-in auto-detect functionality
315  return RAR;
316  }
317 
318  // Otherwise open the archive using 7zip's built-in auto-detect functionality
319  return null;
320  } else if (detectedFormat.contains("application/x-rar-compressed")) //NON-NLS
321  {
322  // for RAR files we need to open them explicitly as RAR. Otherwise, if there is a ZIP archive inside RAR archive
323  // it will be opened incorrectly when using 7zip's built-in auto-detect functionality
324  return RAR;
325  }
326 
327  // Otherwise open the archive using 7zip's built-in auto-detect functionality
328  return null;
329  }
330 
341  private long getRootArchiveId(AbstractFile file) throws TskCoreException {
342  long id = file.getId();
343  Content parentContent = file.getParent();
344  while (parentContent != null) {
345  id = parentContent.getId();
346  parentContent = parentContent.getParent();
347  }
348  return id;
349  }
350 
365  private List<AbstractFile> getAlreadyExtractedFiles(AbstractFile archiveFile, String archiveFilePath) throws TskCoreException, NoCurrentCaseException {
366  //check if already has derived files, skip
367  //check if local unpacked dir exists
368  if (archiveFile.hasChildren() && new File(moduleDirAbsolute, EmbeddedFileExtractorIngestModule.getUniqueName(archiveFile)).exists()) {
369  return Case.getCurrentCaseThrows().getServices().getFileManager().findFilesByParentPath(getRootArchiveId(archiveFile), archiveFilePath);
370  }
371  return new ArrayList<>();
372  }
373 
381  private String getArchiveFilePath(AbstractFile archiveFile) {
382  return archiveFile.getParentPath() + archiveFile.getName();
383  }
384 
391  private void makeLocalDirectories(String uniqueArchiveFileName) {
392  final String localRootAbsPath = getLocalRootAbsPath(uniqueArchiveFileName);
393  final File localRoot = new File(localRootAbsPath);
394  if (!localRoot.exists()) {
395  localRoot.mkdirs();
396  }
397  }
398 
411  private String getPathInArchive(ISevenZipInArchive archive, int inArchiveItemIndex, AbstractFile archiveFile) throws SevenZipException {
412  String pathInArchive = (String) archive.getProperty(
413  inArchiveItemIndex, PropID.PATH);
414 
415  if (pathInArchive == null || pathInArchive.isEmpty()) {
416  //some formats (.tar.gz) may not be handled correctly -- file in archive has no name/path
417  //handle this for .tar.gz and tgz but assuming the child is tar,
418  //otherwise, unpack using itemNumber as name
419 
420  //TODO this should really be signature based, not extension based
421  String archName = archiveFile.getName();
422  int dotI = archName.lastIndexOf(".");
423  String useName = null;
424  if (dotI != -1) {
425  String base = archName.substring(0, dotI);
426  String ext = archName.substring(dotI);
427  int colonIndex = ext.lastIndexOf(":");
428  if (colonIndex != -1) {
429  // If alternate data stream is found, fix the name
430  // so Windows doesn't choke on the colon character.
431  ext = ext.substring(0, colonIndex);
432  }
433  switch (ext) {
434  case ".gz": //NON-NLS
435  useName = base;
436  break;
437  case ".tgz": //NON-NLS
438  useName = base + ".tar"; //NON-NLS
439  break;
440  case ".bz2": //NON-NLS
441  useName = base;
442  break;
443  }
444  }
445  if (useName == null) {
446  pathInArchive = "/" + archName + "/" + Integer.toString(inArchiveItemIndex);
447  } else {
448  pathInArchive = "/" + useName;
449  }
450  String msg = NbBundle.getMessage(SevenZipExtractor.class,
451  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.unknownPath.msg",
452  getArchiveFilePath(archiveFile), pathInArchive);
453  logger.log(Level.WARNING, msg);
454  }
455  return pathInArchive;
456  }
457 
458  /*
459  * Get the String that will represent the key for the hashmap which keeps
460  * track of existing files from an AbstractFile
461  */
462  private String getKeyAbstractFile(AbstractFile fileInDatabase) {
463  return fileInDatabase == null ? null : fileInDatabase.getParentPath() + fileInDatabase.getName();
464  }
465 
466  /*
467  * Get the String that will represent the key for the hashmap which keeps
468  * track of existing files from an unpacked node and the archiveFilePath
469  */
470  private String getKeyFromUnpackedNode(UnpackedTree.UnpackedNode node, String archiveFilePath) {
471  return node == null ? null : archiveFilePath + "/" + node.getFileName();
472  }
473 
481  void unpack(AbstractFile archiveFile, ConcurrentHashMap<Long, Archive> depthMap) {
482  unpack(archiveFile, depthMap, null);
483  }
484 
496  @Messages({"SevenZipExtractor.indexError.message=Failed to index encryption detected artifact for keyword search.",
497  "# {0} - rootArchive",
498  "SevenZipExtractor.zipBombArtifactCreation.text=Zip Bomb Detected {0}"})
499  boolean unpack(AbstractFile archiveFile, ConcurrentHashMap<Long, Archive> depthMap, String password) {
500  boolean unpackSuccessful = true; //initialized to true change to false if any files fail to extract and
501  boolean hasEncrypted = false;
502  boolean fullEncryption = true;
503  boolean progressStarted = false;
504  final String archiveFilePath = getArchiveFilePath(archiveFile);
505  final String escapedArchiveFilePath = FileUtil.escapeFileName(archiveFilePath);
506  HashMap<String, ZipFileStatusWrapper> statusMap = new HashMap<>();
507  List<AbstractFile> unpackedFiles = Collections.<AbstractFile>emptyList();
508  ISevenZipInArchive inArchive = null;
509  currentArchiveName = archiveFile.getName();
510 
511  SevenZipContentReadStream stream = null;
512  progress = ProgressHandle.createHandle(Bundle.EmbeddedFileExtractorIngestModule_ArchiveExtractor_moduleName());
513  //recursion depth check for zip bomb
514  Archive parentAr;
515  try {
516  blackboard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard();
517  } catch (NoCurrentCaseException ex) {
518  logger.log(Level.INFO, "Exception while getting open case.", ex); //NON-NLS
519  unpackSuccessful = false;
520  return unpackSuccessful;
521  }
522  try {
523 
524  List<AbstractFile> existingFiles = getAlreadyExtractedFiles(archiveFile, archiveFilePath);
525  for (AbstractFile file : existingFiles) {
526  statusMap.put(getKeyAbstractFile(file), new ZipFileStatusWrapper(file, ZipFileStatus.EXISTS));
527  }
528  } catch (TskCoreException e) {
529  logger.log(Level.INFO, "Error checking if file already has been processed, skipping: {0}", escapedArchiveFilePath); //NON-NLS
530  unpackSuccessful = false;
531  return unpackSuccessful;
532  } catch (NoCurrentCaseException ex) {
533  logger.log(Level.INFO, "No open case was found while trying to unpack the archive file {0}", escapedArchiveFilePath); //NON-NLS
534  unpackSuccessful = false;
535  return unpackSuccessful;
536  }
537  parentAr = depthMap.get(archiveFile.getId());
538  if (parentAr == null) {
539  parentAr = new Archive(0, archiveFile.getId(), archiveFile);
540  depthMap.put(archiveFile.getId(), parentAr);
541  } else {
542  Archive rootArchive = depthMap.get(parentAr.getRootArchiveId());
543  if (rootArchive.isFlaggedAsZipBomb()) {
544  //skip this archive as the root archive has already been determined to contain a zip bomb
545  unpackSuccessful = false;
546  return unpackSuccessful;
547  } else if (parentAr.getDepth() == MAX_DEPTH) {
548  String details = NbBundle.getMessage(SevenZipExtractor.class,
549  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnDetails.zipBomb",
550  parentAr.getDepth(), FileUtil.escapeFileName(getArchiveFilePath(rootArchive.getArchiveFile())));
551  flagRootArchiveAsZipBomb(rootArchive, archiveFile, details, escapedArchiveFilePath);
552  unpackSuccessful = false;
553  return unpackSuccessful;
554  }
555  }
556  try {
557  stream = new SevenZipContentReadStream(new ReadContentInputStream(archiveFile));
558  // for RAR files we need to open them explicitly as RAR. Otherwise, if there is a ZIP archive inside RAR archive
559  // it will be opened incorrectly when using 7zip's built-in auto-detect functionality.
560  // All other archive formats are still opened using 7zip built-in auto-detect functionality.
561  ArchiveFormat options = get7ZipOptions(archiveFile);
562  if (password == null) {
563  inArchive = SevenZip.openInArchive(options, stream);
564  } else {
565  inArchive = SevenZip.openInArchive(options, stream, password);
566  }
567  numItems = inArchive.getNumberOfItems();
568  progress.start(numItems);
569  progressStarted = true;
570 
571  //setup the archive local root folder
572  final String uniqueArchiveFileName = FileUtil.escapeFileName(EmbeddedFileExtractorIngestModule.getUniqueName(archiveFile));
573  try {
574  makeLocalDirectories(uniqueArchiveFileName);
575  } catch (SecurityException e) {
576  logger.log(Level.SEVERE, "Error setting up output path for archive root: {0}", getLocalRootAbsPath(uniqueArchiveFileName)); //NON-NLS
577  //bail
578  unpackSuccessful = false;
579  return unpackSuccessful;
580  }
581 
582  //initialize tree hierarchy to keep track of unpacked file structure
583  SevenZipExtractor.UnpackedTree unpackedTree = new SevenZipExtractor.UnpackedTree(moduleDirRelative + "/" + uniqueArchiveFileName, archiveFile);
584 
585  long freeDiskSpace;
586  try {
587  freeDiskSpace = services.getFreeDiskSpace();
588  } catch (NullPointerException ex) {
589  //If ingest has not been run at least once getFreeDiskSpace() will throw a null pointer exception
590  //currently getFreeDiskSpace always returns DISK_FREE_SPACE_UNKNOWN
591  freeDiskSpace = IngestMonitor.DISK_FREE_SPACE_UNKNOWN;
592  }
593 
594  Map<Integer, InArchiveItemDetails> archiveDetailsMap = new HashMap<>();
595  for (int inArchiveItemIndex = 0; inArchiveItemIndex < numItems; inArchiveItemIndex++) {
596  progress.progress(String.format("%s: Analyzing archive metadata and creating local files (%d of %d)", currentArchiveName, inArchiveItemIndex + 1, numItems), 0);
597  if (isZipBombArchiveItemCheck(archiveFile, inArchive, inArchiveItemIndex, depthMap, escapedArchiveFilePath)) {
598  unpackSuccessful = false;
599  return unpackSuccessful;
600  }
601 
602  String pathInArchive = getPathInArchive(inArchive, inArchiveItemIndex, archiveFile);
603  UnpackedTree.UnpackedNode unpackedNode = unpackedTree.addNode(pathInArchive);
604 
605  final boolean isEncrypted = (Boolean) inArchive.getProperty(inArchiveItemIndex, PropID.ENCRYPTED);
606 
607  if (isEncrypted && password == null) {
608  logger.log(Level.WARNING, "Skipping encrypted file in archive: {0}", pathInArchive); //NON-NLS
609  hasEncrypted = true;
610  unpackSuccessful = false;
611  continue;
612  } else {
613  fullEncryption = false;
614  }
615 
616  // NOTE: item size may return null in case of certain
617  // archiving formats. Eg: BZ2
618  //check if unpacking this file will result in out of disk space
619  //this is additional to zip bomb prevention mechanism
620  Long archiveItemSize = (Long) inArchive.getProperty(
621  inArchiveItemIndex, PropID.SIZE);
622  if (freeDiskSpace != IngestMonitor.DISK_FREE_SPACE_UNKNOWN && archiveItemSize != null && archiveItemSize > 0) { //if free space is known and file is not empty.
623  String archiveItemPath = (String) inArchive.getProperty(
624  inArchiveItemIndex, PropID.PATH);
625  long newDiskSpace = freeDiskSpace - archiveItemSize;
626  if (newDiskSpace < MIN_FREE_DISK_SPACE) {
627  String msg = NbBundle.getMessage(SevenZipExtractor.class,
628  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.msg",
629  escapedArchiveFilePath, archiveItemPath);
630  String details = NbBundle.getMessage(SevenZipExtractor.class,
631  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.details");
632  services.postMessage(IngestMessage.createErrorMessage(MODULE_NAME, msg, details));
633  logger.log(Level.INFO, "Skipping archive item due to insufficient disk space: {0}, {1}", new String[]{escapedArchiveFilePath, archiveItemPath}); //NON-NLS
634  logger.log(Level.INFO, "Available disk space: {0}", new Object[]{freeDiskSpace}); //NON-NLS
635  unpackSuccessful = false;
636  continue; //skip this file
637  } else {
638  //update est. disk space during this archive, so we don't need to poll for every file extracted
639  freeDiskSpace = newDiskSpace;
640  }
641  }
642  final String uniqueExtractedName = FileUtil.escapeFileName(uniqueArchiveFileName + File.separator + (inArchiveItemIndex / 1000) + File.separator + inArchiveItemIndex + "_" + new File(pathInArchive).getName());
643  final String localAbsPath = moduleDirAbsolute + File.separator + uniqueExtractedName;
644  final String localRelPath = moduleDirRelative + File.separator + uniqueExtractedName;
645 
646  //create local dirs and empty files before extracted
647  File localFile = new java.io.File(localAbsPath);
648  //cannot rely on files in top-bottom order
649  if (!localFile.exists()) {
650  try {
651  if ((Boolean) inArchive.getProperty(
652  inArchiveItemIndex, PropID.IS_FOLDER)) {
653  localFile.mkdirs();
654  } else {
655  localFile.getParentFile().mkdirs();
656  try {
657  localFile.createNewFile();
658  } catch (IOException e) {
659  logger.log(Level.SEVERE, "Error creating extracted file: "//NON-NLS
660  + localFile.getAbsolutePath(), e);
661  }
662  }
663  } catch (SecurityException e) {
664  logger.log(Level.SEVERE, "Error setting up output path for unpacked file: {0}", //NON-NLS
665  pathInArchive); //NON-NLS
666  //TODO consider bail out / msg to the user
667  }
668  }
669  // skip the rest of this loop if we couldn't create the file
670  //continue will skip details from being added to the map
671  if (localFile.exists() == false) {
672  continue;
673  }
674 
675  //Store archiveItemIndex with local paths and unpackedNode reference.
676  //Necessary for the extract call back to write the current archive
677  //file to the correct disk location and to correctly update it's
678  //corresponding unpackedNode
679  archiveDetailsMap.put(inArchiveItemIndex, new InArchiveItemDetails(
680  unpackedNode, localAbsPath, localRelPath));
681  }
682 
683  int[] extractionIndices = getExtractableFilesFromDetailsMap(archiveDetailsMap);
684 
685  StandardIArchiveExtractCallback archiveCallBack
686  = new StandardIArchiveExtractCallback(
687  inArchive, archiveFile, progress,
688  archiveDetailsMap, password, freeDiskSpace);
689 
690  //According to the documentation, indices in sorted order are optimal
691  //for efficiency. Hence, the HashMap and linear processing of
692  //inArchiveItemIndex. False indicates non-test mode
693  inArchive.extract(extractionIndices, false, archiveCallBack);
694 
695  unpackSuccessful &= archiveCallBack.wasSuccessful();
696 
697  archiveDetailsMap = null;
698 
699  // add them to the DB. We wait until the end so that we have the metadata on all of the
700  // intermediate nodes since the order is not guaranteed
701  try {
702  unpackedTree.updateOrAddFileToCaseRec(statusMap, archiveFilePath);
703  unpackedFiles = unpackedTree.getAllFileObjects();
704  //check if children are archives, update archive depth tracking
705  for (int i = 0; i < unpackedFiles.size(); i++) {
706  progress.progress(String.format("%s: Searching for nested archives (%d of %d)", currentArchiveName, i + 1, unpackedFiles.size()));
707  AbstractFile unpackedFile = unpackedFiles.get(i);
708  if (unpackedFile == null) {
709  continue;
710  }
711  if (isSevenZipExtractionSupported(unpackedFile)) {
712  Archive child = new Archive(parentAr.getDepth() + 1, parentAr.getRootArchiveId(), archiveFile);
713  parentAr.addChild(child);
714  depthMap.put(unpackedFile.getId(), child);
715  }
716  unpackedFile.close();
717  }
718 
719  } catch (TskCoreException | NoCurrentCaseException e) {
720  logger.log(Level.SEVERE, "Error populating complete derived file hierarchy from the unpacked dir structure", e); //NON-NLS
721  //TODO decide if anything to cleanup, for now bailing
722  }
723 
724  } catch (SevenZipException ex) {
725  logger.log(Level.WARNING, "Error unpacking file: " + archiveFile, ex); //NON-NLS
726  //inbox message
727 
728  // print a message if the file is allocated
729  if (archiveFile.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC)) {
730  String msg = NbBundle.getMessage(SevenZipExtractor.class,
731  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.errUnpacking.msg",
732  currentArchiveName);
733  String details = NbBundle.getMessage(SevenZipExtractor.class,
734  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.errUnpacking.details",
735  escapedArchiveFilePath, ex.getMessage());
736  services.postMessage(IngestMessage.createErrorMessage(MODULE_NAME, msg, details));
737  }
738  } finally {
739  if (inArchive != null) {
740  try {
741  inArchive.close();
742  } catch (SevenZipException e) {
743  logger.log(Level.SEVERE, "Error closing archive: " + archiveFile, e); //NON-NLS
744  }
745  }
746 
747  if (stream != null) {
748  try {
749  stream.close();
750  } catch (IOException ex) {
751  logger.log(Level.SEVERE, "Error closing stream after unpacking archive: " + archiveFile, ex); //NON-NLS
752  }
753  }
754 
755  //close progress bar
756  if (progressStarted) {
757  progress.finish();
758  }
759  }
760 
761  //create artifact and send user message
762  if (hasEncrypted) {
763  String encryptionType = fullEncryption ? ENCRYPTION_FULL : ENCRYPTION_FILE_LEVEL;
764  try {
765  BlackboardArtifact artifact = archiveFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED);
766  artifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT, MODULE_NAME, encryptionType));
767 
768  try {
769  /*
770  * post the artifact which will index the artifact for
771  * keyword search, and fire an event to notify UI of this
772  * new artifact
773  */
774  blackboard.postArtifact(artifact, MODULE_NAME);
775  } catch (Blackboard.BlackboardException ex) {
776  logger.log(Level.SEVERE, "Unable to post blackboard artifact " + artifact.getArtifactID(), ex); //NON-NLS
777  MessageNotifyUtil.Notify.error(
778  Bundle.SevenZipExtractor_indexError_message(), artifact.getDisplayName());
779  }
780 
781  } catch (TskCoreException ex) {
782  logger.log(Level.SEVERE, "Error creating blackboard artifact for encryption detected for file: " + escapedArchiveFilePath, ex); //NON-NLS
783  }
784 
785  String msg = NbBundle.getMessage(SevenZipExtractor.class,
786  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.encrFileDetected.msg");
787  String details = NbBundle.getMessage(SevenZipExtractor.class,
788  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.encrFileDetected.details",
789  currentArchiveName, MODULE_NAME);
790  services.postMessage(IngestMessage.createWarningMessage(MODULE_NAME, msg, details));
791  }
792 
793  // adding unpacked extracted derived files to the job after closing relevant resources.
794  if (!unpackedFiles.isEmpty()) {
795  //currently sending a single event for all new files
796  services.fireModuleContentEvent(new ModuleContentEvent(archiveFile));
797  if (context != null) {
798  context.addFilesToJob(unpackedFiles);
799  }
800  }
801  return unpackSuccessful;
802  }
803 
808  private int[] getExtractableFilesFromDetailsMap(
809  Map<Integer, InArchiveItemDetails> archiveDetailsMap) {
810 
811  Integer[] wrappedExtractionIndices = archiveDetailsMap.keySet()
812  .toArray(new Integer[archiveDetailsMap.size()]);
813 
814  return Arrays.stream(wrappedExtractionIndices)
815  .mapToInt(Integer::intValue)
816  .toArray();
817  }
818 
826  private final static class UnpackStream implements ISequentialOutStream {
827 
828  private EncodedFileOutputStream output;
829  private String localAbsPath;
830  private int bytesWritten;
831 
832  UnpackStream(String localAbsPath) throws IOException {
833  this.output = new EncodedFileOutputStream(new FileOutputStream(localAbsPath), TskData.EncodingType.XOR1);
834  this.localAbsPath = localAbsPath;
835  this.bytesWritten = 0;
836  }
837 
838  public void setNewOutputStream(String localAbsPath) throws IOException {
839  this.output.close();
840  this.output = new EncodedFileOutputStream(new FileOutputStream(localAbsPath), TskData.EncodingType.XOR1);
841  this.localAbsPath = localAbsPath;
842  this.bytesWritten = 0;
843  }
844 
845  public int getSize() {
846  return bytesWritten;
847  }
848 
849  @Override
850  public int write(byte[] bytes) throws SevenZipException {
851  try {
852  output.write(bytes);
853  this.bytesWritten += bytes.length;
854  } catch (IOException ex) {
855  throw new SevenZipException(
856  NbBundle.getMessage(SevenZipExtractor.class,
857  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackStream.write.exception.msg",
858  localAbsPath), ex);
859  }
860  return bytes.length;
861  }
862 
863  public void close() throws IOException {
864  try (EncodedFileOutputStream out = output) {
865  out.flush();
866  }
867  }
868 
869  }
870 
874  private static class InArchiveItemDetails {
875 
876  private final SevenZipExtractor.UnpackedTree.UnpackedNode unpackedNode;
877  private final String localAbsPath;
878  private final String localRelPath;
879 
881  SevenZipExtractor.UnpackedTree.UnpackedNode unpackedNode,
882  String localAbsPath, String localRelPath) {
883  this.unpackedNode = unpackedNode;
884  this.localAbsPath = localAbsPath;
885  this.localRelPath = localRelPath;
886  }
887 
888  public SevenZipExtractor.UnpackedTree.UnpackedNode getUnpackedNode() {
889  return unpackedNode;
890  }
891 
892  public String getLocalAbsPath() {
893  return localAbsPath;
894  }
895 
896  public String getLocalRelPath() {
897  return localRelPath;
898  }
899  }
900 
905  private static class StandardIArchiveExtractCallback
906  implements IArchiveExtractCallback, ICryptoGetTextPassword {
907 
908  private final AbstractFile archiveFile;
909  private final ISevenZipInArchive inArchive;
910  private UnpackStream unpackStream = null;
911  private final Map<Integer, InArchiveItemDetails> archiveDetailsMap;
912  private final ProgressHandle progressHandle;
913 
914  private int inArchiveItemIndex;
915 
916  private long createTimeInSeconds;
917  private long modTimeInSeconds;
918  private long accessTimeInSeconds;
919 
920  private boolean isFolder;
921  private final String password;
922 
923  private boolean unpackSuccessful = true;
924 
925  StandardIArchiveExtractCallback(ISevenZipInArchive inArchive,
926  AbstractFile archiveFile, ProgressHandle progressHandle,
927  Map<Integer, InArchiveItemDetails> archiveDetailsMap,
928  String password, long freeDiskSpace) {
929 
930  this.inArchive = inArchive;
931  this.progressHandle = progressHandle;
932  this.archiveFile = archiveFile;
933  this.archiveDetailsMap = archiveDetailsMap;
934  this.password = password;
935  }
936 
951  @Override
952  public ISequentialOutStream getStream(int inArchiveItemIndex,
953  ExtractAskMode mode) throws SevenZipException {
954 
955  this.inArchiveItemIndex = inArchiveItemIndex;
956 
957  isFolder = (Boolean) inArchive
958  .getProperty(inArchiveItemIndex, PropID.IS_FOLDER);
959  if (isFolder || mode != ExtractAskMode.EXTRACT) {
960  return null;
961  }
962 
963  final String localAbsPath = archiveDetailsMap.get(
964  inArchiveItemIndex).getLocalAbsPath();
965 
966  //If the Unpackstream has been allocated, then set the Outputstream
967  //to another file rather than creating a new unpack stream. The 7Zip
968  //binding has a memory leak, so creating new unpack streams will not be
969  //dereferenced. As a fix, we create one UnpackStream, and mutate its state,
970  //so that there only exists one 8192 byte buffer in memory per archive.
971  try {
972  if (unpackStream != null) {
973  unpackStream.setNewOutputStream(localAbsPath);
974  } else {
975  unpackStream = new UnpackStream(localAbsPath);
976  }
977  } catch (IOException ex) {
978  logger.log(Level.WARNING, String.format("Error opening or setting new stream " //NON-NLS
979  + "for archive file at %s", localAbsPath), ex.getMessage()); //NON-NLS
980  return null;
981  }
982 
983  return unpackStream;
984  }
985 
994  @Override
995  public void prepareOperation(ExtractAskMode mode) throws SevenZipException {
996  final Date createTime = (Date) inArchive.getProperty(
997  inArchiveItemIndex, PropID.CREATION_TIME);
998  final Date accessTime = (Date) inArchive.getProperty(
999  inArchiveItemIndex, PropID.LAST_ACCESS_TIME);
1000  final Date writeTime = (Date) inArchive.getProperty(
1001  inArchiveItemIndex, PropID.LAST_WRITE_TIME);
1002 
1003  createTimeInSeconds = createTime == null ? 0L
1004  : createTime.getTime() / 1000;
1005  modTimeInSeconds = writeTime == null ? 0L
1006  : writeTime.getTime() / 1000;
1007  accessTimeInSeconds = accessTime == null ? 0L
1008  : accessTime.getTime() / 1000;
1009 
1010  progressHandle.progress(archiveFile.getName() + ": "
1011  + (String) inArchive.getProperty(inArchiveItemIndex, PropID.PATH),
1013 
1014  }
1015 
1024  @Override
1025  public void setOperationResult(ExtractOperationResult result) throws SevenZipException {
1026 
1027  final SevenZipExtractor.UnpackedTree.UnpackedNode unpackedNode
1028  = archiveDetailsMap.get(inArchiveItemIndex).getUnpackedNode();
1029  final String localRelPath = archiveDetailsMap.get(
1030  inArchiveItemIndex).getLocalRelPath();
1031  if (isFolder) {
1032  unpackedNode.addDerivedInfo(0,
1033  !(Boolean) inArchive.getProperty(inArchiveItemIndex, PropID.IS_FOLDER),
1035  localRelPath);
1036  return;
1037  }
1038 
1039  final String localAbsPath = archiveDetailsMap.get(
1040  inArchiveItemIndex).getLocalAbsPath();
1041  if (result != ExtractOperationResult.OK) {
1042  logger.log(Level.WARNING, "Extraction of : {0} encountered error {1}", //NON-NLS
1043  new Object[]{localAbsPath, result});
1044  unpackSuccessful = false;
1045  }
1046 
1047  //record derived data in unode, to be traversed later after unpacking the archive
1048  unpackedNode.addDerivedInfo(unpackStream.getSize(),
1049  !(Boolean) inArchive.getProperty(inArchiveItemIndex, PropID.IS_FOLDER),
1051 
1052  try {
1053  unpackStream.close();
1054  } catch (IOException e) {
1055  logger.log(Level.WARNING, "Error closing unpack stream for file: {0}", localAbsPath); //NON-NLS
1056  }
1057  }
1058 
1059  @Override
1060  public void setTotal(long value) throws SevenZipException {
1061  //Not necessary for extract, left intenionally blank
1062  }
1063 
1064  @Override
1065  public void setCompleted(long value) throws SevenZipException {
1066  //Not necessary for extract, left intenionally blank
1067  }
1068 
1076  @Override
1077  public String cryptoGetTextPassword() throws SevenZipException {
1078  return password;
1079  }
1080 
1081  public boolean wasSuccessful() {
1082  return unpackSuccessful;
1083  }
1084  }
1085 
1093  private class UnpackedTree {
1094 
1095  final UnpackedNode rootNode;
1096  private int nodesProcessed = 0;
1097 
1105  UnpackedTree(String localPathRoot, AbstractFile archiveFile) {
1106  this.rootNode = new UnpackedNode();
1107  this.rootNode.setFile(archiveFile);
1108  this.rootNode.setFileName(archiveFile.getName());
1109  this.rootNode.setLocalRelPath(localPathRoot);
1110  }
1111 
1121  UnpackedNode addNode(String filePath) {
1122  String[] toks = filePath.split("[\\/\\\\]");
1123  List<String> tokens = new ArrayList<>();
1124  for (int i = 0; i < toks.length; ++i) {
1125  if (!toks[i].isEmpty()) {
1126  tokens.add(toks[i]);
1127  }
1128  }
1129  return addNode(rootNode, tokens);
1130  }
1131 
1140  private UnpackedNode addNode(UnpackedNode parent, List<String> tokenPath) {
1141  // we found all of the tokens
1142  if (tokenPath.isEmpty()) {
1143  return parent;
1144  }
1145 
1146  // get the next name in the path and look it up
1147  String childName = tokenPath.remove(0);
1148  UnpackedNode child = parent.getChild(childName);
1149  // create new node
1150  if (child == null) {
1151  child = new UnpackedNode(childName, parent);
1152  parent.addChild(child);
1153  }
1154 
1155  // go down one more level
1156  return addNode(child, tokenPath);
1157  }
1158 
1165  List<AbstractFile> getRootFileObjects() {
1166  List<AbstractFile> ret = new ArrayList<>();
1167  rootNode.getChildren().forEach((child) -> {
1168  ret.add(child.getFile());
1169  });
1170  return ret;
1171  }
1172 
1179  List<AbstractFile> getAllFileObjects() {
1180  List<AbstractFile> ret = new ArrayList<>();
1181  rootNode.getChildren().forEach((child) -> {
1182  getAllFileObjectsRec(ret, child);
1183  });
1184  return ret;
1185  }
1186 
1187  private void getAllFileObjectsRec(List<AbstractFile> list, UnpackedNode parent) {
1188  list.add(parent.getFile());
1189  parent.getChildren().forEach((child) -> {
1190  getAllFileObjectsRec(list, child);
1191  });
1192  }
1193 
1198  void updateOrAddFileToCaseRec(HashMap<String, ZipFileStatusWrapper> statusMap, String archiveFilePath) throws TskCoreException, NoCurrentCaseException {
1200  for (UnpackedNode child : rootNode.getChildren()) {
1201  updateOrAddFileToCaseRec(child, fileManager, statusMap, archiveFilePath);
1202  }
1203  }
1204 
1219  private void updateOrAddFileToCaseRec(UnpackedNode node, FileManager fileManager, HashMap<String, ZipFileStatusWrapper> statusMap, String archiveFilePath) throws TskCoreException {
1220  DerivedFile df;
1221  progress.progress(String.format("%s: Adding/updating files in case database (%d of %d)", currentArchiveName, ++nodesProcessed, numItems));
1222  try {
1223  String nameInDatabase = getKeyFromUnpackedNode(node, archiveFilePath);
1224  ZipFileStatusWrapper existingFile = nameInDatabase == null ? null : statusMap.get(nameInDatabase);
1225  if (existingFile == null) {
1226  df = fileManager.addDerivedFile(node.getFileName(), node.getLocalRelPath(), node.getSize(),
1227  node.getCtime(), node.getCrtime(), node.getAtime(), node.getMtime(),
1228  node.isIsFile(), node.getParent().getFile(), "", MODULE_NAME,
1229  "", "", TskData.EncodingType.XOR1);
1230  statusMap.put(getKeyAbstractFile(df), new ZipFileStatusWrapper(df, ZipFileStatus.EXISTS));
1231  } else {
1232  String key = getKeyAbstractFile(existingFile.getFile());
1233  if (existingFile.getStatus() == ZipFileStatus.EXISTS && existingFile.getFile().getSize() < node.getSize()) {
1234  existingFile.setStatus(ZipFileStatus.UPDATE);
1235  statusMap.put(key, existingFile);
1236  }
1237  if (existingFile.getStatus() == ZipFileStatus.UPDATE) {
1238  //if the we are updating a file and its mime type was octet-stream we want to re-type it
1239  String mimeType = existingFile.getFile().getMIMEType().equalsIgnoreCase("application/octet-stream") ? null : existingFile.getFile().getMIMEType();
1240  df = fileManager.updateDerivedFile((DerivedFile) existingFile.getFile(), node.getLocalRelPath(), node.getSize(),
1241  node.getCtime(), node.getCrtime(), node.getAtime(), node.getMtime(),
1242  node.isIsFile(), mimeType, "", MODULE_NAME,
1243  "", "", TskData.EncodingType.XOR1);
1244  } else {
1245  //ALREADY CURRENT - SKIP
1246  statusMap.put(key, new ZipFileStatusWrapper(existingFile.getFile(), ZipFileStatus.SKIP));
1247  df = (DerivedFile) existingFile.getFile();
1248  }
1249  }
1250  node.setFile(df);
1251  } catch (TskCoreException ex) {
1252  logger.log(Level.SEVERE, "Error adding a derived file to db:" + node.getFileName(), ex); //NON-NLS
1253  throw new TskCoreException(
1254  NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackedTree.exception.msg",
1255  node.getFileName()), ex);
1256  }
1257  //recurse adding the children if this file was incomplete the children presumably need to be added
1258  for (UnpackedNode child : node.getChildren()) {
1259  updateOrAddFileToCaseRec(child, fileManager, statusMap, getKeyFromUnpackedNode(node, archiveFilePath));
1260  }
1261  }
1262 
1266  private class UnpackedNode {
1267 
1268  private String fileName;
1269  private AbstractFile file;
1270  private final List<UnpackedNode> children = new ArrayList<>();
1271  private String localRelPath = "";
1272  private long size;
1273  private long ctime, crtime, atime, mtime;
1274  private boolean isFile;
1276 
1277  //root constructor
1278  UnpackedNode() {
1279  }
1280 
1281  //child node constructor
1282  UnpackedNode(String fileName, UnpackedNode parent) {
1283  this.fileName = fileName;
1284  this.parent = parent;
1285  this.localRelPath = parent.getLocalRelPath() + File.separator + fileName;
1286  }
1287 
1288  long getCtime() {
1289  return ctime;
1290  }
1291 
1292  long getCrtime() {
1293  return crtime;
1294  }
1295 
1296  long getAtime() {
1297  return atime;
1298  }
1299 
1300  long getMtime() {
1301  return mtime;
1302  }
1303 
1304  void setFileName(String fileName) {
1305  this.fileName = fileName;
1306  }
1307 
1313  void addChild(UnpackedNode child) {
1314  children.add(child);
1315  }
1316 
1323  List<UnpackedNode> getChildren() {
1324  return children;
1325  }
1326 
1332  UnpackedNode getParent() {
1333  return parent;
1334  }
1335 
1336  void addDerivedInfo(long size,
1337  boolean isFile,
1338  long ctime, long crtime, long atime, long mtime, String relLocalPath) {
1339  this.size = size;
1340  this.isFile = isFile;
1341  this.ctime = ctime;
1342  this.crtime = crtime;
1343  this.atime = atime;
1344  this.mtime = mtime;
1345  this.localRelPath = relLocalPath;
1346  }
1347 
1348  void setFile(AbstractFile file) {
1349  this.file = file;
1350  }
1351 
1359  UnpackedNode getChild(String childFileName) {
1360  UnpackedNode ret = null;
1361  for (UnpackedNode child : children) {
1362  if (child.getFileName().equals(childFileName)) {
1363  ret = child;
1364  break;
1365  }
1366  }
1367  return ret;
1368  }
1369 
1370  String getFileName() {
1371  return fileName;
1372  }
1373 
1374  AbstractFile getFile() {
1375  return file;
1376  }
1377 
1378  String getLocalRelPath() {
1379  return localRelPath;
1380  }
1381 
1388  void setLocalRelPath(String localRelativePath) {
1389  localRelPath = localRelativePath;
1390  }
1391 
1392  long getSize() {
1393  return size;
1394  }
1395 
1396  boolean isIsFile() {
1397  return isFile;
1398  }
1399  }
1400  }
1401 
1406  static class Archive {
1407 
1408  //depth will be 0 for the root archive unpack was called on, and increase as unpack recurses down through archives contained within
1409  private final int depth;
1410  private final List<Archive> children;
1411  private final long rootArchiveId;
1412  private boolean flaggedAsZipBomb = false;
1413  private final AbstractFile archiveFile;
1414 
1427  Archive(int depth, long rootArchiveId, AbstractFile archiveFile) {
1428  this.children = new ArrayList<>();
1429  this.depth = depth;
1430  this.rootArchiveId = rootArchiveId;
1431  this.archiveFile = archiveFile;
1432  }
1433 
1440  void addChild(Archive child) {
1441  children.add(child);
1442  }
1443 
1448  synchronized void flagAsZipBomb() {
1449  flaggedAsZipBomb = true;
1450  }
1451 
1457  synchronized boolean isFlaggedAsZipBomb() {
1458  return flaggedAsZipBomb;
1459  }
1460 
1466  AbstractFile getArchiveFile() {
1467  return archiveFile;
1468  }
1469 
1475  long getRootArchiveId() {
1476  return rootArchiveId;
1477  }
1478 
1484  long getObjectId() {
1485  return archiveFile.getId();
1486  }
1487 
1495  int getDepth() {
1496  return depth;
1497  }
1498  }
1499 
1504  private final class ZipFileStatusWrapper {
1505 
1506  private final AbstractFile abstractFile;
1508 
1516  private ZipFileStatusWrapper(AbstractFile file, ZipFileStatus status) {
1517  abstractFile = file;
1518  zipStatus = status;
1519  }
1520 
1526  private AbstractFile getFile() {
1527  return abstractFile;
1528  }
1529 
1536  return zipStatus;
1537  }
1538 
1544  private void setStatus(ZipFileStatus status) {
1545  zipStatus = status;
1546  }
1547 
1548  }
1549 
1554  private enum ZipFileStatus {
1555  UPDATE, //Should be updated //NON-NLS
1556  SKIP, //File is current can be skipped //NON-NLS
1557  EXISTS //File exists but it is unknown if it is current //NON-NLS
1558  }
1559 }
void updateOrAddFileToCaseRec(UnpackedNode node, FileManager fileManager, HashMap< String, ZipFileStatusWrapper > statusMap, String archiveFilePath)
synchronized DerivedFile addDerivedFile(String fileName, String localPath, long size, long ctime, long crtime, long atime, long mtime, boolean isFile, Content parentObj, String rederiveDetails, String toolName, String toolVersion, String otherDetails, TskData.EncodingType encodingType)
UnpackedNode addNode(UnpackedNode parent, List< String > tokenPath)
synchronized DerivedFile updateDerivedFile(DerivedFile derivedFile, String localPath, long size, long ctime, long crtime, long atime, long mtime, boolean isFile, String mimeType, String rederiveDetails, String toolName, String toolVersion, String otherDetails, TskData.EncodingType encodingType)

Copyright © 2012-2018 Basis Technology. Generated on: Wed Sep 18 2019
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.