Autopsy  4.13.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  String msg = NbBundle.getMessage(SevenZipExtractor.class,
453  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.unknownPath.msg",
454  getArchiveFilePath(archiveFile), pathInArchive);
455  logger.log(Level.WARNING, msg);
456  }
457  return pathInArchive;
458  }
459 
460  private byte[] getPathBytesInArchive(IInArchive archive, int inArchiveItemIndex, AbstractFile archiveFile) throws SevenZipException {
461  return (byte[]) archive.getProperty(inArchiveItemIndex, PropID.PATH_BYTES);
462  }
463 
464  /*
465  * Get the String that will represent the key for the hashmap which keeps
466  * track of existing files from an AbstractFile
467  */
468  private String getKeyAbstractFile(AbstractFile fileInDatabase) {
469  return fileInDatabase == null ? null : fileInDatabase.getParentPath() + fileInDatabase.getName();
470  }
471 
472  /*
473  * Get the String that will represent the key for the hashmap which keeps
474  * track of existing files from an unpacked node and the archiveFilePath
475  */
476  private String getKeyFromUnpackedNode(UnpackedTree.UnpackedNode node, String archiveFilePath) {
477  return node == null ? null : archiveFilePath + "/" + node.getFileName();
478  }
479 
487  void unpack(AbstractFile archiveFile, ConcurrentHashMap<Long, Archive> depthMap) {
488  unpack(archiveFile, depthMap, null);
489  }
490 
502  @Messages({"SevenZipExtractor.indexError.message=Failed to index encryption detected artifact for keyword search.",
503  "# {0} - rootArchive",
504  "SevenZipExtractor.zipBombArtifactCreation.text=Zip Bomb Detected {0}"})
505  boolean unpack(AbstractFile archiveFile, ConcurrentHashMap<Long, Archive> depthMap, String password) {
506  boolean unpackSuccessful = true; //initialized to true change to false if any files fail to extract and
507  boolean hasEncrypted = false;
508  boolean fullEncryption = true;
509  boolean progressStarted = false;
510  final String archiveFilePath = getArchiveFilePath(archiveFile);
511  final String escapedArchiveFilePath = FileUtil.escapeFileName(archiveFilePath);
512  HashMap<String, ZipFileStatusWrapper> statusMap = new HashMap<>();
513  List<AbstractFile> unpackedFiles = Collections.<AbstractFile>emptyList();
514 
515  currentArchiveName = archiveFile.getName();
516 
517  SevenZipContentReadStream stream = null;
518  progress = ProgressHandle.createHandle(Bundle.EmbeddedFileExtractorIngestModule_ArchiveExtractor_moduleName());
519  //recursion depth check for zip bomb
520  Archive parentAr;
521  try {
522  blackboard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard();
523  } catch (NoCurrentCaseException ex) {
524  logger.log(Level.INFO, "Exception while getting open case.", ex); //NON-NLS
525  unpackSuccessful = false;
526  return unpackSuccessful;
527  }
528  try {
529  List<AbstractFile> existingFiles = getAlreadyExtractedFiles(archiveFile, archiveFilePath);
530  for (AbstractFile file : existingFiles) {
531  statusMap.put(getKeyAbstractFile(file), new ZipFileStatusWrapper(file, ZipFileStatus.EXISTS));
532  }
533  } catch (TskCoreException e) {
534  logger.log(Level.INFO, "Error checking if file already has been processed, skipping: {0}", escapedArchiveFilePath); //NON-NLS
535  unpackSuccessful = false;
536  return unpackSuccessful;
537  } catch (NoCurrentCaseException ex) {
538  logger.log(Level.INFO, "No open case was found while trying to unpack the archive file {0}", escapedArchiveFilePath); //NON-NLS
539  unpackSuccessful = false;
540  return unpackSuccessful;
541  }
542  parentAr = depthMap.get(archiveFile.getId());
543  if (parentAr == null) {
544  parentAr = new Archive(0, archiveFile.getId(), archiveFile);
545  depthMap.put(archiveFile.getId(), parentAr);
546  } else {
547  Archive rootArchive = depthMap.get(parentAr.getRootArchiveId());
548  if (rootArchive.isFlaggedAsZipBomb()) {
549  //skip this archive as the root archive has already been determined to contain a zip bomb
550  unpackSuccessful = false;
551  return unpackSuccessful;
552  } else if (parentAr.getDepth() == MAX_DEPTH) {
553  String details = NbBundle.getMessage(SevenZipExtractor.class,
554  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnDetails.zipBomb",
555  parentAr.getDepth(), FileUtil.escapeFileName(getArchiveFilePath(rootArchive.getArchiveFile())));
556  flagRootArchiveAsZipBomb(rootArchive, archiveFile, details, escapedArchiveFilePath);
557  unpackSuccessful = false;
558  return unpackSuccessful;
559  }
560  }
561  IInArchive inArchive = null;
562  try {
563  stream = new SevenZipContentReadStream(new ReadContentInputStream(archiveFile));
564  // for RAR files we need to open them explicitly as RAR. Otherwise, if there is a ZIP archive inside RAR archive
565  // it will be opened incorrectly when using 7zip's built-in auto-detect functionality.
566  // All other archive formats are still opened using 7zip built-in auto-detect functionality.
567  ArchiveFormat options = get7ZipOptions(archiveFile);
568  if (password == null) {
569  inArchive = SevenZip.openInArchive(options, stream);
570  } else {
571  inArchive = SevenZip.openInArchive(options, stream, password);
572  }
573  numItems = inArchive.getNumberOfItems();
574  progress.start(numItems);
575  progressStarted = true;
576 
577  //setup the archive local root folder
578  final String uniqueArchiveFileName = FileUtil.escapeFileName(EmbeddedFileExtractorIngestModule.getUniqueName(archiveFile));
579  try {
580  makeLocalDirectories(uniqueArchiveFileName);
581  } catch (SecurityException e) {
582  logger.log(Level.SEVERE, "Error setting up output path for archive root: {0}", getLocalRootAbsPath(uniqueArchiveFileName)); //NON-NLS
583  //bail
584  unpackSuccessful = false;
585  return unpackSuccessful;
586  }
587 
588  //initialize tree hierarchy to keep track of unpacked file structure
589  SevenZipExtractor.UnpackedTree unpackedTree = new SevenZipExtractor.UnpackedTree(moduleDirRelative + "/" + uniqueArchiveFileName, archiveFile);
590 
591  long freeDiskSpace;
592  try {
593  freeDiskSpace = services.getFreeDiskSpace();
594  } catch (NullPointerException ex) {
595  //If ingest has not been run at least once getFreeDiskSpace() will throw a null pointer exception
596  //currently getFreeDiskSpace always returns DISK_FREE_SPACE_UNKNOWN
597  freeDiskSpace = IngestMonitor.DISK_FREE_SPACE_UNKNOWN;
598  }
599 
600  Map<Integer, InArchiveItemDetails> archiveDetailsMap = new HashMap<>();
601  for (int inArchiveItemIndex = 0; inArchiveItemIndex < numItems; inArchiveItemIndex++) {
602  progress.progress(String.format("%s: Analyzing archive metadata and creating local files (%d of %d)", currentArchiveName, inArchiveItemIndex + 1, numItems), 0);
603  if (isZipBombArchiveItemCheck(archiveFile, inArchive, inArchiveItemIndex, depthMap, escapedArchiveFilePath)) {
604  unpackSuccessful = false;
605  return unpackSuccessful;
606  }
607 
608  String pathInArchive = getPathInArchive(inArchive, inArchiveItemIndex, archiveFile);
609  byte[] pathBytesInArchive = getPathBytesInArchive(inArchive, inArchiveItemIndex, archiveFile);
610  UnpackedTree.UnpackedNode unpackedNode = unpackedTree.addNode(pathInArchive, pathBytesInArchive);
611 
612  final boolean isEncrypted = (Boolean) inArchive.getProperty(inArchiveItemIndex, PropID.ENCRYPTED);
613 
614  if (isEncrypted && password == null) {
615  logger.log(Level.WARNING, "Skipping encrypted file in archive: {0}", pathInArchive); //NON-NLS
616  hasEncrypted = true;
617  unpackSuccessful = false;
618  continue;
619  } else {
620  fullEncryption = false;
621  }
622 
623  // NOTE: item size may return null in case of certain
624  // archiving formats. Eg: BZ2
625  //check if unpacking this file will result in out of disk space
626  //this is additional to zip bomb prevention mechanism
627  Long archiveItemSize = (Long) inArchive.getProperty(
628  inArchiveItemIndex, PropID.SIZE);
629  if (freeDiskSpace != IngestMonitor.DISK_FREE_SPACE_UNKNOWN && archiveItemSize != null && archiveItemSize > 0) { //if free space is known and file is not empty.
630  String archiveItemPath = (String) inArchive.getProperty(
631  inArchiveItemIndex, PropID.PATH);
632  long newDiskSpace = freeDiskSpace - archiveItemSize;
633  if (newDiskSpace < MIN_FREE_DISK_SPACE) {
634  String msg = NbBundle.getMessage(SevenZipExtractor.class,
635  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.msg",
636  escapedArchiveFilePath, archiveItemPath);
637  String details = NbBundle.getMessage(SevenZipExtractor.class,
638  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.details");
639  services.postMessage(IngestMessage.createErrorMessage(MODULE_NAME, msg, details));
640  logger.log(Level.INFO, "Skipping archive item due to insufficient disk space: {0}, {1}", new String[]{escapedArchiveFilePath, archiveItemPath}); //NON-NLS
641  logger.log(Level.INFO, "Available disk space: {0}", new Object[]{freeDiskSpace}); //NON-NLS
642  unpackSuccessful = false;
643  continue; //skip this file
644  } else {
645  //update est. disk space during this archive, so we don't need to poll for every file extracted
646  freeDiskSpace = newDiskSpace;
647  }
648  }
649  final String uniqueExtractedName = FileUtil.escapeFileName(uniqueArchiveFileName + File.separator + (inArchiveItemIndex / 1000) + File.separator + inArchiveItemIndex + "_" + new File(pathInArchive).getName());
650  final String localAbsPath = moduleDirAbsolute + File.separator + uniqueExtractedName;
651  final String localRelPath = moduleDirRelative + File.separator + uniqueExtractedName;
652 
653  //create local dirs and empty files before extracted
654  File localFile = new java.io.File(localAbsPath);
655  //cannot rely on files in top-bottom order
656  if (!localFile.exists()) {
657  try {
658  if ((Boolean) inArchive.getProperty(
659  inArchiveItemIndex, PropID.IS_FOLDER)) {
660  localFile.mkdirs();
661  } else {
662  localFile.getParentFile().mkdirs();
663  try {
664  localFile.createNewFile();
665  } catch (IOException e) {
666  logger.log(Level.SEVERE, "Error creating extracted file: "//NON-NLS
667  + localFile.getAbsolutePath(), e);
668  }
669  }
670  } catch (SecurityException e) {
671  logger.log(Level.SEVERE, "Error setting up output path for unpacked file: {0}", //NON-NLS
672  pathInArchive); //NON-NLS
673  //TODO consider bail out / msg to the user
674  }
675  }
676  // skip the rest of this loop if we couldn't create the file
677  //continue will skip details from being added to the map
678  if (localFile.exists() == false) {
679  continue;
680  }
681 
682  //Store archiveItemIndex with local paths and unpackedNode reference.
683  //Necessary for the extract call back to write the current archive
684  //file to the correct disk location and to correctly update it's
685  //corresponding unpackedNode
686  archiveDetailsMap.put(inArchiveItemIndex, new InArchiveItemDetails(
687  unpackedNode, localAbsPath, localRelPath));
688  }
689 
690  int[] extractionIndices = getExtractableFilesFromDetailsMap(archiveDetailsMap);
691 
692  StandardIArchiveExtractCallback archiveCallBack
693  = new StandardIArchiveExtractCallback(
694  inArchive, archiveFile, progress,
695  archiveDetailsMap, password, freeDiskSpace);
696 
697  //According to the documentation, indices in sorted order are optimal
698  //for efficiency. Hence, the HashMap and linear processing of
699  //inArchiveItemIndex. False indicates non-test mode
700  inArchive.extract(extractionIndices, false, archiveCallBack);
701 
702  unpackSuccessful &= archiveCallBack.wasSuccessful();
703 
704  archiveDetailsMap = null;
705 
706  // add them to the DB. We wait until the end so that we have the metadata on all of the
707  // intermediate nodes since the order is not guaranteed
708  try {
709  unpackedTree.updateOrAddFileToCaseRec(statusMap, archiveFilePath);
710  unpackedFiles = unpackedTree.getAllFileObjects();
711  //check if children are archives, update archive depth tracking
712  for (int i = 0; i < unpackedFiles.size(); i++) {
713  progress.progress(String.format("%s: Searching for nested archives (%d of %d)", currentArchiveName, i + 1, unpackedFiles.size()));
714  AbstractFile unpackedFile = unpackedFiles.get(i);
715  if (unpackedFile == null) {
716  continue;
717  }
718  if (isSevenZipExtractionSupported(unpackedFile)) {
719  Archive child = new Archive(parentAr.getDepth() + 1, parentAr.getRootArchiveId(), archiveFile);
720  parentAr.addChild(child);
721  depthMap.put(unpackedFile.getId(), child);
722  }
723  unpackedFile.close();
724  }
725 
726  } catch (TskCoreException | NoCurrentCaseException e) {
727  logger.log(Level.SEVERE, "Error populating complete derived file hierarchy from the unpacked dir structure", e); //NON-NLS
728  //TODO decide if anything to cleanup, for now bailing
729  }
730 
731  } catch (SevenZipException ex) {
732  logger.log(Level.WARNING, "Error unpacking file: " + archiveFile, ex); //NON-NLS
733  //inbox message
734 
735  // print a message if the file is allocated
736  if (archiveFile.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC)) {
737  String msg = NbBundle.getMessage(SevenZipExtractor.class,
738  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.errUnpacking.msg",
739  currentArchiveName);
740  String details = NbBundle.getMessage(SevenZipExtractor.class,
741  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.errUnpacking.details",
742  escapedArchiveFilePath, ex.getMessage());
743  services.postMessage(IngestMessage.createErrorMessage(MODULE_NAME, msg, details));
744  }
745  } finally {
746  if (inArchive != null) {
747  try {
748  inArchive.close();
749  } catch (SevenZipException e) {
750  logger.log(Level.SEVERE, "Error closing archive: " + archiveFile, e); //NON-NLS
751  }
752  }
753 
754  if (stream != null) {
755  try {
756  stream.close();
757  } catch (IOException ex) {
758  logger.log(Level.SEVERE, "Error closing stream after unpacking archive: " + archiveFile, ex); //NON-NLS
759  }
760  }
761 
762  //close progress bar
763  if (progressStarted) {
764  progress.finish();
765  }
766  }
767 
768  //create artifact and send user message
769  if (hasEncrypted) {
770  String encryptionType = fullEncryption ? ENCRYPTION_FULL : ENCRYPTION_FILE_LEVEL;
771  try {
772  BlackboardArtifact artifact = archiveFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED);
773  artifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT, MODULE_NAME, encryptionType));
774 
775  try {
776  /*
777  * post the artifact which will index the artifact for
778  * keyword search, and fire an event to notify UI of this
779  * new artifact
780  */
781  blackboard.postArtifact(artifact, MODULE_NAME);
782  } catch (Blackboard.BlackboardException ex) {
783  logger.log(Level.SEVERE, "Unable to post blackboard artifact " + artifact.getArtifactID(), ex); //NON-NLS
784  MessageNotifyUtil.Notify.error(
785  Bundle.SevenZipExtractor_indexError_message(), artifact.getDisplayName());
786  }
787 
788  } catch (TskCoreException ex) {
789  logger.log(Level.SEVERE, "Error creating blackboard artifact for encryption detected for file: " + escapedArchiveFilePath, ex); //NON-NLS
790  }
791 
792  String msg = NbBundle.getMessage(SevenZipExtractor.class,
793  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.encrFileDetected.msg");
794  String details = NbBundle.getMessage(SevenZipExtractor.class,
795  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.encrFileDetected.details",
796  currentArchiveName, MODULE_NAME);
797  services.postMessage(IngestMessage.createWarningMessage(MODULE_NAME, msg, details));
798  }
799 
800  // adding unpacked extracted derived files to the job after closing relevant resources.
801  if (!unpackedFiles.isEmpty()) {
802  //currently sending a single event for all new files
803  services.fireModuleContentEvent(new ModuleContentEvent(archiveFile));
804  if (context != null) {
805  context.addFilesToJob(unpackedFiles);
806  }
807  }
808  return unpackSuccessful;
809  }
810 
811  private Charset detectFilenamesCharset(List<byte[]> byteDatas) {
812  Charset detectedCharset = null;
813  CharsetDetector charsetDetector = new CharsetDetector();
814  int byteSum = 0;
815  int fileNum = 0;
816  for (byte[] byteData : byteDatas) {
817  fileNum++;
818  byteSum += byteData.length;
819  // Only read ~1000 bytes of filenames in this directory
820  if (byteSum >= 1000) {
821  break;
822  }
823  }
824  byte[] allBytes = new byte[byteSum];
825  int start = 0;
826  for (int i = 0; i < fileNum; i++) {
827  byte[] byteData = byteDatas.get(i);
828  System.arraycopy(byteData, 0, allBytes, start, byteData.length);
829  start += byteData.length;
830  }
831  charsetDetector.setText(allBytes);
832  CharsetMatch cm = charsetDetector.detect();
833  if (cm.getConfidence() >= 90 && Charset.isSupported(cm.getName())) {
834  detectedCharset = Charset.forName(cm.getName());
835  }
836  return detectedCharset;
837  }
838 
843  private int[] getExtractableFilesFromDetailsMap(
844  Map<Integer, InArchiveItemDetails> archiveDetailsMap) {
845 
846  Integer[] wrappedExtractionIndices = archiveDetailsMap.keySet()
847  .toArray(new Integer[archiveDetailsMap.size()]);
848 
849  return Arrays.stream(wrappedExtractionIndices)
850  .mapToInt(Integer::intValue)
851  .toArray();
852  }
853 
861  private final static class UnpackStream implements ISequentialOutStream {
862 
863  private EncodedFileOutputStream output;
864  private String localAbsPath;
865  private int bytesWritten;
866 
867  UnpackStream(String localAbsPath) throws IOException {
868  this.output = new EncodedFileOutputStream(new FileOutputStream(localAbsPath), TskData.EncodingType.XOR1);
869  this.localAbsPath = localAbsPath;
870  this.bytesWritten = 0;
871  }
872 
873  public void setNewOutputStream(String localAbsPath) throws IOException {
874  this.output.close();
875  this.output = new EncodedFileOutputStream(new FileOutputStream(localAbsPath), TskData.EncodingType.XOR1);
876  this.localAbsPath = localAbsPath;
877  this.bytesWritten = 0;
878  }
879 
880  public int getSize() {
881  return bytesWritten;
882  }
883 
884  @Override
885  public int write(byte[] bytes) throws SevenZipException {
886  try {
887  output.write(bytes);
888  this.bytesWritten += bytes.length;
889  } catch (IOException ex) {
890  throw new SevenZipException(
891  NbBundle.getMessage(SevenZipExtractor.class,
892  "EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackStream.write.exception.msg",
893  localAbsPath), ex);
894  }
895  return bytes.length;
896  }
897 
898  public void close() throws IOException {
899  try (EncodedFileOutputStream out = output) {
900  out.flush();
901  }
902  }
903 
904  }
905 
909  private static class InArchiveItemDetails {
910 
911  private final SevenZipExtractor.UnpackedTree.UnpackedNode unpackedNode;
912  private final String localAbsPath;
913  private final String localRelPath;
914 
916  SevenZipExtractor.UnpackedTree.UnpackedNode unpackedNode,
917  String localAbsPath, String localRelPath) {
918  this.unpackedNode = unpackedNode;
919  this.localAbsPath = localAbsPath;
920  this.localRelPath = localRelPath;
921  }
922 
923  public SevenZipExtractor.UnpackedTree.UnpackedNode getUnpackedNode() {
924  return unpackedNode;
925  }
926 
927  public String getLocalAbsPath() {
928  return localAbsPath;
929  }
930 
931  public String getLocalRelPath() {
932  return localRelPath;
933  }
934  }
935 
940  private static class StandardIArchiveExtractCallback
941  implements IArchiveExtractCallback, ICryptoGetTextPassword {
942 
943  private final AbstractFile archiveFile;
944  private final IInArchive inArchive;
945  private UnpackStream unpackStream = null;
946  private final Map<Integer, InArchiveItemDetails> archiveDetailsMap;
947  private final ProgressHandle progressHandle;
948 
949  private int inArchiveItemIndex;
950 
951  private long createTimeInSeconds;
952  private long modTimeInSeconds;
953  private long accessTimeInSeconds;
954 
955  private boolean isFolder;
956  private final String password;
957 
958  private boolean unpackSuccessful = true;
959 
960  StandardIArchiveExtractCallback(IInArchive inArchive,
961  AbstractFile archiveFile, ProgressHandle progressHandle,
962  Map<Integer, InArchiveItemDetails> archiveDetailsMap,
963  String password, long freeDiskSpace) {
964  this.inArchive = inArchive;
965  this.progressHandle = progressHandle;
966  this.archiveFile = archiveFile;
967  this.archiveDetailsMap = archiveDetailsMap;
968  this.password = password;
969  }
970 
985  @Override
986  public ISequentialOutStream getStream(int inArchiveItemIndex,
987  ExtractAskMode mode) throws SevenZipException {
988 
989  this.inArchiveItemIndex = inArchiveItemIndex;
990 
991  isFolder = (Boolean) inArchive
992  .getProperty(inArchiveItemIndex, PropID.IS_FOLDER);
993  if (isFolder || mode != ExtractAskMode.EXTRACT) {
994  return null;
995  }
996 
997  final String localAbsPath = archiveDetailsMap.get(
998  inArchiveItemIndex).getLocalAbsPath();
999 
1000  //If the Unpackstream has been allocated, then set the Outputstream
1001  //to another file rather than creating a new unpack stream. The 7Zip
1002  //binding has a memory leak, so creating new unpack streams will not be
1003  //dereferenced. As a fix, we create one UnpackStream, and mutate its state,
1004  //so that there only exists one 8192 byte buffer in memory per archive.
1005  try {
1006  if (unpackStream != null) {
1007  unpackStream.setNewOutputStream(localAbsPath);
1008  } else {
1009  unpackStream = new UnpackStream(localAbsPath);
1010  }
1011  } catch (IOException ex) {
1012  logger.log(Level.WARNING, String.format("Error opening or setting new stream " //NON-NLS
1013  + "for archive file at %s", localAbsPath), ex.getMessage()); //NON-NLS
1014  return null;
1015  }
1016 
1017  return unpackStream;
1018  }
1019 
1028  @Override
1029  public void prepareOperation(ExtractAskMode mode) throws SevenZipException {
1030  final Date createTime = (Date) inArchive.getProperty(
1031  inArchiveItemIndex, PropID.CREATION_TIME);
1032  final Date accessTime = (Date) inArchive.getProperty(
1033  inArchiveItemIndex, PropID.LAST_ACCESS_TIME);
1034  final Date writeTime = (Date) inArchive.getProperty(
1035  inArchiveItemIndex, PropID.LAST_MODIFICATION_TIME);
1036 
1037  createTimeInSeconds = createTime == null ? 0L
1038  : createTime.getTime() / 1000;
1039  modTimeInSeconds = writeTime == null ? 0L
1040  : writeTime.getTime() / 1000;
1041  accessTimeInSeconds = accessTime == null ? 0L
1042  : accessTime.getTime() / 1000;
1043 
1044  progressHandle.progress(archiveFile.getName() + ": "
1045  + (String) inArchive.getProperty(inArchiveItemIndex, PropID.PATH),
1047 
1048  }
1049 
1058  @Override
1059  public void setOperationResult(ExtractOperationResult result) throws SevenZipException {
1060 
1061  final SevenZipExtractor.UnpackedTree.UnpackedNode unpackedNode
1062  = archiveDetailsMap.get(inArchiveItemIndex).getUnpackedNode();
1063  final String localRelPath = archiveDetailsMap.get(
1064  inArchiveItemIndex).getLocalRelPath();
1065  if (isFolder) {
1066  unpackedNode.addDerivedInfo(0,
1067  !(Boolean) inArchive.getProperty(inArchiveItemIndex, PropID.IS_FOLDER),
1069  localRelPath);
1070  return;
1071  }
1072 
1073  final String localAbsPath = archiveDetailsMap.get(
1074  inArchiveItemIndex).getLocalAbsPath();
1075  if (result != ExtractOperationResult.OK) {
1076  logger.log(Level.WARNING, "Extraction of : {0} encountered error {1}", //NON-NLS
1077  new Object[]{localAbsPath, result});
1078  unpackSuccessful = false;
1079  }
1080 
1081  //record derived data in unode, to be traversed later after unpacking the archive
1082  unpackedNode.addDerivedInfo(unpackStream.getSize(),
1083  !(Boolean) inArchive.getProperty(inArchiveItemIndex, PropID.IS_FOLDER),
1085 
1086  try {
1087  unpackStream.close();
1088  } catch (IOException e) {
1089  logger.log(Level.WARNING, "Error closing unpack stream for file: {0}", localAbsPath); //NON-NLS
1090  }
1091  }
1092 
1093  @Override
1094  public void setTotal(long value) throws SevenZipException {
1095  //Not necessary for extract, left intenionally blank
1096  }
1097 
1098  @Override
1099  public void setCompleted(long value) throws SevenZipException {
1100  //Not necessary for extract, left intenionally blank
1101  }
1102 
1110  @Override
1111  public String cryptoGetTextPassword() throws SevenZipException {
1112  return password;
1113  }
1114 
1115  public boolean wasSuccessful() {
1116  return unpackSuccessful;
1117  }
1118  }
1119 
1127  private class UnpackedTree {
1128 
1129  final UnpackedNode rootNode;
1130  private int nodesProcessed = 0;
1131 
1138  UnpackedTree(String localPathRoot, AbstractFile archiveFile) {
1139  this.rootNode = new UnpackedNode();
1140  this.rootNode.setFile(archiveFile);
1141  this.rootNode.setFileName(archiveFile.getName());
1142  this.rootNode.setLocalRelPath(localPathRoot);
1143  }
1144 
1154  UnpackedNode addNode(String filePath, byte[] filePathBytes) {
1155  String[] toks = filePath.split("[\\/\\\\]");
1156  List<String> tokens = new ArrayList<>();
1157  for (int i = 0; i < toks.length; ++i) {
1158  if (!toks[i].isEmpty()) {
1159  tokens.add(toks[i]);
1160  }
1161  }
1162 
1163  List<byte[]> byteTokens = null;
1164  if (filePathBytes == null) {
1165  return addNode(rootNode, tokens, null);
1166  } else {
1167  byteTokens = new ArrayList<>(tokens.size());
1168  int last = 0;
1169  for (int i = 0; i < filePathBytes.length; i++) {
1170  if (filePathBytes[i] == '/') {
1171  int len = i - last;
1172  byte[] arr = new byte[len];
1173  System.arraycopy(filePathBytes, last, arr, 0, len);
1174  byteTokens.add(arr);
1175  last = i + 1;
1176  }
1177  }
1178  int len = filePathBytes.length - last;
1179  if (len > 0) {
1180  byte[] arr = new byte[len];
1181  System.arraycopy(filePathBytes, last, arr, 0, len);
1182  byteTokens.add(arr);
1183  }
1184 
1185  if (tokens.size() != byteTokens.size()) {
1186  logger.log(Level.WARNING, "Could not map path bytes to path string");
1187  return addNode(rootNode, tokens, null);
1188  }
1189  }
1190 
1191  return addNode(rootNode, tokens, byteTokens);
1192  }
1193 
1204  List<String> tokenPath, List<byte[]> tokenPathBytes) {
1205  // we found all of the tokens
1206  if (tokenPath.isEmpty()) {
1207  return parent;
1208  }
1209 
1210  // get the next name in the path and look it up
1211  String childName = tokenPath.remove(0);
1212  byte[] childNameBytes = null;
1213  if (tokenPathBytes != null) {
1214  childNameBytes = tokenPathBytes.remove(0);
1215  }
1216  UnpackedNode child = parent.getChild(childName);
1217  // create new node
1218  if (child == null) {
1219  child = new UnpackedNode(childName, parent);
1220  child.setFileNameBytes(childNameBytes);
1221  parent.addChild(child);
1222  }
1223 
1224  // go down one more level
1225  return addNode(child, tokenPath, tokenPathBytes);
1226  }
1227 
1234  List<AbstractFile> getRootFileObjects() {
1235  List<AbstractFile> ret = new ArrayList<>();
1236  rootNode.getChildren().forEach((child) -> {
1237  ret.add(child.getFile());
1238  });
1239  return ret;
1240  }
1241 
1248  List<AbstractFile> getAllFileObjects() {
1249  List<AbstractFile> ret = new ArrayList<>();
1250  rootNode.getChildren().forEach((child) -> {
1251  getAllFileObjectsRec(ret, child);
1252  });
1253  return ret;
1254  }
1255 
1256  private void getAllFileObjectsRec(List<AbstractFile> list, UnpackedNode parent) {
1257  list.add(parent.getFile());
1258  parent.getChildren().forEach((child) -> {
1259  getAllFileObjectsRec(list, child);
1260  });
1261  }
1262 
1267  void updateOrAddFileToCaseRec(HashMap<String, ZipFileStatusWrapper> statusMap, String archiveFilePath) throws TskCoreException, NoCurrentCaseException {
1269  for (UnpackedNode child : rootNode.getChildren()) {
1270  updateOrAddFileToCaseRec(child, fileManager, statusMap, archiveFilePath);
1271  }
1272  }
1273 
1288  private void updateOrAddFileToCaseRec(UnpackedNode node, FileManager fileManager, HashMap<String, ZipFileStatusWrapper> statusMap, String archiveFilePath) throws TskCoreException {
1289  DerivedFile df;
1290  progress.progress(String.format("%s: Adding/updating files in case database (%d of %d)", currentArchiveName, ++nodesProcessed, numItems));
1291  try {
1292  String nameInDatabase = getKeyFromUnpackedNode(node, archiveFilePath);
1293  ZipFileStatusWrapper existingFile = nameInDatabase == null ? null : statusMap.get(nameInDatabase);
1294  if (existingFile == null) {
1295  df = fileManager.addDerivedFile(node.getFileName(), node.getLocalRelPath(), node.getSize(),
1296  node.getCtime(), node.getCrtime(), node.getAtime(), node.getMtime(),
1297  node.isIsFile(), node.getParent().getFile(), "", MODULE_NAME,
1298  "", "", TskData.EncodingType.XOR1);
1299  statusMap.put(getKeyAbstractFile(df), new ZipFileStatusWrapper(df, ZipFileStatus.EXISTS));
1300  } else {
1301  String key = getKeyAbstractFile(existingFile.getFile());
1302  if (existingFile.getStatus() == ZipFileStatus.EXISTS && existingFile.getFile().getSize() < node.getSize()) {
1303  existingFile.setStatus(ZipFileStatus.UPDATE);
1304  statusMap.put(key, existingFile);
1305  }
1306  if (existingFile.getStatus() == ZipFileStatus.UPDATE) {
1307  //if the we are updating a file and its mime type was octet-stream we want to re-type it
1308  String mimeType = existingFile.getFile().getMIMEType().equalsIgnoreCase("application/octet-stream") ? null : existingFile.getFile().getMIMEType();
1309  df = fileManager.updateDerivedFile((DerivedFile) existingFile.getFile(), node.getLocalRelPath(), node.getSize(),
1310  node.getCtime(), node.getCrtime(), node.getAtime(), node.getMtime(),
1311  node.isIsFile(), mimeType, "", MODULE_NAME,
1312  "", "", TskData.EncodingType.XOR1);
1313  } else {
1314  //ALREADY CURRENT - SKIP
1315  statusMap.put(key, new ZipFileStatusWrapper(existingFile.getFile(), ZipFileStatus.SKIP));
1316  df = (DerivedFile) existingFile.getFile();
1317  }
1318  }
1319  node.setFile(df);
1320  } catch (TskCoreException ex) {
1321  logger.log(Level.SEVERE, "Error adding a derived file to db:" + node.getFileName(), ex); //NON-NLS
1322  throw new TskCoreException(
1323  NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackedTree.exception.msg",
1324  node.getFileName()), ex);
1325  }
1326 
1327  // Determine encoding of children
1328  if (node.getChildren().size() > 0) {
1329  String names = "";
1330  ArrayList<byte[]> byteDatas = new ArrayList<>();
1331  for (UnpackedNode child : node.getChildren()) {
1332  byte[] childBytes = child.getFileNameBytes();
1333  if (childBytes != null) {
1334  byteDatas.add(childBytes);
1335  }
1336  names += child.getFileName();
1337  }
1338  Charset detectedCharset = detectFilenamesCharset(byteDatas);
1339 
1340  // If a charset was detected, transcode filenames accordingly
1341  if (detectedCharset != null && detectedCharset.canEncode()) {
1342  for (UnpackedNode child : node.getChildren()) {
1343  byte[] childBytes = child.getFileNameBytes();
1344  if (childBytes != null) {
1345  String decodedName = new String(childBytes, detectedCharset);
1346  child.setFileName(decodedName);
1347  }
1348  }
1349  }
1350  }
1351 
1352  //recurse adding the children if this file was incomplete the children presumably need to be added
1353  for (UnpackedNode child : node.getChildren()) {
1354  updateOrAddFileToCaseRec(child, fileManager, statusMap, getKeyFromUnpackedNode(node, archiveFilePath));
1355  }
1356  }
1357 
1361  private class UnpackedNode {
1362 
1363  private String fileName;
1364  private byte[] fileNameBytes;
1365  private AbstractFile file;
1366  private final List<UnpackedNode> children = new ArrayList<>();
1367  private String localRelPath = "";
1368  private long size;
1369  private long ctime, crtime, atime, mtime;
1370  private boolean isFile;
1372 
1373  //root constructor
1374  UnpackedNode() {
1375  }
1376 
1377  //child node constructor
1378  UnpackedNode(String fileName, UnpackedNode parent) {
1379  this.fileName = fileName;
1380  this.parent = parent;
1381  this.localRelPath = parent.getLocalRelPath() + File.separator + fileName;
1382  }
1383 
1384  long getCtime() {
1385  return ctime;
1386  }
1387 
1388  long getCrtime() {
1389  return crtime;
1390  }
1391 
1392  long getAtime() {
1393  return atime;
1394  }
1395 
1396  long getMtime() {
1397  return mtime;
1398  }
1399 
1400  void setFileName(String fileName) {
1401  this.fileName = fileName;
1402  }
1403 
1409  void addChild(UnpackedNode child) {
1410  children.add(child);
1411  }
1412 
1419  List<UnpackedNode> getChildren() {
1420  return children;
1421  }
1422 
1428  UnpackedNode getParent() {
1429  return parent;
1430  }
1431 
1432  void addDerivedInfo(long size,
1433  boolean isFile,
1434  long ctime, long crtime, long atime, long mtime, String relLocalPath) {
1435  this.size = size;
1436  this.isFile = isFile;
1437  this.ctime = ctime;
1438  this.crtime = crtime;
1439  this.atime = atime;
1440  this.mtime = mtime;
1441  this.localRelPath = relLocalPath;
1442  }
1443 
1444  void setFile(AbstractFile file) {
1445  this.file = file;
1446  }
1447 
1455  UnpackedNode getChild(String childFileName) {
1456  UnpackedNode ret = null;
1457  for (UnpackedNode child : children) {
1458  if (child.getFileName().equals(childFileName)) {
1459  ret = child;
1460  break;
1461  }
1462  }
1463  return ret;
1464  }
1465 
1466  String getFileName() {
1467  return fileName;
1468  }
1469 
1470  AbstractFile getFile() {
1471  return file;
1472  }
1473 
1474  String getLocalRelPath() {
1475  return localRelPath;
1476  }
1477 
1484  void setLocalRelPath(String localRelativePath) {
1485  localRelPath = localRelativePath;
1486  }
1487 
1488  long getSize() {
1489  return size;
1490  }
1491 
1492  boolean isIsFile() {
1493  return isFile;
1494  }
1495 
1496  void setFileNameBytes(byte[] fileNameBytes) {
1497  if (fileNameBytes != null) {
1498  this.fileNameBytes = Arrays.copyOf(fileNameBytes, fileNameBytes.length);
1499  }
1500  }
1501 
1502  byte[] getFileNameBytes() {
1503  if (fileNameBytes == null) {
1504  return null;
1505  }
1506  return Arrays.copyOf(fileNameBytes, fileNameBytes.length);
1507  }
1508  }
1509  }
1510 
1515  static class Archive {
1516 
1517  //depth will be 0 for the root archive unpack was called on, and increase as unpack recurses down through archives contained within
1518  private final int depth;
1519  private final List<Archive> children;
1520  private final long rootArchiveId;
1521  private boolean flaggedAsZipBomb = false;
1522  private final AbstractFile archiveFile;
1523 
1536  Archive(int depth, long rootArchiveId, AbstractFile archiveFile) {
1537  this.children = new ArrayList<>();
1538  this.depth = depth;
1539  this.rootArchiveId = rootArchiveId;
1540  this.archiveFile = archiveFile;
1541  }
1542 
1549  void addChild(Archive child) {
1550  children.add(child);
1551  }
1552 
1557  synchronized void flagAsZipBomb() {
1558  flaggedAsZipBomb = true;
1559  }
1560 
1566  synchronized boolean isFlaggedAsZipBomb() {
1567  return flaggedAsZipBomb;
1568  }
1569 
1575  AbstractFile getArchiveFile() {
1576  return archiveFile;
1577  }
1578 
1584  long getRootArchiveId() {
1585  return rootArchiveId;
1586  }
1587 
1593  long getObjectId() {
1594  return archiveFile.getId();
1595  }
1596 
1604  int getDepth() {
1605  return depth;
1606  }
1607  }
1608 
1613  private final class ZipFileStatusWrapper {
1614 
1615  private final AbstractFile abstractFile;
1617 
1625  private ZipFileStatusWrapper(AbstractFile file, ZipFileStatus status) {
1626  abstractFile = file;
1627  zipStatus = status;
1628  }
1629 
1635  private AbstractFile getFile() {
1636  return abstractFile;
1637  }
1638 
1645  return zipStatus;
1646  }
1647 
1653  private void setStatus(ZipFileStatus status) {
1654  zipStatus = status;
1655  }
1656 
1657  }
1658 
1663  private enum ZipFileStatus {
1664  UPDATE, //Should be updated //NON-NLS
1665  SKIP, //File is current can be skipped //NON-NLS
1666  EXISTS //File exists but it is unknown if it is current //NON-NLS
1667  }
1668 }
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-2019 Basis Technology. Generated on: Tue Jan 7 2020
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.