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.parser.txt.CharsetDetector;
 
   50 import org.apache.tika.parser.txt.CharsetMatch;
 
   51 import org.netbeans.api.progress.ProgressHandle;
 
   52 import org.openide.util.NbBundle;
 
   53 import org.openide.util.NbBundle.Messages;
 
   70 import static org.
sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT;
 
   72 import static org.
sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT;
 
   73 import static org.
sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DESCRIPTION;
 
   74 import static org.
sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME;
 
   86 class SevenZipExtractor {
 
   88     private static final Logger logger = Logger.getLogger(SevenZipExtractor.class.getName());
 
   90     private static final String MODULE_NAME = EmbeddedFileExtractorModuleFactory.getModuleName();
 
   93     private static final String ENCRYPTION_FILE_LEVEL = NbBundle.getMessage(EmbeddedFileExtractorIngestModule.class,
 
   94             "EmbeddedFileExtractorIngestModule.ArchiveExtractor.encryptionFileLevel");
 
   95     private static final String ENCRYPTION_FULL = NbBundle.getMessage(EmbeddedFileExtractorIngestModule.class,
 
   96             "EmbeddedFileExtractorIngestModule.ArchiveExtractor.encryptionFull");
 
   99     private static final int MAX_DEPTH = 4;
 
  100     private static final int MAX_COMPRESSION_RATIO = 600;
 
  101     private static final long MIN_COMPRESSION_RATIO_SIZE = 500 * 1000000L;
 
  102     private static final long MIN_FREE_DISK_SPACE = 1 * 1000 * 1000000L; 
 
  104     private IngestServices services = IngestServices.getInstance();
 
  105     private final IngestJobContext context;
 
  106     private final FileTypeDetector fileTypeDetector;
 
  107     private final FileTaskExecutor fileTaskExecutor;
 
  109     private String moduleDirRelative;
 
  110     private String moduleDirAbsolute;
 
  112     private Blackboard blackboard;
 
  114     private ProgressHandle progress;
 
  115     private int numItems;
 
  116     private String currentArchiveName;
 
  130         XRAR(
"application/x-rar-compressed"); 
 
  135             this.mimeType = mimeType;
 
  140             return this.mimeType;
 
  165     SevenZipExtractor(
IngestJobContext context, 
FileTypeDetector fileTypeDetector, String moduleDirRelative, String moduleDirAbsolute, FileTaskExecutor fileTaskExecutor) 
throws SevenZipNativeInitializationException {
 
  166         if (!SevenZip.isInitializedSuccessfully()) {
 
  167             throw new SevenZipNativeInitializationException(
"SevenZip has not been previously initialized.");
 
  169         this.context = context;
 
  170         this.fileTypeDetector = fileTypeDetector;
 
  171         this.moduleDirRelative = moduleDirRelative;
 
  172         this.moduleDirAbsolute = moduleDirAbsolute;
 
  173         this.fileTaskExecutor = fileTaskExecutor;
 
  184     boolean isSevenZipExtractionSupported(AbstractFile file) {
 
  185         String fileMimeType = fileTypeDetector.getMIMEType(file);
 
  186         for (SupportedArchiveExtractionFormats mimeType : SupportedArchiveExtractionFormats.values()) {
 
  187             if (mimeType.toString().equals(fileMimeType)) {
 
  217     private boolean isZipBombArchiveItemCheck(AbstractFile archiveFile, IInArchive inArchive, 
int inArchiveItemIndex, ConcurrentHashMap<Long, Archive> depthMap, String escapedFilePath) {
 
  227         if (archiveFile.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.UNALLOC) || archiveFile.getMIMEType().equalsIgnoreCase(SupportedArchiveExtractionFormats.XGZIP.toString())) {
 
  232             final Long archiveItemSize = (Long) inArchive.getProperty(
 
  233                     inArchiveItemIndex, PropID.SIZE);
 
  236             if (archiveItemSize == null || archiveItemSize < MIN_COMPRESSION_RATIO_SIZE) {
 
  240             final Long archiveItemPackedSize = (Long) inArchive.getProperty(
 
  241                     inArchiveItemIndex, PropID.PACKED_SIZE);
 
  243             if (archiveItemPackedSize == null || archiveItemPackedSize <= 0) {
 
  244                 logger.log(Level.WARNING, 
"Cannot getting compression ratio, cannot detect if zipbomb: {0}, item: {1}", 
 
  245                         new Object[]{archiveFile.getName(), (String) inArchive.getProperty(inArchiveItemIndex, PropID.PATH)}); 
 
  249             int cRatio = (int) (archiveItemSize / archiveItemPackedSize);
 
  251             if (cRatio >= MAX_COMPRESSION_RATIO) {
 
  252                 Archive rootArchive = depthMap.get(depthMap.get(archiveFile.getId()).getRootArchiveId());
 
  253                 String details = NbBundle.getMessage(SevenZipExtractor.class,
 
  254                         "EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnDetails",
 
  255                         cRatio, FileUtil.escapeFileName(getArchiveFilePath(rootArchive.getArchiveFile())));
 
  257                 flagRootArchiveAsZipBomb(rootArchive, archiveFile, details, escapedFilePath);
 
  263         } 
catch (SevenZipException ex) {
 
  264             logger.log(Level.WARNING, 
"Error getting archive item size and cannot detect if zipbomb. ", ex); 
 
  280     private void flagRootArchiveAsZipBomb(Archive rootArchive, AbstractFile archiveFile, String details, String escapedFilePath) {
 
  281         rootArchive.flagAsZipBomb();
 
  282         logger.log(Level.INFO, details);
 
  284             Collection<BlackboardAttribute> attributes = Arrays.asList(
 
  285                     new BlackboardAttribute(
 
  286                             TSK_SET_NAME, MODULE_NAME,
 
  287                             "Possible Zip Bomb"),
 
  288                     new BlackboardAttribute(
 
  289                             TSK_DESCRIPTION, MODULE_NAME,
 
  290                             Bundle.SevenZipExtractor_zipBombArtifactCreation_text(archiveFile.getName())),
 
  291                     new BlackboardAttribute(
 
  292                             TSK_COMMENT, MODULE_NAME,
 
  295             if (!blackboard.artifactExists(archiveFile, TSK_INTERESTING_FILE_HIT, attributes)) {
 
  296                 BlackboardArtifact artifact = rootArchive.getArchiveFile().newArtifact(TSK_INTERESTING_FILE_HIT);
 
  297                 artifact.addAttributes(attributes);
 
  304                     blackboard.postArtifact(artifact, MODULE_NAME);
 
  306                     String msg = NbBundle.getMessage(SevenZipExtractor.class,
 
  307                             "EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnMsg", archiveFile.getName(), escapedFilePath);
 
  309                     services.postMessage(IngestMessage.createWarningMessage(MODULE_NAME, msg, details));
 
  311                 } 
catch (Blackboard.BlackboardException ex) {
 
  312                     logger.log(Level.SEVERE, 
"Unable to index blackboard artifact " + artifact.getArtifactID(), ex); 
 
  313                     MessageNotifyUtil.Notify.error(
 
  314                             Bundle.SevenZipExtractor_indexError_message(), artifact.getDisplayName());
 
  317         } 
catch (TskCoreException ex) {
 
  318             logger.log(Level.SEVERE, 
"Error creating blackboard artifact for Zip Bomb Detection for file: " + escapedFilePath, ex); 
 
  330     private ArchiveFormat get7ZipOptions(AbstractFile archiveFile) {
 
  332         String detectedFormat;
 
  333         detectedFormat = archiveFile.getMIMEType();
 
  335         if (detectedFormat == null) {
 
  336             logger.log(Level.WARNING, 
"Could not detect format for file: {0}", archiveFile); 
 
  339             String extension = archiveFile.getNameExtension();
 
  340             if (
"rar".equals(extension)) 
 
  349         } 
else if (detectedFormat.contains(
"application/x-rar-compressed")) 
 
  370     private long getRootArchiveId(AbstractFile file) 
throws TskCoreException {
 
  371         long id = file.getId();
 
  372         Content parentContent = file.getParent();
 
  373         while (parentContent != null) {
 
  374             id = parentContent.getId();
 
  375             parentContent = parentContent.getParent();
 
  399     private List<AbstractFile> getAlreadyExtractedFiles(AbstractFile archiveFile, String archiveFilePath) 
throws TskCoreException, InterruptedException, FileTaskExecutor.FileTaskFailedException {
 
  403         List<AbstractFile> extractedFiles = 
new ArrayList<>();
 
  404         File outputDirectory = 
new File(moduleDirAbsolute, EmbeddedFileExtractorIngestModule.getUniqueName(archiveFile));
 
  405         if (archiveFile.hasChildren() && fileTaskExecutor.exists(outputDirectory)) {
 
  406             Case currentCase = Case.getCurrentCase();
 
  407             FileManager fileManager = currentCase.getServices().getFileManager();
 
  408             extractedFiles.addAll(fileManager.findFilesByParentPath(getRootArchiveId(archiveFile), archiveFilePath));
 
  410         return extractedFiles;
 
  420     private String getArchiveFilePath(AbstractFile archiveFile) {
 
  421         return archiveFile.getParentPath() + archiveFile.getName();
 
  433     private boolean makeExtractedFilesDirectory(String uniqueArchiveFileName) {
 
  434         boolean success = 
true;
 
  435         Path rootDirectoryPath = Paths.get(moduleDirAbsolute, uniqueArchiveFileName);
 
  436         File rootDirectory = rootDirectoryPath.toFile();
 
  438             if (!fileTaskExecutor.exists(rootDirectory)) {
 
  439                 success = fileTaskExecutor.mkdirs(rootDirectory);
 
  441         } 
catch (SecurityException | FileTaskFailedException | InterruptedException ex) {
 
  442             logger.log(Level.SEVERE, String.format(
"Error creating root extracted files directory %s", rootDirectory), ex); 
 
  460     private String getPathInArchive(IInArchive archive, 
int inArchiveItemIndex, AbstractFile archiveFile) 
throws SevenZipException {
 
  461         String pathInArchive = (String) archive.getProperty(inArchiveItemIndex, PropID.PATH);
 
  463         if (pathInArchive == null || pathInArchive.isEmpty()) {
 
  469             String archName = archiveFile.getName();
 
  470             int dotI = archName.lastIndexOf(
".");
 
  471             String useName = null;
 
  473                 String base = archName.substring(0, dotI);
 
  474                 String ext = archName.substring(dotI);
 
  475                 int colonIndex = ext.lastIndexOf(
":");
 
  476                 if (colonIndex != -1) {
 
  479                     ext = ext.substring(0, colonIndex);
 
  486                         useName = base + 
".tar"; 
 
  493             if (useName == null) {
 
  494                 pathInArchive = 
"/" + archName + 
"/" + Integer.toString(inArchiveItemIndex);
 
  496                 pathInArchive = 
"/" + useName;
 
  499         return pathInArchive;
 
  502     private byte[] getPathBytesInArchive(IInArchive archive, 
int inArchiveItemIndex, AbstractFile archiveFile) 
throws SevenZipException {
 
  503         return (byte[]) archive.getProperty(inArchiveItemIndex, PropID.PATH_BYTES);
 
  510     private String getKeyAbstractFile(AbstractFile fileInDatabase) {
 
  511         return fileInDatabase == null ? null : fileInDatabase.getParentPath() + fileInDatabase.getName();
 
  518     private String getKeyFromUnpackedNode(UnpackedTree.UnpackedNode node, String archiveFilePath) {
 
  519         return node == null ? null : archiveFilePath + 
"/" + node.getFileName();
 
  529     void unpack(AbstractFile archiveFile, ConcurrentHashMap<Long, Archive> depthMap) {
 
  530         unpack(archiveFile, depthMap, null);
 
  544     @Messages({
"SevenZipExtractor.indexError.message=Failed to index encryption detected artifact for keyword search.",
 
  545         "# {0} -  rootArchive",
 
  546         "SevenZipExtractor.zipBombArtifactCreation.text=Zip Bomb Detected {0}"})
 
  547     boolean unpack(AbstractFile archiveFile, ConcurrentHashMap<Long, Archive> depthMap, String password) {
 
  548         boolean unpackSuccessful = 
true; 
 
  549         boolean hasEncrypted = 
false;
 
  550         boolean fullEncryption = 
true;
 
  551         boolean progressStarted = 
false;
 
  552         final String archiveFilePath = getArchiveFilePath(archiveFile);
 
  553         final String escapedArchiveFilePath = FileUtil.escapeFileName(archiveFilePath);
 
  554         HashMap<String, ZipFileStatusWrapper> statusMap = 
new HashMap<>();
 
  555         List<AbstractFile> unpackedFiles = Collections.<AbstractFile>emptyList();
 
  557         currentArchiveName = archiveFile.getName();
 
  559         SevenZipContentReadStream stream = null;
 
  560         progress = ProgressHandle.createHandle(Bundle.EmbeddedFileExtractorIngestModule_ArchiveExtractor_moduleName());
 
  564             blackboard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard();
 
  565         } 
catch (NoCurrentCaseException ex) {
 
  566             logger.log(Level.INFO, 
"Exception while getting open case.", ex); 
 
  567             unpackSuccessful = 
false;
 
  568             return unpackSuccessful;
 
  572             List<AbstractFile> existingFiles = getAlreadyExtractedFiles(archiveFile, archiveFilePath);
 
  573             for (AbstractFile file : existingFiles) {
 
  574                 statusMap.put(getKeyAbstractFile(file), 
new ZipFileStatusWrapper(file, ZipFileStatus.EXISTS));
 
  576         } 
catch (TskCoreException | FileTaskFailedException | InterruptedException ex) {
 
  577             logger.log(Level.SEVERE, String.format(
"Error checking if %s has already been processed, skipping", escapedArchiveFilePath), ex); 
 
  578             unpackSuccessful = 
false;
 
  579             return unpackSuccessful;
 
  582         parentAr = depthMap.get(archiveFile.getId());
 
  583         if (parentAr == null) {
 
  584             parentAr = 
new Archive(0, archiveFile.getId(), archiveFile);
 
  585             depthMap.put(archiveFile.getId(), parentAr);
 
  587             Archive rootArchive = depthMap.get(parentAr.getRootArchiveId());
 
  588             if (rootArchive.isFlaggedAsZipBomb()) {
 
  590                 unpackSuccessful = 
false;
 
  591                 return unpackSuccessful;
 
  592             } 
else if (parentAr.getDepth() == MAX_DEPTH) {
 
  593                 String details = NbBundle.getMessage(SevenZipExtractor.class,
 
  594                         "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnDetails.zipBomb",
 
  595                         parentAr.getDepth(), FileUtil.escapeFileName(getArchiveFilePath(rootArchive.getArchiveFile())));
 
  596                 flagRootArchiveAsZipBomb(rootArchive, archiveFile, details, escapedArchiveFilePath);
 
  597                 unpackSuccessful = 
false;
 
  598                 return unpackSuccessful;
 
  601         IInArchive inArchive = null;
 
  603             stream = 
new SevenZipContentReadStream(
new ReadContentInputStream(archiveFile));
 
  607             ArchiveFormat options = get7ZipOptions(archiveFile);
 
  608             if (password == null) {
 
  609                 inArchive = SevenZip.openInArchive(options, stream);
 
  611                 inArchive = SevenZip.openInArchive(options, stream, password);
 
  613             numItems = inArchive.getNumberOfItems();
 
  614             progress.start(numItems);
 
  615             progressStarted = 
true;
 
  618             final String uniqueArchiveFileName = FileUtil.escapeFileName(EmbeddedFileExtractorIngestModule.getUniqueName(archiveFile));
 
  619             if (!makeExtractedFilesDirectory(uniqueArchiveFileName)) {
 
  624             SevenZipExtractor.UnpackedTree unpackedTree = 
new SevenZipExtractor.UnpackedTree(moduleDirRelative + 
"/" + uniqueArchiveFileName, archiveFile);
 
  628                 freeDiskSpace = services.getFreeDiskSpace();
 
  629             } 
catch (NullPointerException ex) {
 
  632                 freeDiskSpace = IngestMonitor.DISK_FREE_SPACE_UNKNOWN;
 
  635             Map<Integer, InArchiveItemDetails> archiveDetailsMap = 
new HashMap<>();
 
  636             for (
int inArchiveItemIndex = 0; inArchiveItemIndex < numItems; inArchiveItemIndex++) {
 
  637                 progress.progress(String.format(
"%s: Analyzing archive metadata and creating local files (%d of %d)", currentArchiveName, inArchiveItemIndex + 1, numItems), 0);
 
  638                 if (isZipBombArchiveItemCheck(archiveFile, inArchive, inArchiveItemIndex, depthMap, escapedArchiveFilePath)) {
 
  639                     unpackSuccessful = 
false;
 
  640                     return unpackSuccessful;
 
  643                 String pathInArchive = getPathInArchive(inArchive, inArchiveItemIndex, archiveFile);
 
  644                 byte[] pathBytesInArchive = getPathBytesInArchive(inArchive, inArchiveItemIndex, archiveFile);
 
  645                 UnpackedTree.UnpackedNode unpackedNode = unpackedTree.addNode(pathInArchive, pathBytesInArchive);
 
  647                 final boolean isEncrypted = (Boolean) inArchive.getProperty(inArchiveItemIndex, PropID.ENCRYPTED);
 
  649                 if (isEncrypted && password == null) {
 
  650                     logger.log(Level.WARNING, 
"Skipping encrypted file in archive: {0}", pathInArchive); 
 
  652                     unpackSuccessful = 
false;
 
  655                     fullEncryption = 
false;
 
  662                 Long archiveItemSize = (Long) inArchive.getProperty(
 
  663                         inArchiveItemIndex, PropID.SIZE);
 
  664                 if (freeDiskSpace != IngestMonitor.DISK_FREE_SPACE_UNKNOWN && archiveItemSize != null && archiveItemSize > 0) { 
 
  665                     String archiveItemPath = (String) inArchive.getProperty(
 
  666                             inArchiveItemIndex, PropID.PATH);
 
  667                     long newDiskSpace = freeDiskSpace - archiveItemSize;
 
  668                     if (newDiskSpace < MIN_FREE_DISK_SPACE) {
 
  669                         String msg = NbBundle.getMessage(SevenZipExtractor.class,
 
  670                                 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.msg",
 
  671                                 escapedArchiveFilePath, archiveItemPath);
 
  672                         String details = NbBundle.getMessage(SevenZipExtractor.class,
 
  673                                 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.details");
 
  674                         services.postMessage(IngestMessage.createErrorMessage(MODULE_NAME, msg, details));
 
  675                         logger.log(Level.INFO, 
"Skipping archive item due to insufficient disk space: {0}, {1}", 
new String[]{escapedArchiveFilePath, archiveItemPath}); 
 
  676                         logger.log(Level.INFO, 
"Available disk space: {0}", 
new Object[]{freeDiskSpace}); 
 
  677                         unpackSuccessful = 
false;
 
  681                         freeDiskSpace = newDiskSpace;
 
  684                 final String uniqueExtractedName = FileUtil.escapeFileName(uniqueArchiveFileName + File.separator + (inArchiveItemIndex / 1000) + File.separator + inArchiveItemIndex + 
"_" + 
new File(pathInArchive).getName());
 
  685                 final String localAbsPath = moduleDirAbsolute + File.separator + uniqueExtractedName;
 
  686                 final String localRelPath = moduleDirRelative + File.separator + uniqueExtractedName;
 
  690                 File localFile = 
new File(localAbsPath);
 
  691                 boolean localFileExists;
 
  693                     if ((Boolean) inArchive.getProperty(inArchiveItemIndex, PropID.IS_FOLDER)) {
 
  694                         localFileExists = findOrCreateDirectory(localFile);
 
  696                         localFileExists = findOrCreateEmptyFile(localFile);
 
  698                 } 
catch (FileTaskFailedException | InterruptedException ex) {
 
  699                     localFileExists = 
false;
 
  700                     logger.log(Level.SEVERE, String.format(
"Error fiding or creating %s", localFile.getAbsolutePath()), ex); 
 
  705                 if (!localFileExists) {
 
  706                     logger.log(Level.SEVERE, String.format(
"Skipping %s because it could not be created", localFile.getAbsolutePath())); 
 
  714                 archiveDetailsMap.put(inArchiveItemIndex, 
new InArchiveItemDetails(
 
  715                         unpackedNode, localAbsPath, localRelPath));
 
  718             int[] extractionIndices = getExtractableFilesFromDetailsMap(archiveDetailsMap);
 
  720             StandardIArchiveExtractCallback archiveCallBack
 
  721                     = 
new StandardIArchiveExtractCallback(
 
  722                             inArchive, archiveFile, progress,
 
  723                             archiveDetailsMap, password, freeDiskSpace);
 
  728             inArchive.extract(extractionIndices, 
false, archiveCallBack);
 
  730             unpackSuccessful &= archiveCallBack.wasSuccessful();
 
  732             archiveDetailsMap = null;
 
  737                 unpackedTree.updateOrAddFileToCaseRec(statusMap, archiveFilePath);
 
  738                 unpackedFiles = unpackedTree.getAllFileObjects();
 
  740                 for (
int i = 0; i < unpackedFiles.size(); i++) {
 
  741                     progress.progress(String.format(
"%s: Searching for nested archives (%d of %d)", currentArchiveName, i + 1, unpackedFiles.size()));
 
  742                     AbstractFile unpackedFile = unpackedFiles.get(i);
 
  743                     if (unpackedFile == null) {
 
  746                     if (isSevenZipExtractionSupported(unpackedFile)) {
 
  747                         Archive child = 
new Archive(parentAr.getDepth() + 1, parentAr.getRootArchiveId(), archiveFile);
 
  748                         parentAr.addChild(child);
 
  749                         depthMap.put(unpackedFile.getId(), child);
 
  751                     unpackedFile.close();
 
  754             } 
catch (TskCoreException | NoCurrentCaseException e) {
 
  755                 logger.log(Level.SEVERE, 
"Error populating complete derived file hierarchy from the unpacked dir structure", e); 
 
  759         } 
catch (SevenZipException | IllegalArgumentException ex) {
 
  760             logger.log(Level.WARNING, 
"Error unpacking file: " + archiveFile, ex); 
 
  764             if (archiveFile.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC)) {
 
  765                 String msg = NbBundle.getMessage(SevenZipExtractor.class,
 
  766                         "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.errUnpacking.msg",
 
  768                 String details = NbBundle.getMessage(SevenZipExtractor.class,
 
  769                         "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.errUnpacking.details",
 
  770                         escapedArchiveFilePath, ex.getMessage());
 
  771                 services.postMessage(IngestMessage.createErrorMessage(MODULE_NAME, msg, details));
 
  774             if (inArchive != null) {
 
  777                 } 
catch (SevenZipException e) {
 
  778                     logger.log(Level.SEVERE, 
"Error closing archive: " + archiveFile, e); 
 
  782             if (stream != null) {
 
  785                 } 
catch (IOException ex) {
 
  786                     logger.log(Level.SEVERE, 
"Error closing stream after unpacking archive: " + archiveFile, ex); 
 
  791             if (progressStarted) {
 
  798             String encryptionType = fullEncryption ? ENCRYPTION_FULL : ENCRYPTION_FILE_LEVEL;
 
  800                 BlackboardArtifact artifact = archiveFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED);
 
  801                 artifact.addAttribute(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT, MODULE_NAME, encryptionType));
 
  809                     blackboard.postArtifact(artifact, MODULE_NAME);
 
  810                 } 
catch (Blackboard.BlackboardException ex) {
 
  811                     logger.log(Level.SEVERE, 
"Unable to post blackboard artifact " + artifact.getArtifactID(), ex); 
 
  812                     MessageNotifyUtil.Notify.error(
 
  813                             Bundle.SevenZipExtractor_indexError_message(), artifact.getDisplayName());
 
  816             } 
catch (TskCoreException ex) {
 
  817                 logger.log(Level.SEVERE, 
"Error creating blackboard artifact for encryption detected for file: " + escapedArchiveFilePath, ex); 
 
  820             String msg = NbBundle.getMessage(SevenZipExtractor.class,
 
  821                     "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.encrFileDetected.msg");
 
  822             String details = NbBundle.getMessage(SevenZipExtractor.class,
 
  823                     "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.encrFileDetected.details",
 
  824                     currentArchiveName, MODULE_NAME);
 
  825             services.postMessage(IngestMessage.createWarningMessage(MODULE_NAME, msg, details));
 
  829         if (!unpackedFiles.isEmpty()) {
 
  831             services.fireModuleContentEvent(
new ModuleContentEvent(archiveFile));
 
  832             if (context != null) {
 
  833                 context.addFilesToJob(unpackedFiles);
 
  837         return unpackSuccessful;
 
  847     private boolean findOrCreateDirectory(File directory) 
throws FileTaskFailedException, InterruptedException {
 
  848         if (!fileTaskExecutor.exists(directory)) {
 
  849             return fileTaskExecutor.mkdirs(directory);
 
  862     private boolean findOrCreateEmptyFile(File file) 
throws FileTaskFailedException, InterruptedException {
 
  863         if (!fileTaskExecutor.exists(file)) {
 
  864             fileTaskExecutor.mkdirs(file.getParentFile());
 
  865             return fileTaskExecutor.createNewFile(file);
 
  871     private Charset detectFilenamesCharset(List<byte[]> byteDatas) {
 
  872         Charset detectedCharset = null;
 
  873         CharsetDetector charsetDetector = 
new CharsetDetector();
 
  876         for (byte[] byteData : byteDatas) {
 
  878             byteSum += byteData.length;
 
  880             if (byteSum >= 1000) {
 
  884         byte[] allBytes = 
new byte[byteSum];
 
  886         for (
int i = 0; i < fileNum; i++) {
 
  887             byte[] byteData = byteDatas.get(i);
 
  888             System.arraycopy(byteData, 0, allBytes, start, byteData.length);
 
  889             start += byteData.length;
 
  891         charsetDetector.setText(allBytes);
 
  892         CharsetMatch cm = charsetDetector.detect();
 
  893         if (cm.getConfidence() >= 90 && Charset.isSupported(cm.getName())) {
 
  894             detectedCharset = Charset.forName(cm.getName());
 
  896         return detectedCharset;
 
  903     private int[] getExtractableFilesFromDetailsMap(
 
  904             Map<Integer, InArchiveItemDetails> archiveDetailsMap) {
 
  906         Integer[] wrappedExtractionIndices = archiveDetailsMap.keySet()
 
  907                 .toArray(
new Integer[archiveDetailsMap.size()]);
 
  909         return Arrays.stream(wrappedExtractionIndices)
 
  910                 .mapToInt(Integer::intValue)
 
  922     private final static class UnpackStream implements ISequentialOutStream {
 
  929             this.output = 
new EncodedFileOutputStream(
new FileOutputStream(localAbsPath), TskData.EncodingType.XOR1);
 
  931             this.bytesWritten = 0;
 
  936             this.output = 
new EncodedFileOutputStream(
new FileOutputStream(localAbsPath), TskData.EncodingType.XOR1);
 
  938             this.bytesWritten = 0;
 
  946         public int write(byte[] bytes) 
throws SevenZipException {
 
  949                 this.bytesWritten += bytes.length;
 
  950             } 
catch (IOException ex) {
 
  951                 throw new SevenZipException(
 
  952                         NbBundle.getMessage(SevenZipExtractor.class,
 
  953                                 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackStream.write.exception.msg",
 
  959         public void close() throws IOException {
 
  960             try (EncodedFileOutputStream out = output) {
 
  972         private final SevenZipExtractor.UnpackedTree.UnpackedNode 
unpackedNode;
 
  977                 SevenZipExtractor.UnpackedTree.UnpackedNode 
unpackedNode,
 
  978                 String localAbsPath, String localRelPath) {
 
 1002             implements IArchiveExtractCallback, ICryptoGetTextPassword {
 
 1019         private boolean unpackSuccessful = 
true;
 
 1022                 AbstractFile archiveFile, ProgressHandle progressHandle,
 
 1023                 Map<Integer, InArchiveItemDetails> archiveDetailsMap,
 
 1024                 String password, 
long freeDiskSpace) {
 
 1047         public ISequentialOutStream 
getStream(
int inArchiveItemIndex,
 
 1048                 ExtractAskMode mode) 
throws SevenZipException {
 
 1052             isFolder = (Boolean) inArchive
 
 1053                     .getProperty(inArchiveItemIndex, PropID.IS_FOLDER);
 
 1054             if (isFolder || mode != ExtractAskMode.EXTRACT) {
 
 1058             final String localAbsPath = archiveDetailsMap.get(
 
 1059                     inArchiveItemIndex).getLocalAbsPath();
 
 1067                 if (unpackStream != null) {
 
 1072             } 
catch (IOException ex) {
 
 1073                 logger.log(Level.WARNING, String.format(
"Error opening or setting new stream "  
 1074                         + 
"for archive file at %s", localAbsPath), ex.getMessage()); 
 
 1091             final Date createTime = (Date) inArchive.getProperty(
 
 1092                     inArchiveItemIndex, PropID.CREATION_TIME);
 
 1093             final Date accessTime = (Date) inArchive.getProperty(
 
 1094                     inArchiveItemIndex, PropID.LAST_ACCESS_TIME);
 
 1095             final Date writeTime = (Date) inArchive.getProperty(
 
 1096                     inArchiveItemIndex, PropID.LAST_MODIFICATION_TIME);
 
 1098             createTimeInSeconds = createTime == null ? 0L
 
 1099                     : createTime.getTime() / 1000;
 
 1100             modTimeInSeconds = writeTime == null ? 0L
 
 1101                     : writeTime.getTime() / 1000;
 
 1102             accessTimeInSeconds = accessTime == null ? 0L
 
 1103                     : accessTime.getTime() / 1000;
 
 1105             progressHandle.progress(archiveFile.getName() + 
": " 
 1106                     + (String) inArchive.getProperty(inArchiveItemIndex, PropID.PATH),
 
 1122             final SevenZipExtractor.UnpackedTree.UnpackedNode unpackedNode
 
 1123                     = archiveDetailsMap.get(inArchiveItemIndex).getUnpackedNode();
 
 1124             final String localRelPath = archiveDetailsMap.get(
 
 1125                     inArchiveItemIndex).getLocalRelPath();
 
 1127                 unpackedNode.addDerivedInfo(0,
 
 1128                         !(Boolean) inArchive.getProperty(inArchiveItemIndex, PropID.IS_FOLDER),
 
 1134             final String localAbsPath = archiveDetailsMap.get(
 
 1135                     inArchiveItemIndex).getLocalAbsPath();
 
 1136             if (result != ExtractOperationResult.OK) {
 
 1137                 if (archiveFile.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.UNALLOC)) {
 
 1138                     logger.log(Level.WARNING, 
"Extraction of : {0} encountered error {1} (file is unallocated and may be corrupt)", 
 
 1139                             new Object[]{localAbsPath, result});
 
 1141                     logger.log(Level.WARNING, 
"Extraction of : {0} encountered error {1}", 
 
 1142                             new Object[]{localAbsPath, result});
 
 1144                 unpackSuccessful = 
false;
 
 1148             unpackedNode.addDerivedInfo(unpackStream.
getSize(),
 
 1149                     !(Boolean) inArchive.getProperty(inArchiveItemIndex, PropID.IS_FOLDER),
 
 1153                 unpackStream.
close();
 
 1154             } 
catch (IOException e) {
 
 1155                 logger.log(Level.WARNING, 
"Error closing unpack stream for file: {0}", localAbsPath); 
 
 1160         public void setTotal(
long value) 
throws SevenZipException {
 
 1204         UnpackedTree(String localPathRoot, AbstractFile archiveFile) {
 
 1206             this.rootNode.setFile(archiveFile);
 
 1207             this.rootNode.setFileName(archiveFile.getName());
 
 1208             this.rootNode.setLocalRelPath(localPathRoot);
 
 1220         UnpackedNode addNode(String filePath, byte[] filePathBytes) {
 
 1221             String[] toks = filePath.split(
"[\\/\\\\]");
 
 1222             List<String> tokens = 
new ArrayList<>();
 
 1223             for (
int i = 0; i < toks.length; ++i) {
 
 1224                 if (!toks[i].isEmpty()) {
 
 1225                     tokens.add(toks[i]);
 
 1229             List<byte[]> byteTokens = null;
 
 1230             if (filePathBytes == null) {
 
 1231                 return addNode(rootNode, tokens, null);
 
 1233                 byteTokens = 
new ArrayList<>(tokens.size());
 
 1235                 for (
int i = 0; i < filePathBytes.length; i++) {
 
 1236                     if (filePathBytes[i] == 
'/') {
 
 1238                         byte[] arr = 
new byte[len];
 
 1239                         System.arraycopy(filePathBytes, last, arr, 0, len);
 
 1240                         byteTokens.add(arr);
 
 1244                 int len = filePathBytes.length - last;
 
 1246                     byte[] arr = 
new byte[len];
 
 1247                     System.arraycopy(filePathBytes, last, arr, 0, len);
 
 1248                     byteTokens.add(arr);
 
 1251                 if (tokens.size() != byteTokens.size()) {
 
 1252                     logger.log(Level.WARNING, 
"Could not map path bytes to path string");
 
 1253                     return addNode(rootNode, tokens, null);
 
 1257             return addNode(rootNode, tokens, byteTokens);
 
 1270                 List<String> tokenPath, List<byte[]> tokenPathBytes) {
 
 1272             if (tokenPath.isEmpty()) {
 
 1277             String childName = tokenPath.remove(0);
 
 1278             byte[] childNameBytes = null;
 
 1279             if (tokenPathBytes != null) {
 
 1280                 childNameBytes = tokenPathBytes.remove(0);
 
 1284             if (child == null) {
 
 1286                 child.setFileNameBytes(childNameBytes);
 
 1287                 parent.addChild(child);
 
 1291             return addNode(child, tokenPath, tokenPathBytes);
 
 1300         List<AbstractFile> getRootFileObjects() {
 
 1301             List<AbstractFile> ret = 
new ArrayList<>();
 
 1302             rootNode.getChildren().forEach((child) -> {
 
 1303                 ret.add(child.getFile());
 
 1314         List<AbstractFile> getAllFileObjects() {
 
 1315             List<AbstractFile> ret = 
new ArrayList<>();
 
 1316             rootNode.getChildren().forEach((child) -> {
 
 1323             list.add(parent.getFile());
 
 1324             parent.getChildren().forEach((child) -> {
 
 1333         void updateOrAddFileToCaseRec(HashMap<String, ZipFileStatusWrapper> statusMap, String archiveFilePath) 
throws TskCoreException, 
NoCurrentCaseException {
 
 1335             for (UnpackedNode child : rootNode.getChildren()) {
 
 1336                 updateOrAddFileToCaseRec(child, fileManager, statusMap, archiveFilePath);
 
 1356             progress.progress(String.format(
"%s: Adding/updating files in case database (%d of %d)", currentArchiveName, ++nodesProcessed, numItems));
 
 1358                 String nameInDatabase = getKeyFromUnpackedNode(node, archiveFilePath);
 
 1359                 ZipFileStatusWrapper existingFile = nameInDatabase == null ? null : statusMap.get(nameInDatabase);
 
 1360                 if (existingFile == null) {
 
 1361                     df = fileManager.
addDerivedFile(node.getFileName(), node.getLocalRelPath(), node.getSize(),
 
 1362                             node.getCtime(), node.getCrtime(), node.getAtime(), node.getMtime(),
 
 1363                             node.isIsFile(), node.getParent().getFile(), 
"", MODULE_NAME,
 
 1364                             "", 
"", TskData.EncodingType.XOR1);
 
 1367                     String key = getKeyAbstractFile(existingFile.
getFile());
 
 1370                         statusMap.put(key, existingFile);
 
 1374                         String mimeType = existingFile.
getFile().getMIMEType().equalsIgnoreCase(
"application/octet-stream") ? null : existingFile.
getFile().getMIMEType();
 
 1376                                 node.getCtime(), node.getCrtime(), node.getAtime(), node.getMtime(),
 
 1377                                 node.isIsFile(), mimeType, 
"", MODULE_NAME,
 
 1378                                 "", 
"", TskData.EncodingType.XOR1);
 
 1382                         df = (DerivedFile) existingFile.
getFile();
 
 1386             } 
catch (TskCoreException ex) {
 
 1387                 logger.log(Level.SEVERE, 
"Error adding a derived file to db:" + node.getFileName(), ex); 
 
 1388                 throw new TskCoreException(
 
 1389                         NbBundle.getMessage(SevenZipExtractor.class, 
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackedTree.exception.msg",
 
 1390                                 node.getFileName()), ex);
 
 1394             if (node.getChildren().size() > 0) {
 
 1396                 ArrayList<byte[]> byteDatas = 
new ArrayList<>();
 
 1398                     byte[] childBytes = child.getFileNameBytes();
 
 1399                     if (childBytes != null) {
 
 1400                         byteDatas.add(childBytes);
 
 1402                     names += child.getFileName();
 
 1404                 Charset detectedCharset = detectFilenamesCharset(byteDatas);
 
 1407                 if (detectedCharset != null && detectedCharset.canEncode()) {
 
 1409                         byte[] childBytes = child.getFileNameBytes();
 
 1410                         if (childBytes != null) {
 
 1411                             String decodedName = 
new String(childBytes, detectedCharset);
 
 1412                             child.setFileName(decodedName);
 
 1420                 updateOrAddFileToCaseRec(child, fileManager, statusMap, getKeyFromUnpackedNode(node, archiveFilePath));
 
 1432             private final List<UnpackedNode> 
children = 
new ArrayList<>();
 
 1433             private String localRelPath = 
"";
 
 1435             private long ctime, crtime, atime, mtime;
 
 1447                 this.localRelPath = parent.getLocalRelPath() + File.separator + 
fileName;
 
 1466             void setFileName(String fileName) {
 
 1475             void addChild(UnpackedNode child) {
 
 1476                 children.add(child);
 
 1485             List<UnpackedNode> getChildren() {
 
 1494             UnpackedNode getParent() {
 
 1498             void addDerivedInfo(
long size,
 
 1500                     long ctime, 
long crtime, 
long atime, 
long mtime, String relLocalPath) {
 
 1504                 this.crtime = crtime;
 
 1507                 this.localRelPath = relLocalPath;
 
 1510             void setFile(AbstractFile file) {
 
 1521             UnpackedNode getChild(String childFileName) {
 
 1522                 UnpackedNode ret = null;
 
 1523                 for (UnpackedNode child : children) {
 
 1524                     if (child.getFileName().equals(childFileName)) {
 
 1532             String getFileName() {
 
 1536             AbstractFile getFile() {
 
 1540             String getLocalRelPath() {
 
 1550             void setLocalRelPath(String localRelativePath) {
 
 1551                 localRelPath = localRelativePath;
 
 1558             boolean isIsFile() {
 
 1562             void setFileNameBytes(byte[] fileNameBytes) {
 
 1563                 if (fileNameBytes != null) {
 
 1564                     this.fileNameBytes = Arrays.copyOf(fileNameBytes, fileNameBytes.length);
 
 1568             byte[] getFileNameBytes() {
 
 1569                 if (fileNameBytes == null) {
 
 1572                 return Arrays.copyOf(fileNameBytes, fileNameBytes.length);
 
 1581     static class Archive {
 
 1584         private final int depth;
 
 1585         private final List<Archive> children;
 
 1586         private final long rootArchiveId;
 
 1587         private boolean flaggedAsZipBomb = 
false;
 
 1588         private final AbstractFile archiveFile;
 
 1602         Archive(
int depth, 
long rootArchiveId, AbstractFile archiveFile) {
 
 1603             this.children = 
new ArrayList<>();
 
 1605             this.rootArchiveId = rootArchiveId;
 
 1606             this.archiveFile = archiveFile;
 
 1615         void addChild(Archive child) {
 
 1616             children.add(child);
 
 1623         synchronized void flagAsZipBomb() {
 
 1624             flaggedAsZipBomb = 
true;
 
 1632         synchronized boolean isFlaggedAsZipBomb() {
 
 1633             return flaggedAsZipBomb;
 
 1641         AbstractFile getArchiveFile() {
 
 1650         long getRootArchiveId() {
 
 1651             return rootArchiveId;
 
 1659         long getObjectId() {
 
 1660             return archiveFile.getId();
 
 1692             abstractFile = file;
 
FileManager getFileManager()
 
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)
 
static Case getCurrentCaseThrows()
 
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)