19 package org.sleuthkit.autopsy.modules.embeddedfileextractor;
 
   22 import java.io.FileOutputStream;
 
   23 import java.io.IOException;
 
   24 import java.nio.charset.Charset;
 
   25 import java.nio.file.Path;
 
   26 import java.nio.file.Paths;
 
   27 import java.util.ArrayList;
 
   28 import java.util.Arrays;
 
   29 import java.util.Collection;
 
   30 import java.util.Collections;
 
   31 import java.util.Date;
 
   32 import java.util.HashMap;
 
   33 import java.util.List;
 
   35 import java.util.concurrent.ConcurrentHashMap;
 
   36 import java.util.logging.Level;
 
   37 import net.sf.sevenzipjbinding.ArchiveFormat;
 
   38 import static net.sf.sevenzipjbinding.ArchiveFormat.RAR;
 
   39 import net.sf.sevenzipjbinding.ExtractAskMode;
 
   40 import net.sf.sevenzipjbinding.ExtractOperationResult;
 
   41 import net.sf.sevenzipjbinding.IArchiveExtractCallback;
 
   42 import net.sf.sevenzipjbinding.ICryptoGetTextPassword;
 
   43 import net.sf.sevenzipjbinding.IInArchive;
 
   44 import net.sf.sevenzipjbinding.ISequentialOutStream;
 
   45 import net.sf.sevenzipjbinding.PropID;
 
   46 import net.sf.sevenzipjbinding.SevenZip;
 
   47 import net.sf.sevenzipjbinding.SevenZipException;
 
   48 import net.sf.sevenzipjbinding.SevenZipNativeInitializationException;
 
   49 import org.apache.tika.Tika;
 
   50 import org.apache.tika.parser.txt.CharsetDetector;
 
   51 import org.apache.tika.parser.txt.CharsetMatch;
 
   52 import org.netbeans.api.progress.ProgressHandle;
 
   53 import org.openide.util.NbBundle;
 
   54 import org.openide.util.NbBundle.Messages;
 
   72 import static org.
sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT;
 
   74 import static org.
sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT;
 
   75 import static org.
sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DESCRIPTION;
 
   76 import static org.
sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME;
 
   82 import org.
sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction;
 
   90 class SevenZipExtractor {
 
   92     private static final Logger logger = Logger.getLogger(SevenZipExtractor.class.getName());
 
   94     private static final String MODULE_NAME = EmbeddedFileExtractorModuleFactory.getModuleName();
 
   97     private static final String ENCRYPTION_FILE_LEVEL = NbBundle.getMessage(EmbeddedFileExtractorIngestModule.class,
 
   98             "EmbeddedFileExtractorIngestModule.ArchiveExtractor.encryptionFileLevel");
 
   99     private static final String ENCRYPTION_FULL = EncryptionDetectionModuleFactory.PASSWORD_PROTECT_MESSAGE;
 
  102     private static final int MAX_DEPTH = 4;
 
  103     private static final int MAX_COMPRESSION_RATIO = 600;
 
  104     private static final long MIN_COMPRESSION_RATIO_SIZE = 500 * 1000000L;
 
  105     private static final long MIN_FREE_DISK_SPACE = 1 * 1000 * 1000000L; 
 
  107     private IngestServices services = IngestServices.getInstance();
 
  108     private final IngestJobContext context;
 
  109     private final FileTypeDetector fileTypeDetector;
 
  110     private final FileTaskExecutor fileTaskExecutor;
 
  112     private String moduleDirRelative;
 
  113     private String moduleDirAbsolute;
 
  115     private Blackboard blackboard;
 
  117     private ProgressHandle progress;
 
  118     private int numItems;
 
  119     private String currentArchiveName;
 
  133         XRAR(
"application/x-rar-compressed"); 
 
  138             this.mimeType = mimeType;
 
  143             return this.mimeType;
 
  168     SevenZipExtractor(
IngestJobContext context, 
FileTypeDetector fileTypeDetector, String moduleDirRelative, String moduleDirAbsolute, FileTaskExecutor fileTaskExecutor) 
throws SevenZipNativeInitializationException {
 
  169         if (!SevenZip.isInitializedSuccessfully()) {
 
  170             throw new SevenZipNativeInitializationException(
"SevenZip has not been previously initialized.");
 
  172         this.context = context;
 
  173         this.fileTypeDetector = fileTypeDetector;
 
  174         this.moduleDirRelative = moduleDirRelative;
 
  175         this.moduleDirAbsolute = moduleDirAbsolute;
 
  176         this.fileTaskExecutor = fileTaskExecutor;
 
  187     boolean isSevenZipExtractionSupported(AbstractFile file) {
 
  188         String fileMimeType = fileTypeDetector.getMIMEType(file);
 
  189         for (SupportedArchiveExtractionFormats mimeType : SupportedArchiveExtractionFormats.values()) {
 
  190             if (checkForIngestCancellation(file)) {
 
  193             if (mimeType.toString().equals(fileMimeType)) {
 
  200     boolean isSevenZipExtractionSupported(String mimeType) {
 
  201         for (SupportedArchiveExtractionFormats supportedMimeType : SupportedArchiveExtractionFormats.values()) {
 
  202             if (mimeType.contains(supportedMimeType.toString())) {
 
  219     private boolean checkForIngestCancellation(AbstractFile file) {
 
  220         if (fileTaskExecutor != null && context != null && context.fileIngestIsCancelled()) {
 
  221             logger.log(Level.INFO, 
"Ingest was cancelled. Results extracted from the following archive file may be incomplete. Name: {0}Object ID: {1}", 
new Object[]{file.getName(), file.getId()});
 
  250     private boolean isZipBombArchiveItemCheck(AbstractFile archiveFile, IInArchive inArchive, 
int inArchiveItemIndex, ConcurrentHashMap<Long, Archive> depthMap, String escapedFilePath) {
 
  260         if (archiveFile.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.UNALLOC) || archiveFile.getMIMEType().equalsIgnoreCase(SupportedArchiveExtractionFormats.XGZIP.toString())) {
 
  265             final Long archiveItemSize = (Long) inArchive.getProperty(
 
  266                     inArchiveItemIndex, PropID.SIZE);
 
  269             if (archiveItemSize == null || archiveItemSize < MIN_COMPRESSION_RATIO_SIZE) {
 
  273             final Long archiveItemPackedSize = (Long) inArchive.getProperty(
 
  274                     inArchiveItemIndex, PropID.PACKED_SIZE);
 
  276             if (archiveItemPackedSize == null || archiveItemPackedSize <= 0) {
 
  277                 logger.log(Level.WARNING, 
"Cannot getting compression ratio, cannot detect if zipbomb: {0}, item: {1}", 
 
  278                         new Object[]{archiveFile.getName(), (String) inArchive.getProperty(inArchiveItemIndex, PropID.PATH)}); 
 
  282             int cRatio = (int) (archiveItemSize / archiveItemPackedSize);
 
  284             if (cRatio >= MAX_COMPRESSION_RATIO) {
 
  285                 Archive rootArchive = depthMap.get(depthMap.get(archiveFile.getId()).getRootArchiveId());
 
  286                 String details = NbBundle.getMessage(SevenZipExtractor.class,
 
  287                         "EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnDetails",
 
  288                         cRatio, FileUtil.escapeFileName(getArchiveFilePath(rootArchive.getArchiveFile())));
 
  290                 flagRootArchiveAsZipBomb(rootArchive, archiveFile, details, escapedFilePath);
 
  296         } 
catch (SevenZipException ex) {
 
  297             logger.log(Level.WARNING, 
"Error getting archive item size and cannot detect if zipbomb. ", ex); 
 
  313     private void flagRootArchiveAsZipBomb(Archive rootArchive, AbstractFile archiveFile, String details, String escapedFilePath) {
 
  314         rootArchive.flagAsZipBomb();
 
  315         logger.log(Level.INFO, details);
 
  317         String setName = 
"Possible Zip Bomb";
 
  319             Collection<BlackboardAttribute> attributes = Arrays.asList(
 
  320                     new BlackboardAttribute(
 
  321                             TSK_SET_NAME, MODULE_NAME,
 
  323                     new BlackboardAttribute(
 
  324                             TSK_DESCRIPTION, MODULE_NAME,
 
  325                             Bundle.SevenZipExtractor_zipBombArtifactCreation_text(archiveFile.getName())),
 
  326                     new BlackboardAttribute(
 
  327                             TSK_COMMENT, MODULE_NAME,
 
  330             if (!blackboard.artifactExists(archiveFile, TSK_INTERESTING_FILE_HIT, attributes)) {
 
  332                 BlackboardArtifact artifact = rootArchive.getArchiveFile().newAnalysisResult(
 
  333                         BlackboardArtifact.Type.TSK_INTERESTING_FILE_HIT, Score.SCORE_LIKELY_NOTABLE, 
 
  336                         .getAnalysisResult();
 
  344                     blackboard.postArtifact(artifact, MODULE_NAME);
 
  346                     String msg = NbBundle.getMessage(SevenZipExtractor.class,
 
  347                             "EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnMsg", archiveFile.getName(), escapedFilePath);
 
  349                     services.postMessage(IngestMessage.createWarningMessage(MODULE_NAME, msg, details));
 
  351                 } 
catch (Blackboard.BlackboardException ex) {
 
  352                     logger.log(Level.SEVERE, 
"Unable to index blackboard artifact " + artifact.getArtifactID(), ex); 
 
  353                     MessageNotifyUtil.Notify.error(
 
  354                             Bundle.SevenZipExtractor_indexError_message(), artifact.getDisplayName());
 
  357         } 
catch (TskCoreException ex) {
 
  358             logger.log(Level.SEVERE, 
"Error creating blackboard artifact for Zip Bomb Detection for file: " + escapedFilePath, ex); 
 
  370     private ArchiveFormat get7ZipOptions(AbstractFile archiveFile) {
 
  372         String detectedFormat;
 
  373         detectedFormat = archiveFile.getMIMEType();
 
  375         if (detectedFormat == null) {
 
  376             logger.log(Level.WARNING, 
"Could not detect format for file: {0}", archiveFile); 
 
  379             String extension = archiveFile.getNameExtension();
 
  380             if (
"rar".equals(extension)) 
 
  389         } 
else if (detectedFormat.contains(
"application/x-rar-compressed")) 
 
  410     private long getRootArchiveId(AbstractFile file) 
throws TskCoreException {
 
  411         long id = file.getId();
 
  412         Content parentContent = file.getParent();
 
  413         while (parentContent != null) {
 
  414             id = parentContent.getId();
 
  415             parentContent = parentContent.getParent();
 
  439     private List<AbstractFile> getAlreadyExtractedFiles(AbstractFile archiveFile, String archiveFilePath) 
throws TskCoreException, InterruptedException, FileTaskExecutor.FileTaskFailedException {
 
  443         List<AbstractFile> extractedFiles = 
new ArrayList<>();
 
  444         File outputDirectory = 
new File(moduleDirAbsolute, EmbeddedFileExtractorIngestModule.getUniqueName(archiveFile));
 
  445         if (archiveFile.hasChildren() && fileTaskExecutor.exists(outputDirectory)) {
 
  446             Case currentCase = Case.getCurrentCase();
 
  447             FileManager fileManager = currentCase.getServices().getFileManager();
 
  448             extractedFiles.addAll(fileManager.findFilesByParentPath(getRootArchiveId(archiveFile), archiveFilePath));
 
  450         return extractedFiles;
 
  460     private String getArchiveFilePath(AbstractFile archiveFile) {
 
  461         return archiveFile.getParentPath() + archiveFile.getName();
 
  473     private boolean makeExtractedFilesDirectory(String uniqueArchiveFileName) {
 
  474         boolean success = 
true;
 
  475         Path rootDirectoryPath = Paths.get(moduleDirAbsolute, uniqueArchiveFileName);
 
  476         File rootDirectory = rootDirectoryPath.toFile();
 
  478             if (!fileTaskExecutor.exists(rootDirectory)) {
 
  479                 success = fileTaskExecutor.mkdirs(rootDirectory);
 
  481         } 
catch (SecurityException | FileTaskFailedException | InterruptedException ex) {
 
  482             logger.log(Level.SEVERE, String.format(
"Error creating root extracted files directory %s", rootDirectory), ex); 
 
  500     private String getPathInArchive(IInArchive archive, 
int inArchiveItemIndex, AbstractFile archiveFile) 
throws SevenZipException {
 
  501         String pathInArchive = (String) archive.getProperty(inArchiveItemIndex, PropID.PATH);
 
  503         if (pathInArchive == null || pathInArchive.isEmpty()) {
 
  509             String archName = archiveFile.getName();
 
  510             int dotI = archName.lastIndexOf(
".");
 
  511             String useName = null;
 
  513                 String base = archName.substring(0, dotI);
 
  514                 String ext = archName.substring(dotI);
 
  515                 int colonIndex = ext.lastIndexOf(
":");
 
  516                 if (colonIndex != -1) {
 
  519                     ext = ext.substring(0, colonIndex);
 
  526                         useName = base + 
".tar"; 
 
  533             if (useName == null) {
 
  534                 pathInArchive = 
"/" + archName + 
"/" + Integer.toString(inArchiveItemIndex);
 
  536                 pathInArchive = 
"/" + useName;
 
  539         return pathInArchive;
 
  542     private byte[] getPathBytesInArchive(IInArchive archive, 
int inArchiveItemIndex, AbstractFile archiveFile) 
throws SevenZipException {
 
  543         return (byte[]) archive.getProperty(inArchiveItemIndex, PropID.PATH_BYTES);
 
  550     private String getKeyAbstractFile(AbstractFile fileInDatabase) {
 
  551         return fileInDatabase == null ? null : fileInDatabase.getParentPath() + fileInDatabase.getName();
 
  558     private String getKeyFromUnpackedNode(UnpackedTree.UnpackedNode node, String archiveFilePath) {
 
  559         return node == null ? null : archiveFilePath + 
"/" + node.getFileName();
 
  569     void unpack(AbstractFile archiveFile, ConcurrentHashMap<Long, Archive> depthMap) {
 
  570         unpack(archiveFile, depthMap, null);
 
  584     @Messages({
"SevenZipExtractor.indexError.message=Failed to index encryption detected artifact for keyword search.",
 
  585         "# {0} -  rootArchive",
 
  586         "SevenZipExtractor.zipBombArtifactCreation.text=Zip Bomb Detected {0}"})
 
  587     boolean unpack(AbstractFile archiveFile, ConcurrentHashMap<Long, Archive> depthMap, String password) {
 
  588         boolean unpackSuccessful = 
true; 
 
  589         boolean hasEncrypted = 
false;
 
  590         boolean fullEncryption = 
true;
 
  591         boolean progressStarted = 
false;
 
  592         final String archiveFilePath = getArchiveFilePath(archiveFile);
 
  593         final String escapedArchiveFilePath = FileUtil.escapeFileName(archiveFilePath);
 
  594         HashMap<String, ZipFileStatusWrapper> statusMap = 
new HashMap<>();
 
  595         List<AbstractFile> unpackedFiles = Collections.<AbstractFile>emptyList();
 
  597         currentArchiveName = archiveFile.getName();
 
  599         SevenZipContentReadStream stream = null;
 
  600         progress = ProgressHandle.createHandle(Bundle.EmbeddedFileExtractorIngestModule_ArchiveExtractor_moduleName());
 
  604             blackboard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard();
 
  605         } 
catch (NoCurrentCaseException ex) {
 
  606             logger.log(Level.INFO, 
"Exception while getting open case.", ex); 
 
  607             unpackSuccessful = 
false;
 
  608             return unpackSuccessful;
 
  610         if (checkForIngestCancellation(archiveFile)) {
 
  614             List<AbstractFile> existingFiles = getAlreadyExtractedFiles(archiveFile, archiveFilePath);
 
  615             for (AbstractFile file : existingFiles) {
 
  616                 statusMap.put(getKeyAbstractFile(file), 
new ZipFileStatusWrapper(file, ZipFileStatus.EXISTS));
 
  618         } 
catch (TskCoreException | FileTaskFailedException | InterruptedException ex) {
 
  619             logger.log(Level.SEVERE, String.format(
"Error checking if %s has already been processed, skipping", escapedArchiveFilePath), ex); 
 
  620             unpackSuccessful = 
false;
 
  621             return unpackSuccessful;
 
  623         if (checkForIngestCancellation(archiveFile)) {
 
  626         parentAr = depthMap.get(archiveFile.getId());
 
  627         if (parentAr == null) {
 
  628             parentAr = 
new Archive(0, archiveFile.getId(), archiveFile);
 
  629             depthMap.put(archiveFile.getId(), parentAr);
 
  631             Archive rootArchive = depthMap.get(parentAr.getRootArchiveId());
 
  632             if (rootArchive.isFlaggedAsZipBomb()) {
 
  634                 unpackSuccessful = 
false;
 
  635                 return unpackSuccessful;
 
  636             } 
else if (parentAr.getDepth() == MAX_DEPTH) {
 
  637                 String details = NbBundle.getMessage(SevenZipExtractor.class,
 
  638                         "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnDetails.zipBomb",
 
  639                         parentAr.getDepth(), FileUtil.escapeFileName(getArchiveFilePath(rootArchive.getArchiveFile())));
 
  640                 flagRootArchiveAsZipBomb(rootArchive, archiveFile, details, escapedArchiveFilePath);
 
  641                 unpackSuccessful = 
false;
 
  642                 return unpackSuccessful;
 
  645         if (checkForIngestCancellation(archiveFile)) {
 
  648         IInArchive inArchive = null;
 
  650             stream = 
new SevenZipContentReadStream(
new ReadContentInputStream(archiveFile));
 
  654             ArchiveFormat options = get7ZipOptions(archiveFile);
 
  655             if (checkForIngestCancellation(archiveFile)) {
 
  658             if (password == null) {
 
  659                 inArchive = SevenZip.openInArchive(options, stream);
 
  661                 inArchive = SevenZip.openInArchive(options, stream, password);
 
  663             numItems = inArchive.getNumberOfItems();
 
  664             progress.start(numItems);
 
  665             progressStarted = 
true;
 
  666             if (checkForIngestCancellation(archiveFile)) {
 
  670             final String uniqueArchiveFileName = FileUtil.escapeFileName(EmbeddedFileExtractorIngestModule.getUniqueName(archiveFile));
 
  671             if (!makeExtractedFilesDirectory(uniqueArchiveFileName)) {
 
  676             SevenZipExtractor.UnpackedTree unpackedTree = 
new SevenZipExtractor.UnpackedTree(moduleDirRelative + 
"/" + uniqueArchiveFileName, archiveFile);
 
  680                 freeDiskSpace = services.getFreeDiskSpace();
 
  681             } 
catch (NullPointerException ex) {
 
  684                 freeDiskSpace = IngestMonitor.DISK_FREE_SPACE_UNKNOWN;
 
  687             Map<Integer, InArchiveItemDetails> archiveDetailsMap = 
new HashMap<>();
 
  688             for (
int inArchiveItemIndex = 0; inArchiveItemIndex < numItems; inArchiveItemIndex++) {
 
  689                 if (checkForIngestCancellation(archiveFile)) {
 
  692                 progress.progress(String.format(
"%s: Analyzing archive metadata and creating local files (%d of %d)", currentArchiveName, inArchiveItemIndex + 1, numItems), 0);
 
  693                 if (isZipBombArchiveItemCheck(archiveFile, inArchive, inArchiveItemIndex, depthMap, escapedArchiveFilePath)) {
 
  694                     unpackSuccessful = 
false;
 
  695                     return unpackSuccessful;
 
  698                 String pathInArchive = getPathInArchive(inArchive, inArchiveItemIndex, archiveFile);
 
  699                 byte[] pathBytesInArchive = getPathBytesInArchive(inArchive, inArchiveItemIndex, archiveFile);
 
  700                 UnpackedTree.UnpackedNode unpackedNode = unpackedTree.addNode(pathInArchive, pathBytesInArchive);
 
  701                 if (checkForIngestCancellation(archiveFile)) {
 
  704                 final boolean isEncrypted = (Boolean) inArchive.getProperty(inArchiveItemIndex, PropID.ENCRYPTED);
 
  706                 if (isEncrypted && password == null) {
 
  707                     logger.log(Level.WARNING, 
"Skipping encrypted file in archive: {0}", pathInArchive); 
 
  709                     unpackSuccessful = 
false;
 
  712                     fullEncryption = 
false;
 
  719                 Long archiveItemSize = (Long) inArchive.getProperty(
 
  720                         inArchiveItemIndex, PropID.SIZE);
 
  721                 if (freeDiskSpace != IngestMonitor.DISK_FREE_SPACE_UNKNOWN && archiveItemSize != null && archiveItemSize > 0) { 
 
  722                     String archiveItemPath = (String) inArchive.getProperty(
 
  723                             inArchiveItemIndex, PropID.PATH);
 
  724                     long newDiskSpace = freeDiskSpace - archiveItemSize;
 
  725                     if (newDiskSpace < MIN_FREE_DISK_SPACE) {
 
  726                         String msg = NbBundle.getMessage(SevenZipExtractor.class,
 
  727                                 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.msg",
 
  728                                 escapedArchiveFilePath, archiveItemPath);
 
  729                         String details = NbBundle.getMessage(SevenZipExtractor.class,
 
  730                                 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.details");
 
  731                         services.postMessage(IngestMessage.createErrorMessage(MODULE_NAME, msg, details));
 
  732                         logger.log(Level.INFO, 
"Skipping archive item due to insufficient disk space: {0}, {1}", 
new String[]{escapedArchiveFilePath, archiveItemPath}); 
 
  733                         logger.log(Level.INFO, 
"Available disk space: {0}", 
new Object[]{freeDiskSpace}); 
 
  734                         unpackSuccessful = 
false;
 
  738                         freeDiskSpace = newDiskSpace;
 
  741                 if (checkForIngestCancellation(archiveFile)) {
 
  744                 final String uniqueExtractedName = FileUtil.escapeFileName(uniqueArchiveFileName + File.separator + (inArchiveItemIndex / 1000) + File.separator + inArchiveItemIndex);
 
  745                 final String localAbsPath = moduleDirAbsolute + File.separator + uniqueExtractedName;
 
  746                 final String localRelPath = moduleDirRelative + File.separator + uniqueExtractedName;
 
  750                 File localFile = 
new File(localAbsPath);
 
  751                 boolean localFileExists;
 
  753                     if ((Boolean) inArchive.getProperty(inArchiveItemIndex, PropID.IS_FOLDER)) {
 
  754                         localFileExists = findOrCreateDirectory(localFile);
 
  756                         localFileExists = findOrCreateEmptyFile(localFile);
 
  758                 } 
catch (FileTaskFailedException | InterruptedException ex) {
 
  759                     localFileExists = 
false;
 
  760                     logger.log(Level.SEVERE, String.format(
"Error fiding or creating %s", localFile.getAbsolutePath()), ex); 
 
  762                 if (checkForIngestCancellation(archiveFile)) {
 
  767                 if (!localFileExists) {
 
  768                     logger.log(Level.SEVERE, String.format(
"Skipping %s because it could not be created", localFile.getAbsolutePath())); 
 
  776                 archiveDetailsMap.put(inArchiveItemIndex, 
new InArchiveItemDetails(
 
  777                         unpackedNode, localAbsPath, localRelPath));
 
  780             int[] extractionIndices = getExtractableFilesFromDetailsMap(archiveDetailsMap);
 
  781             if (checkForIngestCancellation(archiveFile)) {
 
  784             StandardIArchiveExtractCallback archiveCallBack
 
  785                     = 
new StandardIArchiveExtractCallback(
 
  786                             inArchive, archiveFile, progress,
 
  787                             archiveDetailsMap, password, freeDiskSpace);
 
  792             inArchive.extract(extractionIndices, 
false, archiveCallBack);
 
  793             if (checkForIngestCancellation(archiveFile)) {
 
  796             unpackSuccessful &= archiveCallBack.wasSuccessful();
 
  798             archiveDetailsMap = null;
 
  803                 unpackedTree.updateOrAddFileToCaseRec(statusMap, archiveFilePath, parentAr, archiveFile, depthMap);
 
  804                 unpackedTree.commitCurrentTransaction();
 
  805             } 
catch (TskCoreException | NoCurrentCaseException ex) {
 
  806                 logger.log(Level.SEVERE, 
"Error populating complete derived file hierarchy from the unpacked dir structure", ex); 
 
  808                 unpackedTree.rollbackCurrentTransaction();
 
  811             if (checkForIngestCancellation(archiveFile)) {
 
  816             unpackedFiles = unpackedTree.getAllFileObjects();
 
  817         } 
catch (SevenZipException | IllegalArgumentException ex) {
 
  818             logger.log(Level.WARNING, 
"Error unpacking file: " + archiveFile, ex); 
 
  822             if (archiveFile.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC)) {
 
  823                 String msg = NbBundle.getMessage(SevenZipExtractor.class,
 
  824                         "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.errUnpacking.msg",
 
  826                 String details = NbBundle.getMessage(SevenZipExtractor.class,
 
  827                         "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.errUnpacking.details",
 
  828                         escapedArchiveFilePath, ex.getMessage());
 
  829                 services.postMessage(IngestMessage.createErrorMessage(MODULE_NAME, msg, details));
 
  832             if (inArchive != null) {
 
  835                 } 
catch (SevenZipException e) {
 
  836                     logger.log(Level.SEVERE, 
"Error closing archive: " + archiveFile, e); 
 
  840             if (stream != null) {
 
  843                 } 
catch (IOException ex) {
 
  844                     logger.log(Level.SEVERE, 
"Error closing stream after unpacking archive: " + archiveFile, ex); 
 
  849             if (progressStarted) {
 
  853         if (checkForIngestCancellation(archiveFile)) {
 
  858             String encryptionType = fullEncryption ? ENCRYPTION_FULL : ENCRYPTION_FILE_LEVEL;
 
  860                 BlackboardArtifact artifact = archiveFile.newAnalysisResult(
 
  861                         new BlackboardArtifact.Type(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED), 
 
  863                         null, null, encryptionType, 
 
  864                         Arrays.asList(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT, MODULE_NAME, encryptionType)))
 
  865                         .getAnalysisResult();
 
  873                     blackboard.postArtifact(artifact, MODULE_NAME);
 
  874                 } 
catch (Blackboard.BlackboardException ex) {
 
  875                     logger.log(Level.SEVERE, 
"Unable to post blackboard artifact " + artifact.getArtifactID(), ex); 
 
  876                     MessageNotifyUtil.Notify.error(
 
  877                             Bundle.SevenZipExtractor_indexError_message(), artifact.getDisplayName());
 
  880             } 
catch (TskCoreException ex) {
 
  881                 logger.log(Level.SEVERE, 
"Error creating blackboard artifact for encryption detected for file: " + escapedArchiveFilePath, ex); 
 
  884             String msg = NbBundle.getMessage(SevenZipExtractor.class,
 
  885                     "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.encrFileDetected.msg");
 
  886             String details = NbBundle.getMessage(SevenZipExtractor.class,
 
  887                     "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.encrFileDetected.details",
 
  888                     currentArchiveName, MODULE_NAME);
 
  889             services.postMessage(IngestMessage.createWarningMessage(MODULE_NAME, msg, details));
 
  893         if (!unpackedFiles.isEmpty()) {
 
  895             services.fireModuleContentEvent(
new ModuleContentEvent(archiveFile));
 
  896             if (context != null) {
 
  897                 context.addFilesToJob(unpackedFiles);
 
  901         return unpackSuccessful;
 
  911     private boolean findOrCreateDirectory(File directory) 
throws FileTaskFailedException, InterruptedException {
 
  912         if (!fileTaskExecutor.exists(directory)) {
 
  913             return fileTaskExecutor.mkdirs(directory);
 
  926     private boolean findOrCreateEmptyFile(File file) 
throws FileTaskFailedException, InterruptedException {
 
  927         if (!fileTaskExecutor.exists(file)) {
 
  928             fileTaskExecutor.mkdirs(file.getParentFile());
 
  929             return fileTaskExecutor.createNewFile(file);
 
  935     private Charset detectFilenamesCharset(List<byte[]> byteDatas) {
 
  936         Charset detectedCharset = null;
 
  937         CharsetDetector charsetDetector = 
new CharsetDetector();
 
  940         for (byte[] byteData : byteDatas) {
 
  942             byteSum += byteData.length;
 
  944             if (byteSum >= 1000) {
 
  948         byte[] allBytes = 
new byte[byteSum];
 
  950         for (
int i = 0; i < fileNum; i++) {
 
  951             byte[] byteData = byteDatas.get(i);
 
  952             System.arraycopy(byteData, 0, allBytes, start, byteData.length);
 
  953             start += byteData.length;
 
  955         charsetDetector.setText(allBytes);
 
  956         CharsetMatch cm = charsetDetector.detect();
 
  957         if (cm.getConfidence() >= 90 && Charset.isSupported(cm.getName())) {
 
  958             detectedCharset = Charset.forName(cm.getName());
 
  960         return detectedCharset;
 
  967     private int[] getExtractableFilesFromDetailsMap(
 
  968             Map<Integer, InArchiveItemDetails> archiveDetailsMap) {
 
  970         Integer[] wrappedExtractionIndices = archiveDetailsMap.keySet()
 
  971                 .toArray(
new Integer[archiveDetailsMap.size()]);
 
  973         return Arrays.stream(wrappedExtractionIndices)
 
  974                 .mapToInt(Integer::intValue)
 
  986     private final static class UnpackStream implements ISequentialOutStream {
 
  991         private static final Tika 
tika = 
new Tika();
 
  992         private String mimeType = 
"";
 
  995             this.output = 
new EncodedFileOutputStream(
new FileOutputStream(localAbsPath), TskData.EncodingType.XOR1);
 
  997             this.bytesWritten = 0;
 
 1001             this.output.close();
 
 1002             this.output = 
new EncodedFileOutputStream(
new FileOutputStream(localAbsPath), TskData.EncodingType.XOR1);
 
 1004             this.bytesWritten = 0;
 
 1013         public int write(byte[] bytes) 
throws SevenZipException {
 
 1016                 if (bytesWritten == 0) {
 
 1017                     mimeType = tika.detect(bytes);
 
 1019                 output.write(bytes);
 
 1020                 this.bytesWritten += bytes.length;
 
 1021             } 
catch (IOException ex) {
 
 1022                 throw new SevenZipException(
 
 1023                         NbBundle.getMessage(SevenZipExtractor.class,
 
 1024                                 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackStream.write.exception.msg",
 
 1027             return bytes.length;
 
 1034         public void close() throws IOException {
 
 1035             try (EncodedFileOutputStream out = output) {
 
 1052                 SevenZipExtractor.UnpackedTree.UnpackedNode 
unpackedNode,
 
 1053                 String localAbsPath, String localRelPath) {
 
 1077             implements IArchiveExtractCallback, ICryptoGetTextPassword {
 
 1094         private boolean unpackSuccessful = 
true;
 
 1097                 AbstractFile archiveFile, ProgressHandle progressHandle,
 
 1098                 Map<Integer, InArchiveItemDetails> archiveDetailsMap,
 
 1099                 String password, 
long freeDiskSpace) {
 
 1122         public ISequentialOutStream 
getStream(
int inArchiveItemIndex,
 
 1123                 ExtractAskMode mode) 
throws SevenZipException {
 
 1127             isFolder = (Boolean) inArchive
 
 1128                     .getProperty(inArchiveItemIndex, PropID.IS_FOLDER);
 
 1129             if (isFolder || mode != ExtractAskMode.EXTRACT) {
 
 1133             final String localAbsPath = archiveDetailsMap.get(
 
 1134                     inArchiveItemIndex).getLocalAbsPath();
 
 1142                 if (unpackStream != null) {
 
 1147             } 
catch (IOException ex) {
 
 1148                 logger.log(Level.WARNING, String.format(
"Error opening or setting new stream "  
 1149                         + 
"for archive file at %s", localAbsPath), ex.getMessage()); 
 
 1166             final Date createTime = (Date) inArchive.getProperty(
 
 1167                     inArchiveItemIndex, PropID.CREATION_TIME);
 
 1168             final Date accessTime = (Date) inArchive.getProperty(
 
 1169                     inArchiveItemIndex, PropID.LAST_ACCESS_TIME);
 
 1170             final Date writeTime = (Date) inArchive.getProperty(
 
 1171                     inArchiveItemIndex, PropID.LAST_MODIFICATION_TIME);
 
 1173             createTimeInSeconds = createTime == null ? 0L
 
 1174                     : createTime.getTime() / 1000;
 
 1175             modTimeInSeconds = writeTime == null ? 0L
 
 1176                     : writeTime.getTime() / 1000;
 
 1177             accessTimeInSeconds = accessTime == null ? 0L
 
 1178                     : accessTime.getTime() / 1000;
 
 1180             progressHandle.progress(archiveFile.getName() + 
": " 
 1181                     + (String) inArchive.getProperty(inArchiveItemIndex, PropID.PATH),
 
 1197             final SevenZipExtractor.UnpackedTree.UnpackedNode unpackedNode
 
 1198                     = archiveDetailsMap.get(inArchiveItemIndex).getUnpackedNode();
 
 1199             final String localRelPath = archiveDetailsMap.get(
 
 1200                     inArchiveItemIndex).getLocalRelPath();
 
 1202                 unpackedNode.addDerivedInfo(0,
 
 1203                         !(Boolean) inArchive.getProperty(inArchiveItemIndex, PropID.IS_FOLDER),
 
 1208                 unpackedNode.setMimeType(unpackStream.
getMIMEType());
 
 1211             final String localAbsPath = archiveDetailsMap.get(
 
 1212                     inArchiveItemIndex).getLocalAbsPath();
 
 1213             if (result != ExtractOperationResult.OK) {
 
 1214                 if (archiveFile.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.UNALLOC)) {
 
 1215                     logger.log(Level.WARNING, 
"Extraction of : {0} encountered error {1} (file is unallocated and may be corrupt)", 
 
 1216                             new Object[]{localAbsPath, result});
 
 1218                     logger.log(Level.WARNING, 
"Extraction of : {0} encountered error {1}", 
 
 1219                             new Object[]{localAbsPath, result});
 
 1221                 unpackSuccessful = 
false;
 
 1225             unpackedNode.addDerivedInfo(unpackStream.
getSize(),
 
 1226                     !(Boolean) inArchive.getProperty(inArchiveItemIndex, PropID.IS_FOLDER),
 
 1230                 unpackStream.
close();
 
 1231             } 
catch (IOException e) {
 
 1232                 logger.log(Level.WARNING, 
"Error closing unpack stream for file: {0}", localAbsPath); 
 
 1237         public void setTotal(
long value) 
throws SevenZipException {
 
 1290         UnpackedTree(String localPathRoot, AbstractFile archiveFile) {
 
 1292             this.rootNode.setFile(archiveFile);
 
 1293             this.rootNode.setFileName(archiveFile.getName());
 
 1294             this.rootNode.setLocalRelPath(localPathRoot);
 
 1306         UnpackedNode addNode(String filePath, byte[] filePathBytes) {
 
 1307             String[] toks = filePath.split(
"[\\/\\\\]");
 
 1308             List<String> tokens = 
new ArrayList<>();
 
 1309             for (
int i = 0; i < toks.length; ++i) {
 
 1310                 if (!toks[i].isEmpty()) {
 
 1311                     tokens.add(toks[i]);
 
 1315             List<byte[]> byteTokens;
 
 1316             if (filePathBytes == null) {
 
 1317                 return addNode(rootNode, tokens, null);
 
 1319                 byteTokens = 
new ArrayList<>(tokens.size());
 
 1321                 for (
int i = 0; i < filePathBytes.length; i++) {
 
 1322                     if (filePathBytes[i] == 
'/') {
 
 1325                             byte[] arr = 
new byte[len];
 
 1326                             System.arraycopy(filePathBytes, last, arr, 0, len);
 
 1327                             byteTokens.add(arr);
 
 1332                 int len = filePathBytes.length - last;
 
 1334                     byte[] arr = 
new byte[len];
 
 1335                     System.arraycopy(filePathBytes, last, arr, 0, len);
 
 1336                     byteTokens.add(arr);
 
 1339                 if (tokens.size() != byteTokens.size()) {
 
 1340                     String rootFileInfo = 
"(unknown)";
 
 1341                     if (rootNode.getFile() != null) {
 
 1342                         rootFileInfo = rootNode.getFile().getParentPath() + rootNode.getFile().getName() 
 
 1343                                 + 
"(ID: " + rootNode.getFile().getId() + 
")";
 
 1345                     logger.log(Level.WARNING, 
"Could not map path bytes to path string while extracting archive {0} (path string: \"{1}\", bytes: {2})", 
 
 1346                             new Object[]{rootFileInfo, this.rootNode.getFile().getId(), filePath, bytesToString(filePathBytes)});
 
 1347                     return addNode(rootNode, tokens, null);
 
 1351             return addNode(rootNode, tokens, byteTokens);
 
 1362             StringBuilder result = 
new StringBuilder();
 
 1363             for (byte b : bytes) {
 
 1364                 result.append(String.format(
"%02x", b));
 
 1366             return result.toString();
 
 1379                 List<String> tokenPath, List<byte[]> tokenPathBytes) {
 
 1381             if (tokenPath.isEmpty()) {
 
 1386             String childName = tokenPath.remove(0);
 
 1387             byte[] childNameBytes = null;
 
 1388             if (tokenPathBytes != null) {
 
 1389                 childNameBytes = tokenPathBytes.remove(0);
 
 1393             if (child == null) {
 
 1395                 child.setFileNameBytes(childNameBytes);
 
 1396                 parent.addChild(child);
 
 1400             return addNode(child, tokenPath, tokenPathBytes);
 
 1409         List<AbstractFile> getRootFileObjects() {
 
 1410             List<AbstractFile> ret = 
new ArrayList<>();
 
 1411             rootNode.getChildren().forEach((child) -> {
 
 1412                 ret.add(child.getFile());
 
 1423         List<AbstractFile> getAllFileObjects() {
 
 1424             List<AbstractFile> ret = 
new ArrayList<>();
 
 1425             rootNode.getChildren().forEach((child) -> {
 
 1432             list.add(parent.getFile());
 
 1433             parent.getChildren().forEach((child) -> {
 
 1442         void updateOrAddFileToCaseRec(HashMap<String, ZipFileStatusWrapper> statusMap, String archiveFilePath, Archive parentAr, AbstractFile archiveFile, ConcurrentHashMap<Long, Archive> depthMap) 
throws TskCoreException, 
NoCurrentCaseException {
 
 1444             for (UnpackedNode child : rootNode.getChildren()) {
 
 1445                 updateOrAddFileToCaseRec(child, fileManager, statusMap, archiveFilePath, parentAr, archiveFile, depthMap);
 
 1466         private void updateOrAddFileToCaseRec(
UnpackedNode node, 
FileManager fileManager, HashMap<String, ZipFileStatusWrapper> statusMap, String archiveFilePath, Archive parentAr, AbstractFile archiveFile, ConcurrentHashMap<Long, Archive> depthMap) 
throws TskCoreException {
 
 1468             progress.progress(String.format(
"%s: Adding/updating files in case database (%d of %d)", currentArchiveName, ++nodesProcessed, numItems));
 
 1470                 String nameInDatabase = getKeyFromUnpackedNode(node, archiveFilePath);
 
 1471                 ZipFileStatusWrapper existingFile = nameInDatabase == null ? null : statusMap.get(nameInDatabase);
 
 1472                 if (existingFile == null) {
 
 1474                             node.getCtime(), node.getCrtime(), node.getAtime(), node.getMtime(),
 
 1475                             node.isIsFile(), node.getParent().getFile(), 
"", MODULE_NAME,
 
 1479                     String key = getKeyAbstractFile(existingFile.
getFile());
 
 1482                         statusMap.put(key, existingFile);
 
 1486                         String mimeType = existingFile.
getFile().getMIMEType().equalsIgnoreCase(
"application/octet-stream") ? null : existingFile.
getFile().getMIMEType();
 
 1488                                 node.getCtime(), node.getCrtime(), node.getAtime(), node.getMtime(),
 
 1489                                 node.isIsFile(), mimeType, 
"", MODULE_NAME,
 
 1494                         df = (DerivedFile) existingFile.
getFile();
 
 1499                 logger.log(Level.SEVERE, 
"Error adding a derived file to db:" + node.getFileName(), ex); 
 
 1500                 throw new TskCoreException(
 
 1501                         NbBundle.getMessage(SevenZipExtractor.class, 
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackedTree.exception.msg",
 
 1502                                 node.getFileName()), ex);
 
 1506             if (node.getChildren().size() > 0) {
 
 1508                 ArrayList<byte[]> byteDatas = 
new ArrayList<>();
 
 1510                     byte[] childBytes = child.getFileNameBytes();
 
 1511                     if (childBytes != null) {
 
 1512                         byteDatas.add(childBytes);
 
 1514                     names += child.getFileName();
 
 1516                 Charset detectedCharset = detectFilenamesCharset(byteDatas);
 
 1519                 if (detectedCharset != null && detectedCharset.canEncode()) {
 
 1521                         byte[] childBytes = child.getFileNameBytes();
 
 1522                         if (childBytes != null) {
 
 1523                             String decodedName = 
new String(childBytes, detectedCharset);
 
 1524                             child.setFileName(decodedName);
 
 1531             if (isSevenZipExtractionSupported(node.getMimeType())) {
 
 1532                 Archive child = 
new Archive(parentAr.getDepth() + 1, parentAr.getRootArchiveId(), archiveFile);
 
 1533                 parentAr.addChild(child);
 
 1534                 depthMap.put(node.getFile().getId(), child);
 
 1539                 updateOrAddFileToCaseRec(child, fileManager, statusMap, getKeyFromUnpackedNode(node, archiveFilePath), parentAr, archiveFile, depthMap);
 
 1555             if (currentTransaction == null) {
 
 1559             if (transactionCounter > MAX_TRANSACTION_SIZE) {
 
 1564             transactionCounter++;
 
 1576                 transactionCounter = 0;
 
 1578                 throw new TskCoreException(
"Case is closed");
 
 1588             if (currentTransaction != null) {
 
 1589                 currentTransaction.commit();
 
 1590                 currentTransaction = null;
 
 1598             if (currentTransaction != null) {
 
 1600                     currentTransaction.rollback();
 
 1601                     currentTransaction = null;
 
 1602                 } 
catch (TskCoreException ex) {
 
 1616             private final List<UnpackedNode> 
children = 
new ArrayList<>();
 
 1617             private String localRelPath = 
"";
 
 1619             private long ctime, crtime, atime, mtime;
 
 1621             private String mimeType = 
"";
 
 1632                 this.localRelPath = parent.getLocalRelPath() + File.separator + 
fileName;
 
 1651             void setFileName(String fileName) {
 
 1660             void addChild(UnpackedNode child) {
 
 1661                 children.add(child);
 
 1670             List<UnpackedNode> getChildren() {
 
 1679             UnpackedNode getParent() {
 
 1683             void addDerivedInfo(
long size,
 
 1685                     long ctime, 
long crtime, 
long atime, 
long mtime, String relLocalPath) {
 
 1689                 this.crtime = crtime;
 
 1692                 this.localRelPath = relLocalPath;
 
 1695             void setFile(AbstractFile file) {
 
 1699             void setMimeType(String mimeType) {
 
 1703             String getMimeType() {
 
 1714             UnpackedNode getChild(String childFileName) {
 
 1715                 UnpackedNode ret = null;
 
 1716                 for (UnpackedNode child : children) {
 
 1717                     if (child.getFileName().equals(childFileName)) {
 
 1725             String getFileName() {
 
 1729             AbstractFile getFile() {
 
 1733             String getLocalRelPath() {
 
 1743             void setLocalRelPath(String localRelativePath) {
 
 1744                 localRelPath = localRelativePath;
 
 1751             boolean isIsFile() {
 
 1755             void setFileNameBytes(byte[] fileNameBytes) {
 
 1756                 if (fileNameBytes != null) {
 
 1757                     this.fileNameBytes = Arrays.copyOf(fileNameBytes, fileNameBytes.length);
 
 1761             byte[] getFileNameBytes() {
 
 1762                 if (fileNameBytes == null) {
 
 1765                 return Arrays.copyOf(fileNameBytes, fileNameBytes.length);
 
 1774     static class Archive {
 
 1777         private final int depth;
 
 1778         private final List<Archive> children;
 
 1779         private final long rootArchiveId;
 
 1780         private boolean flaggedAsZipBomb = 
false;
 
 1781         private final AbstractFile archiveFile;
 
 1795         Archive(
int depth, 
long rootArchiveId, AbstractFile archiveFile) {
 
 1796             this.children = 
new ArrayList<>();
 
 1798             this.rootArchiveId = rootArchiveId;
 
 1799             this.archiveFile = archiveFile;
 
 1808         void addChild(Archive child) {
 
 1809             children.add(child);
 
 1816         synchronized void flagAsZipBomb() {
 
 1817             flaggedAsZipBomb = 
true;
 
 1825         synchronized boolean isFlaggedAsZipBomb() {
 
 1826             return flaggedAsZipBomb;
 
 1834         AbstractFile getArchiveFile() {
 
 1843         long getRootArchiveId() {
 
 1844             return rootArchiveId;
 
 1852         long getObjectId() {
 
 1853             return archiveFile.getId();
 
 1885             abstractFile = file;
 
FileManager getFileManager()
 
SleuthkitCase getSleuthkitCase()
 
static Case getCurrentCaseThrows()