Autopsy  4.14.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.nio.charset.Charset;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.Collection;
28 import java.util.Collections;
29 import java.util.Date;
30 import java.util.HashMap;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.concurrent.ConcurrentHashMap;
34 import java.util.logging.Level;
35 import net.sf.sevenzipjbinding.ArchiveFormat;
36 import static net.sf.sevenzipjbinding.ArchiveFormat.RAR;
37 import net.sf.sevenzipjbinding.ExtractAskMode;
38 import net.sf.sevenzipjbinding.ExtractOperationResult;
39 import net.sf.sevenzipjbinding.IArchiveExtractCallback;
40 import net.sf.sevenzipjbinding.ICryptoGetTextPassword;
41 import net.sf.sevenzipjbinding.IInArchive;
42 import net.sf.sevenzipjbinding.ISequentialOutStream;
43 import net.sf.sevenzipjbinding.PropID;
44 import net.sf.sevenzipjbinding.SevenZip;
45 import net.sf.sevenzipjbinding.SevenZipException;
46 import net.sf.sevenzipjbinding.SevenZipNativeInitializationException;
47 import org.apache.tika.parser.txt.CharsetDetector;
48 import org.apache.tika.parser.txt.CharsetMatch;
49 import org.netbeans.api.progress.ProgressHandle;
50 import org.openide.util.NbBundle;
51 import org.openide.util.NbBundle.Messages;
64 import org.sleuthkit.datamodel.AbstractFile;
65 import org.sleuthkit.datamodel.Blackboard;
66 import org.sleuthkit.datamodel.BlackboardArtifact;
67 import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT;
68 import org.sleuthkit.datamodel.BlackboardAttribute;
69 import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT;
70 import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DESCRIPTION;
71 import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME;
72 import org.sleuthkit.datamodel.Content;
73 import org.sleuthkit.datamodel.DerivedFile;
74 import org.sleuthkit.datamodel.EncodedFileOutputStream;
75 import org.sleuthkit.datamodel.ReadContentInputStream;
76 import org.sleuthkit.datamodel.TskCoreException;
77 import org.sleuthkit.datamodel.TskData;
78 
79 class SevenZipExtractor {
80 
81  private static final Logger logger = Logger.getLogger(SevenZipExtractor.class.getName());
82 
83  private static final String MODULE_NAME = EmbeddedFileExtractorModuleFactory.getModuleName();
84 
85  //encryption type strings
86  private static final String ENCRYPTION_FILE_LEVEL = NbBundle.getMessage(EmbeddedFileExtractorIngestModule.class,
87  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.encryptionFileLevel");
88  private static final String ENCRYPTION_FULL = NbBundle.getMessage(EmbeddedFileExtractorIngestModule.class,
89  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.encryptionFull");
90 
91  //zip bomb detection
92  private static final int MAX_DEPTH = 4;
93  private static final int MAX_COMPRESSION_RATIO = 600;
94  private static final long MIN_COMPRESSION_RATIO_SIZE = 500 * 1000000L;
95  private static final long MIN_FREE_DISK_SPACE = 1 * 1000 * 1000000L; //1GB
96 
97  private IngestServices services = IngestServices.getInstance();
98  private final IngestJobContext context;
99  private final FileTypeDetector fileTypeDetector;
100 
101  private String moduleDirRelative;
102  private String moduleDirAbsolute;
103 
104  private Blackboard blackboard;
105 
106  private ProgressHandle progress;
107  private int numItems;
108  private String currentArchiveName;
109 
110  private String getLocalRootAbsPath(String uniqueArchiveFileName) {
111  return moduleDirAbsolute + File.separator + uniqueArchiveFileName;
112  }
113 
118 
119  ZIP("application/zip"), //NON-NLS
120  SEVENZ("application/x-7z-compressed"), //NON-NLS
121  GZIP("application/gzip"), //NON-NLS
122  XGZIP("application/x-gzip"), //NON-NLS
123  XBZIP2("application/x-bzip2"), //NON-NLS
124  XTAR("application/x-tar"), //NON-NLS
125  XGTAR("application/x-gtar"),
126  XRAR("application/x-rar-compressed"); //NON-NLS
127 
128  private final String mimeType;
129 
130  SupportedArchiveExtractionFormats(final String mimeType) {
131  this.mimeType = mimeType;
132  }
133 
134  @Override
135  public String toString() {
136  return this.mimeType;
137  }
138  // TODO Expand to support more formats after upgrading Tika
139  }
140 
141  SevenZipExtractor(IngestJobContext context, FileTypeDetector fileTypeDetector, String moduleDirRelative, String moduleDirAbsolute) throws SevenZipNativeInitializationException {
142  if (!SevenZip.isInitializedSuccessfully() && (SevenZip.getLastInitializationException() == null)) {
143  SevenZip.initSevenZipFromPlatformJAR();
144  }
145  this.context = context;
146  this.fileTypeDetector = fileTypeDetector;
147  this.moduleDirRelative = moduleDirRelative;
148  this.moduleDirAbsolute = moduleDirAbsolute;
149  }
150 
159  boolean isSevenZipExtractionSupported(AbstractFile file) {
160  String fileMimeType = fileTypeDetector.getMIMEType(file);
161  for (SupportedArchiveExtractionFormats mimeType : SupportedArchiveExtractionFormats.values()) {
162  if (mimeType.toString().equals(fileMimeType)) {
163  return true;
164  }
165  }
166  return false;
167  }
168 
192  private boolean isZipBombArchiveItemCheck(AbstractFile archiveFile, IInArchive inArchive, int inArchiveItemIndex, ConcurrentHashMap<Long, Archive> depthMap, String escapedFilePath) {
193  //If a file is corrupted as a result of reconstructing it from unallocated space, then
194  //7zip does a poor job estimating the original uncompressed file size.
195  //As a result, many corrupted files have wonky compression ratios and could flood the UI
196  //with false zip bomb notifications. The decision was made to skip compression ratio checks
197  //for unallocated zip files. Instead, we let the depth be an indicator of a zip bomb.
198  //Gzip archives compress a single file. They may have a sparse file,
199  //and that file could be much larger, however it won't be the exponential growth seen with more dangerous zip bombs.
200  //In addition a fair number of browser cache files will be gzip archives,
201  //and their file sizes are frequently retrieved incorrectly so ignoring gzip files is a reasonable decision.
202  if (archiveFile.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.UNALLOC) || archiveFile.getMIMEType().equalsIgnoreCase(SupportedArchiveExtractionFormats.XGZIP.toString())) {
203  return false;
204  }
205 
206  try {
207  final Long archiveItemSize = (Long) inArchive.getProperty(
208  inArchiveItemIndex, PropID.SIZE);
209 
210  //skip the check for small files
211  if (archiveItemSize == null || archiveItemSize < MIN_COMPRESSION_RATIO_SIZE) {
212  return false;
213  }
214 
215  final Long archiveItemPackedSize = (Long) inArchive.getProperty(
216  inArchiveItemIndex, PropID.PACKED_SIZE);
217 
218  if (archiveItemPackedSize == null || archiveItemPackedSize <= 0) {
219  logger.log(Level.WARNING, "Cannot getting compression ratio, cannot detect if zipbomb: {0}, item: {1}", //NON-NLS
220  new Object[]{archiveFile.getName(), (String) inArchive.getProperty(inArchiveItemIndex, PropID.PATH)}); //NON-NLS
221  return false;
222  }
223 
224  int cRatio = (int) (archiveItemSize / archiveItemPackedSize);
225 
226  if (cRatio >= MAX_COMPRESSION_RATIO) {
227  Archive rootArchive = depthMap.get(depthMap.get(archiveFile.getId()).getRootArchiveId());
228  String details = NbBundle.getMessage(SevenZipExtractor.class,
229  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnDetails",
230  cRatio, FileUtil.escapeFileName(getArchiveFilePath(rootArchive.getArchiveFile())));
231 
232  flagRootArchiveAsZipBomb(rootArchive, archiveFile, details, escapedFilePath);
233  return true;
234  } else {
235  return false;
236  }
237 
238  } catch (SevenZipException ex) {
239  logger.log(Level.WARNING, "Error getting archive item size and cannot detect if zipbomb. ", ex); //NON-NLS
240  return false;
241  }
242  }
243 
255  private void flagRootArchiveAsZipBomb(Archive rootArchive, AbstractFile archiveFile, String details, String escapedFilePath) {
256  rootArchive.flagAsZipBomb();
257  logger.log(Level.INFO, details);
258  try {
259  Collection<BlackboardAttribute> attributes = Arrays.asList(
260  new BlackboardAttribute(
261  TSK_SET_NAME, MODULE_NAME,
262  "Possible Zip Bomb"),
263  new BlackboardAttribute(
264  TSK_DESCRIPTION, MODULE_NAME,
265  Bundle.SevenZipExtractor_zipBombArtifactCreation_text(archiveFile.getName())),
266  new BlackboardAttribute(
267  TSK_COMMENT, MODULE_NAME,
268  details));
269 
270  if (!blackboard.artifactExists(archiveFile, TSK_INTERESTING_FILE_HIT, attributes)) {
271  BlackboardArtifact artifact = rootArchive.getArchiveFile().newArtifact(TSK_INTERESTING_FILE_HIT);
272  artifact.addAttributes(attributes);
273  try {
274  /*
275  * post the artifact which will index the artifact for
276  * keyword search, and fire an event to notify UI of this
277  * new artifact
278  */
279  blackboard.postArtifact(artifact, MODULE_NAME);
280 
281  String msg = NbBundle.getMessage(SevenZipExtractor.class,
282  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnMsg", archiveFile.getName(), escapedFilePath);//NON-NLS
283 
284  services.postMessage(IngestMessage.createWarningMessage(MODULE_NAME, msg, details));
285 
286  } catch (Blackboard.BlackboardException ex) {
287  logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex); //NON-NLS
288  MessageNotifyUtil.Notify.error(
289  Bundle.SevenZipExtractor_indexError_message(), artifact.getDisplayName());
290  }
291  }
292  } catch (TskCoreException ex) {
293  logger.log(Level.SEVERE, "Error creating blackboard artifact for Zip Bomb Detection for file: " + escapedFilePath, ex); //NON-NLS
294  }
295  }
296 
305  private ArchiveFormat get7ZipOptions(AbstractFile archiveFile) {
306  // try to get the file type from the BB
307  String detectedFormat;
308  detectedFormat = archiveFile.getMIMEType();
309 
310  if (detectedFormat == null) {
311  logger.log(Level.WARNING, "Could not detect format for file: {0}", archiveFile); //NON-NLS
312 
313  // if we don't have attribute info then use file extension
314  String extension = archiveFile.getNameExtension();
315  if ("rar".equals(extension)) //NON-NLS
316  {
317  // for RAR files we need to open them explicitly as RAR. Otherwise, if there is a ZIP archive inside RAR archive
318  // it will be opened incorrectly when using 7zip's built-in auto-detect functionality
319  return RAR;
320  }
321 
322  // Otherwise open the archive using 7zip's built-in auto-detect functionality
323  return null;
324  } else if (detectedFormat.contains("application/x-rar-compressed")) //NON-NLS
325  {
326  // for RAR files we need to open them explicitly as RAR. Otherwise, if there is a ZIP archive inside RAR archive
327  // it will be opened incorrectly when using 7zip's built-in auto-detect functionality
328  return RAR;
329  }
330 
331  // Otherwise open the archive using 7zip's built-in auto-detect functionality
332  return null;
333  }
334 
345  private long getRootArchiveId(AbstractFile file) throws TskCoreException {
346  long id = file.getId();
347  Content parentContent = file.getParent();
348  while (parentContent != null) {
349  id = parentContent.getId();
350  parentContent = parentContent.getParent();
351  }
352  return id;
353  }
354 
369  private List<AbstractFile> getAlreadyExtractedFiles(AbstractFile archiveFile, String archiveFilePath) throws TskCoreException, NoCurrentCaseException {
370  //check if already has derived files, skip
371  //check if local unpacked dir exists
372  if (archiveFile.hasChildren() && new File(moduleDirAbsolute, EmbeddedFileExtractorIngestModule.getUniqueName(archiveFile)).exists()) {
373  return Case.getCurrentCaseThrows().getServices().getFileManager().findFilesByParentPath(getRootArchiveId(archiveFile), archiveFilePath);
374  }
375  return new ArrayList<>();
376  }
377 
385  private String getArchiveFilePath(AbstractFile archiveFile) {
386  return archiveFile.getParentPath() + archiveFile.getName();
387  }
388 
395  private void makeLocalDirectories(String uniqueArchiveFileName) {
396  final String localRootAbsPath = getLocalRootAbsPath(uniqueArchiveFileName);
397  final File localRoot = new File(localRootAbsPath);
398  if (!localRoot.exists()) {
399  localRoot.mkdirs();
400  }
401  }
402 
414  private String getPathInArchive(IInArchive archive, int inArchiveItemIndex, AbstractFile archiveFile) throws SevenZipException {
415  String pathInArchive = (String) archive.getProperty(inArchiveItemIndex, PropID.PATH);
416 
417  if (pathInArchive == null || pathInArchive.isEmpty()) {
418  //some formats (.tar.gz) may not be handled correctly -- file in archive has no name/path
419  //handle this for .tar.gz and tgz but assuming the child is tar,
420  //otherwise, unpack using itemNumber as name
421 
422  //TODO this should really be signature based, not extension based
423  String archName = archiveFile.getName();
424  int dotI = archName.lastIndexOf(".");
425  String useName = null;
426  if (dotI != -1) {
427  String base = archName.substring(0, dotI);
428  String ext = archName.substring(dotI);
429  int colonIndex = ext.lastIndexOf(":");
430  if (colonIndex != -1) {
431  // If alternate data stream is found, fix the name
432  // so Windows doesn't choke on the colon character.
433  ext = ext.substring(0, colonIndex);
434  }
435  switch (ext) {
436  case ".gz": //NON-NLS
437  useName = base;
438  break;
439  case ".tgz": //NON-NLS
440  useName = base + ".tar"; //NON-NLS
441  break;
442  case ".bz2": //NON-NLS
443  useName = base;
444  break;
445  }
446  }
447  if (useName == null) {
448  pathInArchive = "/" + archName + "/" + Integer.toString(inArchiveItemIndex);
449  } else {
450  pathInArchive = "/" + useName;
451  }
452  }
453  return pathInArchive;
454  }
455 
456  private byte[] getPathBytesInArchive(IInArchive archive, int inArchiveItemIndex, AbstractFile archiveFile) throws SevenZipException {
457  return (byte[]) archive.getProperty(inArchiveItemIndex, PropID.PATH_BYTES);
458  }
459 
460  /*
461  * Get the String that will represent the key for the hashmap which keeps
462  * track of existing files from an AbstractFile
463  */
464  private String getKeyAbstractFile(AbstractFile fileInDatabase) {
465  return fileInDatabase == null ? null : fileInDatabase.getParentPath() + fileInDatabase.getName();
466  }
467 
468  /*
469  * Get the String that will represent the key for the hashmap which keeps
470  * track of existing files from an unpacked node and the archiveFilePath
471  */
472  private String getKeyFromUnpackedNode(UnpackedTree.UnpackedNode node, String archiveFilePath) {
473  return node == null ? null : archiveFilePath + "/" + node.getFileName();
474  }
475 
483  void unpack(AbstractFile archiveFile, ConcurrentHashMap<Long, Archive> depthMap) {
484  unpack(archiveFile, depthMap, null);
485  }
486 
498  @Messages({"SevenZipExtractor.indexError.message=Failed to index encryption detected artifact for keyword search.",
499  "# {0} - rootArchive",
500  "SevenZipExtractor.zipBombArtifactCreation.text=Zip Bomb Detected {0}"})
501  boolean unpack(AbstractFile archiveFile, ConcurrentHashMap<Long, Archive> depthMap, String password) {
502  boolean unpackSuccessful = true; //initialized to true change to false if any files fail to extract and
503  boolean hasEncrypted = false;
504  boolean fullEncryption = true;
505  boolean progressStarted = false;
506  final String archiveFilePath = getArchiveFilePath(archiveFile);
507  final String escapedArchiveFilePath = FileUtil.escapeFileName(archiveFilePath);
508  HashMap<String, ZipFileStatusWrapper> statusMap = new HashMap<>();
509  List<AbstractFile> unpackedFiles = Collections.<AbstractFile>emptyList();
510 
511  currentArchiveName = archiveFile.getName();
512 
513  SevenZipContentReadStream stream = null;
514  progress = ProgressHandle.createHandle(Bundle.EmbeddedFileExtractorIngestModule_ArchiveExtractor_moduleName());
515  //recursion depth check for zip bomb
516  Archive parentAr;
517  try {
518  blackboard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard();
519  } catch (NoCurrentCaseException ex) {
520  logger.log(Level.INFO, "Exception while getting open case.", ex); //NON-NLS
521  unpackSuccessful = false;
522  return unpackSuccessful;
523  }
524  try {
525  List<AbstractFile> existingFiles = getAlreadyExtractedFiles(archiveFile, archiveFilePath);
526  for (AbstractFile file : existingFiles) {
527  statusMap.put(getKeyAbstractFile(file), new ZipFileStatusWrapper(file, ZipFileStatus.EXISTS));
528  }
529  } catch (TskCoreException e) {
530  logger.log(Level.INFO, "Error checking if file already has been processed, skipping: {0}", escapedArchiveFilePath); //NON-NLS
531  unpackSuccessful = false;
532  return unpackSuccessful;
533  } catch (NoCurrentCaseException ex) {
534  logger.log(Level.INFO, "No open case was found while trying to unpack the archive file {0}", escapedArchiveFilePath); //NON-NLS
535  unpackSuccessful = false;
536  return unpackSuccessful;
537  }
538  parentAr = depthMap.get(archiveFile.getId());
539  if (parentAr == null) {
540  parentAr = new Archive(0, archiveFile.getId(), archiveFile);
541  depthMap.put(archiveFile.getId(), parentAr);
542  } else {
543  Archive rootArchive = depthMap.get(parentAr.getRootArchiveId());
544  if (rootArchive.isFlaggedAsZipBomb()) {
545  //skip this archive as the root archive has already been determined to contain a zip bomb
546  unpackSuccessful = false;
547  return unpackSuccessful;
548  } else if (parentAr.getDepth() == MAX_DEPTH) {
549  String details = NbBundle.getMessage(SevenZipExtractor.class,
550  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnDetails.zipBomb",
551  parentAr.getDepth(), FileUtil.escapeFileName(getArchiveFilePath(rootArchive.getArchiveFile())));
552  flagRootArchiveAsZipBomb(rootArchive, archiveFile, details, escapedArchiveFilePath);
553  unpackSuccessful = false;
554  return unpackSuccessful;
555  }
556  }
557  IInArchive inArchive = null;
558  try {
559  stream = new SevenZipContentReadStream(new ReadContentInputStream(archiveFile));
560  // for RAR files we need to open them explicitly as RAR. Otherwise, if there is a ZIP archive inside RAR archive
561  // it will be opened incorrectly when using 7zip's built-in auto-detect functionality.
562  // All other archive formats are still opened using 7zip built-in auto-detect functionality.
563  ArchiveFormat options = get7ZipOptions(archiveFile);
564  if (password == null) {
565  inArchive = SevenZip.openInArchive(options, stream);
566  } else {
567  inArchive = SevenZip.openInArchive(options, stream, password);
568  }
569  numItems = inArchive.getNumberOfItems();
570  progress.start(numItems);
571  progressStarted = true;
572 
573  //setup the archive local root folder
574  final String uniqueArchiveFileName = FileUtil.escapeFileName(EmbeddedFileExtractorIngestModule.getUniqueName(archiveFile));
575  try {
576  makeLocalDirectories(uniqueArchiveFileName);
577  } catch (SecurityException e) {
578  logger.log(Level.SEVERE, "Error setting up output path for archive root: {0}", getLocalRootAbsPath(uniqueArchiveFileName)); //NON-NLS
579  //bail
580  unpackSuccessful = false;
581  return unpackSuccessful;
582  }
583 
584  //initialize tree hierarchy to keep track of unpacked file structure
585  SevenZipExtractor.UnpackedTree unpackedTree = new SevenZipExtractor.UnpackedTree(moduleDirRelative + "/" + uniqueArchiveFileName, archiveFile);
586 
587  long freeDiskSpace;
588  try {
589  freeDiskSpace = services.getFreeDiskSpace();
590  } catch (NullPointerException ex) {
591  //If ingest has not been run at least once getFreeDiskSpace() will throw a null pointer exception
592  //currently getFreeDiskSpace always returns DISK_FREE_SPACE_UNKNOWN
593  freeDiskSpace = IngestMonitor.DISK_FREE_SPACE_UNKNOWN;
594  }
595 
596  Map<Integer, InArchiveItemDetails> archiveDetailsMap = new HashMap<>();
597  for (int inArchiveItemIndex = 0; inArchiveItemIndex < numItems; inArchiveItemIndex++) {
598  progress.progress(String.format("%s: Analyzing archive metadata and creating local files (%d of %d)", currentArchiveName, inArchiveItemIndex + 1, numItems), 0);
599  if (isZipBombArchiveItemCheck(archiveFile, inArchive, inArchiveItemIndex, depthMap, escapedArchiveFilePath)) {
600  unpackSuccessful = false;
601  return unpackSuccessful;
602  }
603 
604  String pathInArchive = getPathInArchive(inArchive, inArchiveItemIndex, archiveFile);
605  byte[] pathBytesInArchive = getPathBytesInArchive(inArchive, inArchiveItemIndex, archiveFile);
606  UnpackedTree.UnpackedNode unpackedNode = unpackedTree.addNode(pathInArchive, pathBytesInArchive);
607 
608  final boolean isEncrypted = (Boolean) inArchive.getProperty(inArchiveItemIndex, PropID.ENCRYPTED);
609 
610  if (isEncrypted && password == null) {
611  logger.log(Level.WARNING, "Skipping encrypted file in archive: {0}", pathInArchive); //NON-NLS
612  hasEncrypted = true;
613  unpackSuccessful = false;
614  continue;
615  } else {
616  fullEncryption = false;
617  }
618 
619  // NOTE: item size may return null in case of certain
620  // archiving formats. Eg: BZ2
621  //check if unpacking this file will result in out of disk space
622  //this is additional to zip bomb prevention mechanism
623  Long archiveItemSize = (Long) inArchive.getProperty(
624  inArchiveItemIndex, PropID.SIZE);
625  if (freeDiskSpace != IngestMonitor.DISK_FREE_SPACE_UNKNOWN && archiveItemSize != null && archiveItemSize > 0) { //if free space is known and file is not empty.
626  String archiveItemPath = (String) inArchive.getProperty(
627  inArchiveItemIndex, PropID.PATH);
628  long newDiskSpace = freeDiskSpace - archiveItemSize;
629  if (newDiskSpace < MIN_FREE_DISK_SPACE) {
630  String msg = NbBundle.getMessage(SevenZipExtractor.class,
631  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.msg",
632  escapedArchiveFilePath, archiveItemPath);
633  String details = NbBundle.getMessage(SevenZipExtractor.class,
634  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.details");
635  services.postMessage(IngestMessage.createErrorMessage(MODULE_NAME, msg, details));
636  logger.log(Level.INFO, "Skipping archive item due to insufficient disk space: {0}, {1}", new String[]{escapedArchiveFilePath, archiveItemPath}); //NON-NLS
637  logger.log(Level.INFO, "Available disk space: {0}", new Object[]{freeDiskSpace}); //NON-NLS
638  unpackSuccessful = false;
639  continue; //skip this file
640  } else {
641  //update est. disk space during this archive, so we don't need to poll for every file extracted
642  freeDiskSpace = newDiskSpace;
643  }
644  }
645  final String uniqueExtractedName = FileUtil.escapeFileName(uniqueArchiveFileName + File.separator + (inArchiveItemIndex / 1000) + File.separator + inArchiveItemIndex + "_" + new File(pathInArchive).getName());
646  final String localAbsPath = moduleDirAbsolute + File.separator + uniqueExtractedName;
647  final String localRelPath = moduleDirRelative + File.separator + uniqueExtractedName;
648 
649  //create local dirs and empty files before extracted
650  File localFile = new java.io.File(localAbsPath);
651  //cannot rely on files in top-bottom order
652  if (!localFile.exists()) {
653  try {
654  if ((Boolean) inArchive.getProperty(
655  inArchiveItemIndex, PropID.IS_FOLDER)) {
656  localFile.mkdirs();
657  } else {
658  localFile.getParentFile().mkdirs();
659  try {
660  localFile.createNewFile();
661  } catch (IOException e) {
662  logger.log(Level.SEVERE, "Error creating extracted file: "//NON-NLS
663  + localFile.getAbsolutePath(), e);
664  }
665  }
666  } catch (SecurityException e) {
667  logger.log(Level.SEVERE, "Error setting up output path for unpacked file: {0}", //NON-NLS
668  pathInArchive); //NON-NLS
669  //TODO consider bail out / msg to the user
670  }
671  }
672  // skip the rest of this loop if we couldn't create the file
673  //continue will skip details from being added to the map
674  if (localFile.exists() == false) {
675  continue;
676  }
677 
678  //Store archiveItemIndex with local paths and unpackedNode reference.
679  //Necessary for the extract call back to write the current archive
680  //file to the correct disk location and to correctly update it's
681  //corresponding unpackedNode
682  archiveDetailsMap.put(inArchiveItemIndex, new InArchiveItemDetails(
683  unpackedNode, localAbsPath, localRelPath));
684  }
685 
686  int[] extractionIndices = getExtractableFilesFromDetailsMap(archiveDetailsMap);
687 
688  StandardIArchiveExtractCallback archiveCallBack
689  = new StandardIArchiveExtractCallback(
690  inArchive, archiveFile, progress,
691  archiveDetailsMap, password, freeDiskSpace);
692 
693  //According to the documentation, indices in sorted order are optimal
694  //for efficiency. Hence, the HashMap and linear processing of
695  //inArchiveItemIndex. False indicates non-test mode
696  inArchive.extract(extractionIndices, false, archiveCallBack);
697 
698  unpackSuccessful &= archiveCallBack.wasSuccessful();
699 
700  archiveDetailsMap = null;
701 
702  // add them to the DB. We wait until the end so that we have the metadata on all of the
703  // intermediate nodes since the order is not guaranteed
704  try {
705  unpackedTree.updateOrAddFileToCaseRec(statusMap, archiveFilePath);
706  unpackedFiles = unpackedTree.getAllFileObjects();
707  //check if children are archives, update archive depth tracking
708  for (int i = 0; i < unpackedFiles.size(); i++) {
709  progress.progress(String.format("%s: Searching for nested archives (%d of %d)", currentArchiveName, i + 1, unpackedFiles.size()));
710  AbstractFile unpackedFile = unpackedFiles.get(i);
711  if (unpackedFile == null) {
712  continue;
713  }
714  if (isSevenZipExtractionSupported(unpackedFile)) {
715  Archive child = new Archive(parentAr.getDepth() + 1, parentAr.getRootArchiveId(), archiveFile);
716  parentAr.addChild(child);
717  depthMap.put(unpackedFile.getId(), child);
718  }
719  unpackedFile.close();
720  }
721 
722  } catch (TskCoreException | NoCurrentCaseException e) {
723  logger.log(Level.SEVERE, "Error populating complete derived file hierarchy from the unpacked dir structure", e); //NON-NLS
724  //TODO decide if anything to cleanup, for now bailing
725  }
726 
727  } catch (SevenZipException ex) {
728  logger.log(Level.WARNING, "Error unpacking file: " + archiveFile, ex); //NON-NLS
729  //inbox message
730 
731  // print a message if the file is allocated
732  if (archiveFile.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC)) {
733  String msg = NbBundle.getMessage(SevenZipExtractor.class,
734  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.errUnpacking.msg",
735  currentArchiveName);
736  String details = NbBundle.getMessage(SevenZipExtractor.class,
737  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.errUnpacking.details",
738  escapedArchiveFilePath, ex.getMessage());
739  services.postMessage(IngestMessage.createErrorMessage(MODULE_NAME, msg, details));
740  }
741  } finally {
742  if (inArchive != null) {
743  try {
744  inArchive.close();
745  } catch (SevenZipException e) {
746  logger.log(Level.SEVERE, "Error closing archive: " + archiveFile, e); //NON-NLS
747  }
748  }
749 
750  if (stream != null) {
751  try {
752  stream.close();
753  } catch (IOException ex) {
754  logger.log(Level.SEVERE, "Error closing stream after unpacking archive: " + archiveFile, ex); //NON-NLS
755  }
756  }
757 
758  //close progress bar
759  if (progressStarted) {
760  progress.finish();
761  }
762  }
763 
764  //create artifact and send user message
765  if (hasEncrypted) {
766  String encryptionType = fullEncryption ? ENCRYPTION_FULL : ENCRYPTION_FILE_LEVEL;
767  try {
768  BlackboardArtifact artifact = archiveFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED);
769  artifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT, MODULE_NAME, encryptionType));
770 
771  try {
772  /*
773  * post the artifact which will index the artifact for
774  * keyword search, and fire an event to notify UI of this
775  * new artifact
776  */
777  blackboard.postArtifact(artifact, MODULE_NAME);
778  } catch (Blackboard.BlackboardException ex) {
779  logger.log(Level.SEVERE, "Unable to post blackboard artifact " + artifact.getArtifactID(), ex); //NON-NLS
780  MessageNotifyUtil.Notify.error(
781  Bundle.SevenZipExtractor_indexError_message(), artifact.getDisplayName());
782  }
783 
784  } catch (TskCoreException ex) {
785  logger.log(Level.SEVERE, "Error creating blackboard artifact for encryption detected for file: " + escapedArchiveFilePath, ex); //NON-NLS
786  }
787 
788  String msg = NbBundle.getMessage(SevenZipExtractor.class,
789  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.encrFileDetected.msg");
790  String details = NbBundle.getMessage(SevenZipExtractor.class,
791  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.encrFileDetected.details",
792  currentArchiveName, MODULE_NAME);
793  services.postMessage(IngestMessage.createWarningMessage(MODULE_NAME, msg, details));
794  }
795 
796  // adding unpacked extracted derived files to the job after closing relevant resources.
797  if (!unpackedFiles.isEmpty()) {
798  //currently sending a single event for all new files
799  services.fireModuleContentEvent(new ModuleContentEvent(archiveFile));
800  if (context != null) {
801  context.addFilesToJob(unpackedFiles);
802  }
803  }
804  return unpackSuccessful;
805  }
806 
807  private Charset detectFilenamesCharset(List<byte[]> byteDatas) {
808  Charset detectedCharset = null;
809  CharsetDetector charsetDetector = new CharsetDetector();
810  int byteSum = 0;
811  int fileNum = 0;
812  for (byte[] byteData : byteDatas) {
813  fileNum++;
814  byteSum += byteData.length;
815  // Only read ~1000 bytes of filenames in this directory
816  if (byteSum >= 1000) {
817  break;
818  }
819  }
820  byte[] allBytes = new byte[byteSum];
821  int start = 0;
822  for (int i = 0; i < fileNum; i++) {
823  byte[] byteData = byteDatas.get(i);
824  System.arraycopy(byteData, 0, allBytes, start, byteData.length);
825  start += byteData.length;
826  }
827  charsetDetector.setText(allBytes);
828  CharsetMatch cm = charsetDetector.detect();
829  if (cm.getConfidence() >= 90 && Charset.isSupported(cm.getName())) {
830  detectedCharset = Charset.forName(cm.getName());
831  }
832  return detectedCharset;
833  }
834 
839  private int[] getExtractableFilesFromDetailsMap(
840  Map<Integer, InArchiveItemDetails> archiveDetailsMap) {
841 
842  Integer[] wrappedExtractionIndices = archiveDetailsMap.keySet()
843  .toArray(new Integer[archiveDetailsMap.size()]);
844 
845  return Arrays.stream(wrappedExtractionIndices)
846  .mapToInt(Integer::intValue)
847  .toArray();
848  }
849 
857  private final static class UnpackStream implements ISequentialOutStream {
858 
859  private EncodedFileOutputStream output;
860  private String localAbsPath;
861  private int bytesWritten;
862 
863  UnpackStream(String localAbsPath) throws IOException {
864  this.output = new EncodedFileOutputStream(new FileOutputStream(localAbsPath), TskData.EncodingType.XOR1);
865  this.localAbsPath = localAbsPath;
866  this.bytesWritten = 0;
867  }
868 
869  public void setNewOutputStream(String localAbsPath) throws IOException {
870  this.output.close();
871  this.output = new EncodedFileOutputStream(new FileOutputStream(localAbsPath), TskData.EncodingType.XOR1);
872  this.localAbsPath = localAbsPath;
873  this.bytesWritten = 0;
874  }
875 
876  public int getSize() {
877  return bytesWritten;
878  }
879 
880  @Override
881  public int write(byte[] bytes) throws SevenZipException {
882  try {
883  output.write(bytes);
884  this.bytesWritten += bytes.length;
885  } catch (IOException ex) {
886  throw new SevenZipException(
887  NbBundle.getMessage(SevenZipExtractor.class,
888  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackStream.write.exception.msg",
889  localAbsPath), ex);
890  }
891  return bytes.length;
892  }
893 
894  public void close() throws IOException {
895  try (EncodedFileOutputStream out = output) {
896  out.flush();
897  }
898  }
899 
900  }
901 
905  private static class InArchiveItemDetails {
906 
907  private final SevenZipExtractor.UnpackedTree.UnpackedNode unpackedNode;
908  private final String localAbsPath;
909  private final String localRelPath;
910 
912  SevenZipExtractor.UnpackedTree.UnpackedNode unpackedNode,
913  String localAbsPath, String localRelPath) {
914  this.unpackedNode = unpackedNode;
915  this.localAbsPath = localAbsPath;
916  this.localRelPath = localRelPath;
917  }
918 
919  public SevenZipExtractor.UnpackedTree.UnpackedNode getUnpackedNode() {
920  return unpackedNode;
921  }
922 
923  public String getLocalAbsPath() {
924  return localAbsPath;
925  }
926 
927  public String getLocalRelPath() {
928  return localRelPath;
929  }
930  }
931 
936  private static class StandardIArchiveExtractCallback
937  implements IArchiveExtractCallback, ICryptoGetTextPassword {
938 
939  private final AbstractFile archiveFile;
940  private final IInArchive inArchive;
941  private UnpackStream unpackStream = null;
942  private final Map<Integer, InArchiveItemDetails> archiveDetailsMap;
943  private final ProgressHandle progressHandle;
944 
945  private int inArchiveItemIndex;
946 
947  private long createTimeInSeconds;
948  private long modTimeInSeconds;
949  private long accessTimeInSeconds;
950 
951  private boolean isFolder;
952  private final String password;
953 
954  private boolean unpackSuccessful = true;
955 
956  StandardIArchiveExtractCallback(IInArchive inArchive,
957  AbstractFile archiveFile, ProgressHandle progressHandle,
958  Map<Integer, InArchiveItemDetails> archiveDetailsMap,
959  String password, long freeDiskSpace) {
960  this.inArchive = inArchive;
961  this.progressHandle = progressHandle;
962  this.archiveFile = archiveFile;
963  this.archiveDetailsMap = archiveDetailsMap;
964  this.password = password;
965  }
966 
981  @Override
982  public ISequentialOutStream getStream(int inArchiveItemIndex,
983  ExtractAskMode mode) throws SevenZipException {
984 
985  this.inArchiveItemIndex = inArchiveItemIndex;
986 
987  isFolder = (Boolean) inArchive
988  .getProperty(inArchiveItemIndex, PropID.IS_FOLDER);
989  if (isFolder || mode != ExtractAskMode.EXTRACT) {
990  return null;
991  }
992 
993  final String localAbsPath = archiveDetailsMap.get(
994  inArchiveItemIndex).getLocalAbsPath();
995 
996  //If the Unpackstream has been allocated, then set the Outputstream
997  //to another file rather than creating a new unpack stream. The 7Zip
998  //binding has a memory leak, so creating new unpack streams will not be
999  //dereferenced. As a fix, we create one UnpackStream, and mutate its state,
1000  //so that there only exists one 8192 byte buffer in memory per archive.
1001  try {
1002  if (unpackStream != null) {
1003  unpackStream.setNewOutputStream(localAbsPath);
1004  } else {
1005  unpackStream = new UnpackStream(localAbsPath);
1006  }
1007  } catch (IOException ex) {
1008  logger.log(Level.WARNING, String.format("Error opening or setting new stream " //NON-NLS
1009  + "for archive file at %s", localAbsPath), ex.getMessage()); //NON-NLS
1010  return null;
1011  }
1012 
1013  return unpackStream;
1014  }
1015 
1024  @Override
1025  public void prepareOperation(ExtractAskMode mode) throws SevenZipException {
1026  final Date createTime = (Date) inArchive.getProperty(
1027  inArchiveItemIndex, PropID.CREATION_TIME);
1028  final Date accessTime = (Date) inArchive.getProperty(
1029  inArchiveItemIndex, PropID.LAST_ACCESS_TIME);
1030  final Date writeTime = (Date) inArchive.getProperty(
1031  inArchiveItemIndex, PropID.LAST_MODIFICATION_TIME);
1032 
1033  createTimeInSeconds = createTime == null ? 0L
1034  : createTime.getTime() / 1000;
1035  modTimeInSeconds = writeTime == null ? 0L
1036  : writeTime.getTime() / 1000;
1037  accessTimeInSeconds = accessTime == null ? 0L
1038  : accessTime.getTime() / 1000;
1039 
1040  progressHandle.progress(archiveFile.getName() + ": "
1041  + (String) inArchive.getProperty(inArchiveItemIndex, PropID.PATH),
1043 
1044  }
1045 
1054  @Override
1055  public void setOperationResult(ExtractOperationResult result) throws SevenZipException {
1056 
1057  final SevenZipExtractor.UnpackedTree.UnpackedNode unpackedNode
1058  = archiveDetailsMap.get(inArchiveItemIndex).getUnpackedNode();
1059  final String localRelPath = archiveDetailsMap.get(
1060  inArchiveItemIndex).getLocalRelPath();
1061  if (isFolder) {
1062  unpackedNode.addDerivedInfo(0,
1063  !(Boolean) inArchive.getProperty(inArchiveItemIndex, PropID.IS_FOLDER),
1065  localRelPath);
1066  return;
1067  }
1068 
1069  final String localAbsPath = archiveDetailsMap.get(
1070  inArchiveItemIndex).getLocalAbsPath();
1071  if (result != ExtractOperationResult.OK) {
1072  logger.log(Level.WARNING, "Extraction of : {0} encountered error {1}", //NON-NLS
1073  new Object[]{localAbsPath, result});
1074  unpackSuccessful = false;
1075  }
1076 
1077  //record derived data in unode, to be traversed later after unpacking the archive
1078  unpackedNode.addDerivedInfo(unpackStream.getSize(),
1079  !(Boolean) inArchive.getProperty(inArchiveItemIndex, PropID.IS_FOLDER),
1081 
1082  try {
1083  unpackStream.close();
1084  } catch (IOException e) {
1085  logger.log(Level.WARNING, "Error closing unpack stream for file: {0}", localAbsPath); //NON-NLS
1086  }
1087  }
1088 
1089  @Override
1090  public void setTotal(long value) throws SevenZipException {
1091  //Not necessary for extract, left intenionally blank
1092  }
1093 
1094  @Override
1095  public void setCompleted(long value) throws SevenZipException {
1096  //Not necessary for extract, left intenionally blank
1097  }
1098 
1106  @Override
1107  public String cryptoGetTextPassword() throws SevenZipException {
1108  return password;
1109  }
1110 
1111  public boolean wasSuccessful() {
1112  return unpackSuccessful;
1113  }
1114  }
1115 
1123  private class UnpackedTree {
1124 
1125  final UnpackedNode rootNode;
1126  private int nodesProcessed = 0;
1127 
1134  UnpackedTree(String localPathRoot, AbstractFile archiveFile) {
1135  this.rootNode = new UnpackedNode();
1136  this.rootNode.setFile(archiveFile);
1137  this.rootNode.setFileName(archiveFile.getName());
1138  this.rootNode.setLocalRelPath(localPathRoot);
1139  }
1140 
1150  UnpackedNode addNode(String filePath, byte[] filePathBytes) {
1151  String[] toks = filePath.split("[\\/\\\\]");
1152  List<String> tokens = new ArrayList<>();
1153  for (int i = 0; i < toks.length; ++i) {
1154  if (!toks[i].isEmpty()) {
1155  tokens.add(toks[i]);
1156  }
1157  }
1158 
1159  List<byte[]> byteTokens = null;
1160  if (filePathBytes == null) {
1161  return addNode(rootNode, tokens, null);
1162  } else {
1163  byteTokens = new ArrayList<>(tokens.size());
1164  int last = 0;
1165  for (int i = 0; i < filePathBytes.length; i++) {
1166  if (filePathBytes[i] == '/') {
1167  int len = i - last;
1168  byte[] arr = new byte[len];
1169  System.arraycopy(filePathBytes, last, arr, 0, len);
1170  byteTokens.add(arr);
1171  last = i + 1;
1172  }
1173  }
1174  int len = filePathBytes.length - last;
1175  if (len > 0) {
1176  byte[] arr = new byte[len];
1177  System.arraycopy(filePathBytes, last, arr, 0, len);
1178  byteTokens.add(arr);
1179  }
1180 
1181  if (tokens.size() != byteTokens.size()) {
1182  logger.log(Level.WARNING, "Could not map path bytes to path string");
1183  return addNode(rootNode, tokens, null);
1184  }
1185  }
1186 
1187  return addNode(rootNode, tokens, byteTokens);
1188  }
1189 
1200  List<String> tokenPath, List<byte[]> tokenPathBytes) {
1201  // we found all of the tokens
1202  if (tokenPath.isEmpty()) {
1203  return parent;
1204  }
1205 
1206  // get the next name in the path and look it up
1207  String childName = tokenPath.remove(0);
1208  byte[] childNameBytes = null;
1209  if (tokenPathBytes != null) {
1210  childNameBytes = tokenPathBytes.remove(0);
1211  }
1212  UnpackedNode child = parent.getChild(childName);
1213  // create new node
1214  if (child == null) {
1215  child = new UnpackedNode(childName, parent);
1216  child.setFileNameBytes(childNameBytes);
1217  parent.addChild(child);
1218  }
1219 
1220  // go down one more level
1221  return addNode(child, tokenPath, tokenPathBytes);
1222  }
1223 
1230  List<AbstractFile> getRootFileObjects() {
1231  List<AbstractFile> ret = new ArrayList<>();
1232  rootNode.getChildren().forEach((child) -> {
1233  ret.add(child.getFile());
1234  });
1235  return ret;
1236  }
1237 
1244  List<AbstractFile> getAllFileObjects() {
1245  List<AbstractFile> ret = new ArrayList<>();
1246  rootNode.getChildren().forEach((child) -> {
1247  getAllFileObjectsRec(ret, child);
1248  });
1249  return ret;
1250  }
1251 
1252  private void getAllFileObjectsRec(List<AbstractFile> list, UnpackedNode parent) {
1253  list.add(parent.getFile());
1254  parent.getChildren().forEach((child) -> {
1255  getAllFileObjectsRec(list, child);
1256  });
1257  }
1258 
1263  void updateOrAddFileToCaseRec(HashMap<String, ZipFileStatusWrapper> statusMap, String archiveFilePath) throws TskCoreException, NoCurrentCaseException {
1265  for (UnpackedNode child : rootNode.getChildren()) {
1266  updateOrAddFileToCaseRec(child, fileManager, statusMap, archiveFilePath);
1267  }
1268  }
1269 
1284  private void updateOrAddFileToCaseRec(UnpackedNode node, FileManager fileManager, HashMap<String, ZipFileStatusWrapper> statusMap, String archiveFilePath) throws TskCoreException {
1285  DerivedFile df;
1286  progress.progress(String.format("%s: Adding/updating files in case database (%d of %d)", currentArchiveName, ++nodesProcessed, numItems));
1287  try {
1288  String nameInDatabase = getKeyFromUnpackedNode(node, archiveFilePath);
1289  ZipFileStatusWrapper existingFile = nameInDatabase == null ? null : statusMap.get(nameInDatabase);
1290  if (existingFile == null) {
1291  df = fileManager.addDerivedFile(node.getFileName(), node.getLocalRelPath(), node.getSize(),
1292  node.getCtime(), node.getCrtime(), node.getAtime(), node.getMtime(),
1293  node.isIsFile(), node.getParent().getFile(), "", MODULE_NAME,
1294  "", "", TskData.EncodingType.XOR1);
1295  statusMap.put(getKeyAbstractFile(df), new ZipFileStatusWrapper(df, ZipFileStatus.EXISTS));
1296  } else {
1297  String key = getKeyAbstractFile(existingFile.getFile());
1298  if (existingFile.getStatus() == ZipFileStatus.EXISTS && existingFile.getFile().getSize() < node.getSize()) {
1299  existingFile.setStatus(ZipFileStatus.UPDATE);
1300  statusMap.put(key, existingFile);
1301  }
1302  if (existingFile.getStatus() == ZipFileStatus.UPDATE) {
1303  //if the we are updating a file and its mime type was octet-stream we want to re-type it
1304  String mimeType = existingFile.getFile().getMIMEType().equalsIgnoreCase("application/octet-stream") ? null : existingFile.getFile().getMIMEType();
1305  df = fileManager.updateDerivedFile((DerivedFile) existingFile.getFile(), node.getLocalRelPath(), node.getSize(),
1306  node.getCtime(), node.getCrtime(), node.getAtime(), node.getMtime(),
1307  node.isIsFile(), mimeType, "", MODULE_NAME,
1308  "", "", TskData.EncodingType.XOR1);
1309  } else {
1310  //ALREADY CURRENT - SKIP
1311  statusMap.put(key, new ZipFileStatusWrapper(existingFile.getFile(), ZipFileStatus.SKIP));
1312  df = (DerivedFile) existingFile.getFile();
1313  }
1314  }
1315  node.setFile(df);
1316  } catch (TskCoreException ex) {
1317  logger.log(Level.SEVERE, "Error adding a derived file to db:" + node.getFileName(), ex); //NON-NLS
1318  throw new TskCoreException(
1319  NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackedTree.exception.msg",
1320  node.getFileName()), ex);
1321  }
1322 
1323  // Determine encoding of children
1324  if (node.getChildren().size() > 0) {
1325  String names = "";
1326  ArrayList<byte[]> byteDatas = new ArrayList<>();
1327  for (UnpackedNode child : node.getChildren()) {
1328  byte[] childBytes = child.getFileNameBytes();
1329  if (childBytes != null) {
1330  byteDatas.add(childBytes);
1331  }
1332  names += child.getFileName();
1333  }
1334  Charset detectedCharset = detectFilenamesCharset(byteDatas);
1335 
1336  // If a charset was detected, transcode filenames accordingly
1337  if (detectedCharset != null && detectedCharset.canEncode()) {
1338  for (UnpackedNode child : node.getChildren()) {
1339  byte[] childBytes = child.getFileNameBytes();
1340  if (childBytes != null) {
1341  String decodedName = new String(childBytes, detectedCharset);
1342  child.setFileName(decodedName);
1343  }
1344  }
1345  }
1346  }
1347 
1348  //recurse adding the children if this file was incomplete the children presumably need to be added
1349  for (UnpackedNode child : node.getChildren()) {
1350  updateOrAddFileToCaseRec(child, fileManager, statusMap, getKeyFromUnpackedNode(node, archiveFilePath));
1351  }
1352  }
1353 
1357  private class UnpackedNode {
1358 
1359  private String fileName;
1360  private byte[] fileNameBytes;
1361  private AbstractFile file;
1362  private final List<UnpackedNode> children = new ArrayList<>();
1363  private String localRelPath = "";
1364  private long size;
1365  private long ctime, crtime, atime, mtime;
1366  private boolean isFile;
1368 
1369  //root constructor
1370  UnpackedNode() {
1371  }
1372 
1373  //child node constructor
1374  UnpackedNode(String fileName, UnpackedNode parent) {
1375  this.fileName = fileName;
1376  this.parent = parent;
1377  this.localRelPath = parent.getLocalRelPath() + File.separator + fileName;
1378  }
1379 
1380  long getCtime() {
1381  return ctime;
1382  }
1383 
1384  long getCrtime() {
1385  return crtime;
1386  }
1387 
1388  long getAtime() {
1389  return atime;
1390  }
1391 
1392  long getMtime() {
1393  return mtime;
1394  }
1395 
1396  void setFileName(String fileName) {
1397  this.fileName = fileName;
1398  }
1399 
1405  void addChild(UnpackedNode child) {
1406  children.add(child);
1407  }
1408 
1415  List<UnpackedNode> getChildren() {
1416  return children;
1417  }
1418 
1424  UnpackedNode getParent() {
1425  return parent;
1426  }
1427 
1428  void addDerivedInfo(long size,
1429  boolean isFile,
1430  long ctime, long crtime, long atime, long mtime, String relLocalPath) {
1431  this.size = size;
1432  this.isFile = isFile;
1433  this.ctime = ctime;
1434  this.crtime = crtime;
1435  this.atime = atime;
1436  this.mtime = mtime;
1437  this.localRelPath = relLocalPath;
1438  }
1439 
1440  void setFile(AbstractFile file) {
1441  this.file = file;
1442  }
1443 
1451  UnpackedNode getChild(String childFileName) {
1452  UnpackedNode ret = null;
1453  for (UnpackedNode child : children) {
1454  if (child.getFileName().equals(childFileName)) {
1455  ret = child;
1456  break;
1457  }
1458  }
1459  return ret;
1460  }
1461 
1462  String getFileName() {
1463  return fileName;
1464  }
1465 
1466  AbstractFile getFile() {
1467  return file;
1468  }
1469 
1470  String getLocalRelPath() {
1471  return localRelPath;
1472  }
1473 
1480  void setLocalRelPath(String localRelativePath) {
1481  localRelPath = localRelativePath;
1482  }
1483 
1484  long getSize() {
1485  return size;
1486  }
1487 
1488  boolean isIsFile() {
1489  return isFile;
1490  }
1491 
1492  void setFileNameBytes(byte[] fileNameBytes) {
1493  if (fileNameBytes != null) {
1494  this.fileNameBytes = Arrays.copyOf(fileNameBytes, fileNameBytes.length);
1495  }
1496  }
1497 
1498  byte[] getFileNameBytes() {
1499  if (fileNameBytes == null) {
1500  return null;
1501  }
1502  return Arrays.copyOf(fileNameBytes, fileNameBytes.length);
1503  }
1504  }
1505  }
1506 
1511  static class Archive {
1512 
1513  //depth will be 0 for the root archive unpack was called on, and increase as unpack recurses down through archives contained within
1514  private final int depth;
1515  private final List<Archive> children;
1516  private final long rootArchiveId;
1517  private boolean flaggedAsZipBomb = false;
1518  private final AbstractFile archiveFile;
1519 
1532  Archive(int depth, long rootArchiveId, AbstractFile archiveFile) {
1533  this.children = new ArrayList<>();
1534  this.depth = depth;
1535  this.rootArchiveId = rootArchiveId;
1536  this.archiveFile = archiveFile;
1537  }
1538 
1545  void addChild(Archive child) {
1546  children.add(child);
1547  }
1548 
1553  synchronized void flagAsZipBomb() {
1554  flaggedAsZipBomb = true;
1555  }
1556 
1562  synchronized boolean isFlaggedAsZipBomb() {
1563  return flaggedAsZipBomb;
1564  }
1565 
1571  AbstractFile getArchiveFile() {
1572  return archiveFile;
1573  }
1574 
1580  long getRootArchiveId() {
1581  return rootArchiveId;
1582  }
1583 
1589  long getObjectId() {
1590  return archiveFile.getId();
1591  }
1592 
1600  int getDepth() {
1601  return depth;
1602  }
1603  }
1604 
1609  private final class ZipFileStatusWrapper {
1610 
1611  private final AbstractFile abstractFile;
1613 
1621  private ZipFileStatusWrapper(AbstractFile file, ZipFileStatus status) {
1622  abstractFile = file;
1623  zipStatus = status;
1624  }
1625 
1631  private AbstractFile getFile() {
1632  return abstractFile;
1633  }
1634 
1641  return zipStatus;
1642  }
1643 
1649  private void setStatus(ZipFileStatus status) {
1650  zipStatus = status;
1651  }
1652 
1653  }
1654 
1659  private enum ZipFileStatus {
1660  UPDATE, //Should be updated //NON-NLS
1661  SKIP, //File is current can be skipped //NON-NLS
1662  EXISTS //File exists but it is unknown if it is current //NON-NLS
1663  }
1664 }
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, List< byte[]> tokenPathBytes)
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-2020 Basis Technology. Generated on: Wed Apr 8 2020
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.