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)