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.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.Collection;
28 import java.util.Collections;
29 import java.util.Date;
30 import java.util.HashMap;
31 import java.util.List;
33 import java.util.concurrent.ConcurrentHashMap;
34 import java.util.logging.Level;
35 import net.sf.sevenzipjbinding.ArchiveFormat;
36 import static net.sf.sevenzipjbinding.ArchiveFormat.RAR;
37 import net.sf.sevenzipjbinding.ExtractAskMode;
38 import net.sf.sevenzipjbinding.ExtractOperationResult;
39 import net.sf.sevenzipjbinding.IArchiveExtractCallback;
40 import net.sf.sevenzipjbinding.ICryptoGetTextPassword;
41 import net.sf.sevenzipjbinding.IInArchive;
42 import net.sf.sevenzipjbinding.ISequentialOutStream;
43 import net.sf.sevenzipjbinding.PropID;
44 import net.sf.sevenzipjbinding.SevenZip;
45 import net.sf.sevenzipjbinding.SevenZipException;
46 import net.sf.sevenzipjbinding.SevenZipNativeInitializationException;
47 import org.apache.tika.parser.txt.CharsetDetector;
48 import org.apache.tika.parser.txt.CharsetMatch;
49 import org.netbeans.api.progress.ProgressHandle;
50 import org.openide.util.NbBundle;
51 import org.openide.util.NbBundle.Messages;
67 import static org.
sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT;
69 import static org.
sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT;
70 import static org.
sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DESCRIPTION;
71 import static org.
sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME;
79 class SevenZipExtractor {
81 private static final Logger logger = Logger.getLogger(SevenZipExtractor.class.getName());
83 private static final String MODULE_NAME = EmbeddedFileExtractorModuleFactory.getModuleName();
86 private static final String ENCRYPTION_FILE_LEVEL = NbBundle.getMessage(EmbeddedFileExtractorIngestModule.class,
87 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.encryptionFileLevel");
88 private static final String ENCRYPTION_FULL = NbBundle.getMessage(EmbeddedFileExtractorIngestModule.class,
89 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.encryptionFull");
92 private static final int MAX_DEPTH = 4;
93 private static final int MAX_COMPRESSION_RATIO = 600;
94 private static final long MIN_COMPRESSION_RATIO_SIZE = 500 * 1000000L;
95 private static final long MIN_FREE_DISK_SPACE = 1 * 1000 * 1000000L;
97 private IngestServices services = IngestServices.getInstance();
98 private final IngestJobContext context;
99 private final FileTypeDetector fileTypeDetector;
101 private String moduleDirRelative;
102 private String moduleDirAbsolute;
104 private Blackboard blackboard;
106 private ProgressHandle progress;
107 private int numItems;
108 private String currentArchiveName;
110 private String getLocalRootAbsPath(String uniqueArchiveFileName) {
111 return moduleDirAbsolute + File.separator + uniqueArchiveFileName;
126 XRAR(
"application/x-rar-compressed");
131 this.mimeType = mimeType;
136 return this.mimeType;
141 SevenZipExtractor(
IngestJobContext context,
FileTypeDetector fileTypeDetector, String moduleDirRelative, String moduleDirAbsolute)
throws SevenZipNativeInitializationException {
142 if (!SevenZip.isInitializedSuccessfully() && (SevenZip.getLastInitializationException() == null)) {
143 SevenZip.initSevenZipFromPlatformJAR();
145 this.context = context;
146 this.fileTypeDetector = fileTypeDetector;
147 this.moduleDirRelative = moduleDirRelative;
148 this.moduleDirAbsolute = moduleDirAbsolute;
159 boolean isSevenZipExtractionSupported(AbstractFile file) {
160 String fileMimeType = fileTypeDetector.getMIMEType(file);
161 for (SupportedArchiveExtractionFormats mimeType : SupportedArchiveExtractionFormats.values()) {
162 if (mimeType.toString().equals(fileMimeType)) {
192 private boolean isZipBombArchiveItemCheck(AbstractFile archiveFile, IInArchive inArchive,
int inArchiveItemIndex, ConcurrentHashMap<Long, Archive> depthMap, String escapedFilePath) {
202 if (archiveFile.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.UNALLOC) || archiveFile.getMIMEType().equalsIgnoreCase(SupportedArchiveExtractionFormats.XGZIP.toString())) {
207 final Long archiveItemSize = (Long) inArchive.getProperty(
208 inArchiveItemIndex, PropID.SIZE);
211 if (archiveItemSize == null || archiveItemSize < MIN_COMPRESSION_RATIO_SIZE) {
215 final Long archiveItemPackedSize = (Long) inArchive.getProperty(
216 inArchiveItemIndex, PropID.PACKED_SIZE);
218 if (archiveItemPackedSize == null || archiveItemPackedSize <= 0) {
219 logger.log(Level.WARNING,
"Cannot getting compression ratio, cannot detect if zipbomb: {0}, item: {1}",
220 new Object[]{archiveFile.getName(), (String) inArchive.getProperty(inArchiveItemIndex, PropID.PATH)});
224 int cRatio = (int) (archiveItemSize / archiveItemPackedSize);
226 if (cRatio >= MAX_COMPRESSION_RATIO) {
227 Archive rootArchive = depthMap.get(depthMap.get(archiveFile.getId()).getRootArchiveId());
228 String details = NbBundle.getMessage(SevenZipExtractor.class,
229 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnDetails",
230 cRatio, FileUtil.escapeFileName(getArchiveFilePath(rootArchive.getArchiveFile())));
232 flagRootArchiveAsZipBomb(rootArchive, archiveFile, details, escapedFilePath);
238 }
catch (SevenZipException ex) {
239 logger.log(Level.WARNING,
"Error getting archive item size and cannot detect if zipbomb. ", ex);
255 private void flagRootArchiveAsZipBomb(Archive rootArchive, AbstractFile archiveFile, String details, String escapedFilePath) {
256 rootArchive.flagAsZipBomb();
257 logger.log(Level.INFO, details);
259 Collection<BlackboardAttribute> attributes = Arrays.asList(
260 new BlackboardAttribute(
261 TSK_SET_NAME, MODULE_NAME,
262 "Possible Zip Bomb"),
263 new BlackboardAttribute(
264 TSK_DESCRIPTION, MODULE_NAME,
265 Bundle.SevenZipExtractor_zipBombArtifactCreation_text(archiveFile.getName())),
266 new BlackboardAttribute(
267 TSK_COMMENT, MODULE_NAME,
270 if (!blackboard.artifactExists(archiveFile, TSK_INTERESTING_FILE_HIT, attributes)) {
271 BlackboardArtifact artifact = rootArchive.getArchiveFile().newArtifact(TSK_INTERESTING_FILE_HIT);
272 artifact.addAttributes(attributes);
279 blackboard.postArtifact(artifact, MODULE_NAME);
281 String msg = NbBundle.getMessage(SevenZipExtractor.class,
282 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnMsg", archiveFile.getName(), escapedFilePath);
284 services.postMessage(IngestMessage.createWarningMessage(MODULE_NAME, msg, details));
286 }
catch (Blackboard.BlackboardException ex) {
287 logger.log(Level.SEVERE,
"Unable to index blackboard artifact " + artifact.getArtifactID(), ex);
288 MessageNotifyUtil.Notify.error(
289 Bundle.SevenZipExtractor_indexError_message(), artifact.getDisplayName());
292 }
catch (TskCoreException ex) {
293 logger.log(Level.SEVERE,
"Error creating blackboard artifact for Zip Bomb Detection for file: " + escapedFilePath, ex);
305 private ArchiveFormat get7ZipOptions(AbstractFile archiveFile) {
307 String detectedFormat;
308 detectedFormat = archiveFile.getMIMEType();
310 if (detectedFormat == null) {
311 logger.log(Level.WARNING,
"Could not detect format for file: {0}", archiveFile);
314 String extension = archiveFile.getNameExtension();
315 if (
"rar".equals(extension))
324 }
else if (detectedFormat.contains(
"application/x-rar-compressed"))
345 private long getRootArchiveId(AbstractFile file)
throws TskCoreException {
346 long id = file.getId();
347 Content parentContent = file.getParent();
348 while (parentContent != null) {
349 id = parentContent.getId();
350 parentContent = parentContent.getParent();
369 private List<AbstractFile> getAlreadyExtractedFiles(AbstractFile archiveFile, String archiveFilePath)
throws TskCoreException, NoCurrentCaseException {
372 if (archiveFile.hasChildren() &&
new File(moduleDirAbsolute, EmbeddedFileExtractorIngestModule.getUniqueName(archiveFile)).exists()) {
373 return Case.getCurrentCaseThrows().getServices().getFileManager().findFilesByParentPath(getRootArchiveId(archiveFile), archiveFilePath);
375 return new ArrayList<>();
385 private String getArchiveFilePath(AbstractFile archiveFile) {
386 return archiveFile.getParentPath() + archiveFile.getName();
395 private void makeLocalDirectories(String uniqueArchiveFileName) {
396 final String localRootAbsPath = getLocalRootAbsPath(uniqueArchiveFileName);
397 final File localRoot =
new File(localRootAbsPath);
398 if (!localRoot.exists()) {
414 private String getPathInArchive(IInArchive archive,
int inArchiveItemIndex, AbstractFile archiveFile)
throws SevenZipException {
415 String pathInArchive = (String) archive.getProperty(inArchiveItemIndex, PropID.PATH);
417 if (pathInArchive == null || pathInArchive.isEmpty()) {
423 String archName = archiveFile.getName();
424 int dotI = archName.lastIndexOf(
".");
425 String useName = null;
427 String base = archName.substring(0, dotI);
428 String ext = archName.substring(dotI);
429 int colonIndex = ext.lastIndexOf(
":");
430 if (colonIndex != -1) {
433 ext = ext.substring(0, colonIndex);
440 useName = base +
".tar";
447 if (useName == null) {
448 pathInArchive =
"/" + archName +
"/" + Integer.toString(inArchiveItemIndex);
450 pathInArchive =
"/" + useName;
452 String msg = NbBundle.getMessage(SevenZipExtractor.class,
453 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.unknownPath.msg",
454 getArchiveFilePath(archiveFile), pathInArchive);
455 logger.log(Level.WARNING, msg);
457 return pathInArchive;
460 private byte[] getPathBytesInArchive(IInArchive archive,
int inArchiveItemIndex, AbstractFile archiveFile)
throws SevenZipException {
461 return (byte[]) archive.getProperty(inArchiveItemIndex, PropID.PATH_BYTES);
468 private String getKeyAbstractFile(AbstractFile fileInDatabase) {
469 return fileInDatabase == null ? null : fileInDatabase.getParentPath() + fileInDatabase.getName();
476 private String getKeyFromUnpackedNode(UnpackedTree.UnpackedNode node, String archiveFilePath) {
477 return node == null ? null : archiveFilePath +
"/" + node.getFileName();
487 void unpack(AbstractFile archiveFile, ConcurrentHashMap<Long, Archive> depthMap) {
488 unpack(archiveFile, depthMap, null);
502 @Messages({
"SevenZipExtractor.indexError.message=Failed to index encryption detected artifact for keyword search.",
503 "# {0} - rootArchive",
504 "SevenZipExtractor.zipBombArtifactCreation.text=Zip Bomb Detected {0}"})
505 boolean unpack(AbstractFile archiveFile, ConcurrentHashMap<Long, Archive> depthMap, String password) {
506 boolean unpackSuccessful =
true;
507 boolean hasEncrypted =
false;
508 boolean fullEncryption =
true;
509 boolean progressStarted =
false;
510 final String archiveFilePath = getArchiveFilePath(archiveFile);
511 final String escapedArchiveFilePath = FileUtil.escapeFileName(archiveFilePath);
512 HashMap<String, ZipFileStatusWrapper> statusMap =
new HashMap<>();
513 List<AbstractFile> unpackedFiles = Collections.<AbstractFile>emptyList();
515 currentArchiveName = archiveFile.getName();
517 SevenZipContentReadStream stream = null;
518 progress = ProgressHandle.createHandle(Bundle.EmbeddedFileExtractorIngestModule_ArchiveExtractor_moduleName());
522 blackboard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard();
523 }
catch (NoCurrentCaseException ex) {
524 logger.log(Level.INFO,
"Exception while getting open case.", ex);
525 unpackSuccessful =
false;
526 return unpackSuccessful;
529 List<AbstractFile> existingFiles = getAlreadyExtractedFiles(archiveFile, archiveFilePath);
530 for (AbstractFile file : existingFiles) {
531 statusMap.put(getKeyAbstractFile(file),
new ZipFileStatusWrapper(file, ZipFileStatus.EXISTS));
533 }
catch (TskCoreException e) {
534 logger.log(Level.INFO,
"Error checking if file already has been processed, skipping: {0}", escapedArchiveFilePath);
535 unpackSuccessful =
false;
536 return unpackSuccessful;
537 }
catch (NoCurrentCaseException ex) {
538 logger.log(Level.INFO,
"No open case was found while trying to unpack the archive file {0}", escapedArchiveFilePath);
539 unpackSuccessful =
false;
540 return unpackSuccessful;
542 parentAr = depthMap.get(archiveFile.getId());
543 if (parentAr == null) {
544 parentAr =
new Archive(0, archiveFile.getId(), archiveFile);
545 depthMap.put(archiveFile.getId(), parentAr);
547 Archive rootArchive = depthMap.get(parentAr.getRootArchiveId());
548 if (rootArchive.isFlaggedAsZipBomb()) {
550 unpackSuccessful =
false;
551 return unpackSuccessful;
552 }
else if (parentAr.getDepth() == MAX_DEPTH) {
553 String details = NbBundle.getMessage(SevenZipExtractor.class,
554 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnDetails.zipBomb",
555 parentAr.getDepth(), FileUtil.escapeFileName(getArchiveFilePath(rootArchive.getArchiveFile())));
556 flagRootArchiveAsZipBomb(rootArchive, archiveFile, details, escapedArchiveFilePath);
557 unpackSuccessful =
false;
558 return unpackSuccessful;
561 IInArchive inArchive = null;
563 stream =
new SevenZipContentReadStream(
new ReadContentInputStream(archiveFile));
567 ArchiveFormat options = get7ZipOptions(archiveFile);
568 if (password == null) {
569 inArchive = SevenZip.openInArchive(options, stream);
571 inArchive = SevenZip.openInArchive(options, stream, password);
573 numItems = inArchive.getNumberOfItems();
574 progress.start(numItems);
575 progressStarted =
true;
578 final String uniqueArchiveFileName = FileUtil.escapeFileName(EmbeddedFileExtractorIngestModule.getUniqueName(archiveFile));
580 makeLocalDirectories(uniqueArchiveFileName);
581 }
catch (SecurityException e) {
582 logger.log(Level.SEVERE,
"Error setting up output path for archive root: {0}", getLocalRootAbsPath(uniqueArchiveFileName));
584 unpackSuccessful =
false;
585 return unpackSuccessful;
589 SevenZipExtractor.UnpackedTree unpackedTree =
new SevenZipExtractor.UnpackedTree(moduleDirRelative +
"/" + uniqueArchiveFileName, archiveFile);
593 freeDiskSpace = services.getFreeDiskSpace();
594 }
catch (NullPointerException ex) {
597 freeDiskSpace = IngestMonitor.DISK_FREE_SPACE_UNKNOWN;
600 Map<Integer, InArchiveItemDetails> archiveDetailsMap =
new HashMap<>();
601 for (
int inArchiveItemIndex = 0; inArchiveItemIndex < numItems; inArchiveItemIndex++) {
602 progress.progress(String.format(
"%s: Analyzing archive metadata and creating local files (%d of %d)", currentArchiveName, inArchiveItemIndex + 1, numItems), 0);
603 if (isZipBombArchiveItemCheck(archiveFile, inArchive, inArchiveItemIndex, depthMap, escapedArchiveFilePath)) {
604 unpackSuccessful =
false;
605 return unpackSuccessful;
608 String pathInArchive = getPathInArchive(inArchive, inArchiveItemIndex, archiveFile);
609 byte[] pathBytesInArchive = getPathBytesInArchive(inArchive, inArchiveItemIndex, archiveFile);
610 UnpackedTree.UnpackedNode unpackedNode = unpackedTree.addNode(pathInArchive, pathBytesInArchive);
612 final boolean isEncrypted = (Boolean) inArchive.getProperty(inArchiveItemIndex, PropID.ENCRYPTED);
614 if (isEncrypted && password == null) {
615 logger.log(Level.WARNING,
"Skipping encrypted file in archive: {0}", pathInArchive);
617 unpackSuccessful =
false;
620 fullEncryption =
false;
627 Long archiveItemSize = (Long) inArchive.getProperty(
628 inArchiveItemIndex, PropID.SIZE);
629 if (freeDiskSpace != IngestMonitor.DISK_FREE_SPACE_UNKNOWN && archiveItemSize != null && archiveItemSize > 0) {
630 String archiveItemPath = (String) inArchive.getProperty(
631 inArchiveItemIndex, PropID.PATH);
632 long newDiskSpace = freeDiskSpace - archiveItemSize;
633 if (newDiskSpace < MIN_FREE_DISK_SPACE) {
634 String msg = NbBundle.getMessage(SevenZipExtractor.class,
635 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.msg",
636 escapedArchiveFilePath, archiveItemPath);
637 String details = NbBundle.getMessage(SevenZipExtractor.class,
638 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.details");
639 services.postMessage(IngestMessage.createErrorMessage(MODULE_NAME, msg, details));
640 logger.log(Level.INFO,
"Skipping archive item due to insufficient disk space: {0}, {1}",
new String[]{escapedArchiveFilePath, archiveItemPath});
641 logger.log(Level.INFO,
"Available disk space: {0}",
new Object[]{freeDiskSpace});
642 unpackSuccessful =
false;
646 freeDiskSpace = newDiskSpace;
649 final String uniqueExtractedName = FileUtil.escapeFileName(uniqueArchiveFileName + File.separator + (inArchiveItemIndex / 1000) + File.separator + inArchiveItemIndex +
"_" +
new File(pathInArchive).getName());
650 final String localAbsPath = moduleDirAbsolute + File.separator + uniqueExtractedName;
651 final String localRelPath = moduleDirRelative + File.separator + uniqueExtractedName;
654 File localFile =
new java.io.File(localAbsPath);
656 if (!localFile.exists()) {
658 if ((Boolean) inArchive.getProperty(
659 inArchiveItemIndex, PropID.IS_FOLDER)) {
662 localFile.getParentFile().mkdirs();
664 localFile.createNewFile();
665 }
catch (IOException e) {
666 logger.log(Level.SEVERE,
"Error creating extracted file: "
667 + localFile.getAbsolutePath(), e);
670 }
catch (SecurityException e) {
671 logger.log(Level.SEVERE,
"Error setting up output path for unpacked file: {0}",
678 if (localFile.exists() ==
false) {
686 archiveDetailsMap.put(inArchiveItemIndex,
new InArchiveItemDetails(
687 unpackedNode, localAbsPath, localRelPath));
690 int[] extractionIndices = getExtractableFilesFromDetailsMap(archiveDetailsMap);
692 StandardIArchiveExtractCallback archiveCallBack
693 =
new StandardIArchiveExtractCallback(
694 inArchive, archiveFile, progress,
695 archiveDetailsMap, password, freeDiskSpace);
700 inArchive.extract(extractionIndices,
false, archiveCallBack);
702 unpackSuccessful &= archiveCallBack.wasSuccessful();
704 archiveDetailsMap = null;
709 unpackedTree.updateOrAddFileToCaseRec(statusMap, archiveFilePath);
710 unpackedFiles = unpackedTree.getAllFileObjects();
712 for (
int i = 0; i < unpackedFiles.size(); i++) {
713 progress.progress(String.format(
"%s: Searching for nested archives (%d of %d)", currentArchiveName, i + 1, unpackedFiles.size()));
714 AbstractFile unpackedFile = unpackedFiles.get(i);
715 if (unpackedFile == null) {
718 if (isSevenZipExtractionSupported(unpackedFile)) {
719 Archive child =
new Archive(parentAr.getDepth() + 1, parentAr.getRootArchiveId(), archiveFile);
720 parentAr.addChild(child);
721 depthMap.put(unpackedFile.getId(), child);
723 unpackedFile.close();
726 }
catch (TskCoreException | NoCurrentCaseException e) {
727 logger.log(Level.SEVERE,
"Error populating complete derived file hierarchy from the unpacked dir structure", e);
731 }
catch (SevenZipException ex) {
732 logger.log(Level.WARNING,
"Error unpacking file: " + archiveFile, ex);
736 if (archiveFile.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC)) {
737 String msg = NbBundle.getMessage(SevenZipExtractor.class,
738 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.errUnpacking.msg",
740 String details = NbBundle.getMessage(SevenZipExtractor.class,
741 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.errUnpacking.details",
742 escapedArchiveFilePath, ex.getMessage());
743 services.postMessage(IngestMessage.createErrorMessage(MODULE_NAME, msg, details));
746 if (inArchive != null) {
749 }
catch (SevenZipException e) {
750 logger.log(Level.SEVERE,
"Error closing archive: " + archiveFile, e);
754 if (stream != null) {
757 }
catch (IOException ex) {
758 logger.log(Level.SEVERE,
"Error closing stream after unpacking archive: " + archiveFile, ex);
763 if (progressStarted) {
770 String encryptionType = fullEncryption ? ENCRYPTION_FULL : ENCRYPTION_FILE_LEVEL;
772 BlackboardArtifact artifact = archiveFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED);
773 artifact.addAttribute(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT, MODULE_NAME, encryptionType));
781 blackboard.postArtifact(artifact, MODULE_NAME);
782 }
catch (Blackboard.BlackboardException ex) {
783 logger.log(Level.SEVERE,
"Unable to post blackboard artifact " + artifact.getArtifactID(), ex);
784 MessageNotifyUtil.Notify.error(
785 Bundle.SevenZipExtractor_indexError_message(), artifact.getDisplayName());
788 }
catch (TskCoreException ex) {
789 logger.log(Level.SEVERE,
"Error creating blackboard artifact for encryption detected for file: " + escapedArchiveFilePath, ex);
792 String msg = NbBundle.getMessage(SevenZipExtractor.class,
793 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.encrFileDetected.msg");
794 String details = NbBundle.getMessage(SevenZipExtractor.class,
795 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.encrFileDetected.details",
796 currentArchiveName, MODULE_NAME);
797 services.postMessage(IngestMessage.createWarningMessage(MODULE_NAME, msg, details));
801 if (!unpackedFiles.isEmpty()) {
803 services.fireModuleContentEvent(
new ModuleContentEvent(archiveFile));
804 if (context != null) {
805 context.addFilesToJob(unpackedFiles);
808 return unpackSuccessful;
811 private Charset detectFilenamesCharset(List<byte[]> byteDatas) {
812 Charset detectedCharset = null;
813 CharsetDetector charsetDetector =
new CharsetDetector();
816 for (byte[] byteData : byteDatas) {
818 byteSum += byteData.length;
820 if (byteSum >= 1000) {
824 byte[] allBytes =
new byte[byteSum];
826 for (
int i = 0; i < fileNum; i++) {
827 byte[] byteData = byteDatas.get(i);
828 System.arraycopy(byteData, 0, allBytes, start, byteData.length);
829 start += byteData.length;
831 charsetDetector.setText(allBytes);
832 CharsetMatch cm = charsetDetector.detect();
833 if (cm.getConfidence() >= 90 && Charset.isSupported(cm.getName())) {
834 detectedCharset = Charset.forName(cm.getName());
836 return detectedCharset;
843 private int[] getExtractableFilesFromDetailsMap(
844 Map<Integer, InArchiveItemDetails> archiveDetailsMap) {
846 Integer[] wrappedExtractionIndices = archiveDetailsMap.keySet()
847 .toArray(
new Integer[archiveDetailsMap.size()]);
849 return Arrays.stream(wrappedExtractionIndices)
850 .mapToInt(Integer::intValue)
861 private final static class UnpackStream implements ISequentialOutStream {
868 this.output =
new EncodedFileOutputStream(
new FileOutputStream(localAbsPath), TskData.EncodingType.XOR1);
870 this.bytesWritten = 0;
875 this.output =
new EncodedFileOutputStream(
new FileOutputStream(localAbsPath), TskData.EncodingType.XOR1);
877 this.bytesWritten = 0;
885 public int write(byte[] bytes)
throws SevenZipException {
888 this.bytesWritten += bytes.length;
889 }
catch (IOException ex) {
890 throw new SevenZipException(
891 NbBundle.getMessage(SevenZipExtractor.class,
892 "EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackStream.write.exception.msg",
898 public void close() throws IOException {
899 try (EncodedFileOutputStream out = output) {
911 private final SevenZipExtractor.UnpackedTree.UnpackedNode
unpackedNode;
916 SevenZipExtractor.UnpackedTree.UnpackedNode
unpackedNode,
917 String localAbsPath, String localRelPath) {
941 implements IArchiveExtractCallback, ICryptoGetTextPassword {
958 private boolean unpackSuccessful =
true;
961 AbstractFile archiveFile, ProgressHandle progressHandle,
962 Map<Integer, InArchiveItemDetails> archiveDetailsMap,
963 String password,
long freeDiskSpace) {
986 public ISequentialOutStream
getStream(
int inArchiveItemIndex,
987 ExtractAskMode mode)
throws SevenZipException {
991 isFolder = (Boolean) inArchive
992 .getProperty(inArchiveItemIndex, PropID.IS_FOLDER);
993 if (isFolder || mode != ExtractAskMode.EXTRACT) {
997 final String localAbsPath = archiveDetailsMap.get(
998 inArchiveItemIndex).getLocalAbsPath();
1006 if (unpackStream != null) {
1011 }
catch (IOException ex) {
1012 logger.log(Level.WARNING, String.format(
"Error opening or setting new stream "
1013 +
"for archive file at %s", localAbsPath), ex.getMessage());
1030 final Date createTime = (Date) inArchive.getProperty(
1031 inArchiveItemIndex, PropID.CREATION_TIME);
1032 final Date accessTime = (Date) inArchive.getProperty(
1033 inArchiveItemIndex, PropID.LAST_ACCESS_TIME);
1034 final Date writeTime = (Date) inArchive.getProperty(
1035 inArchiveItemIndex, PropID.LAST_MODIFICATION_TIME);
1037 createTimeInSeconds = createTime == null ? 0L
1038 : createTime.getTime() / 1000;
1039 modTimeInSeconds = writeTime == null ? 0L
1040 : writeTime.getTime() / 1000;
1041 accessTimeInSeconds = accessTime == null ? 0L
1042 : accessTime.getTime() / 1000;
1044 progressHandle.progress(archiveFile.getName() +
": "
1045 + (String) inArchive.getProperty(inArchiveItemIndex, PropID.PATH),
1061 final SevenZipExtractor.UnpackedTree.UnpackedNode unpackedNode
1062 = archiveDetailsMap.get(inArchiveItemIndex).getUnpackedNode();
1063 final String localRelPath = archiveDetailsMap.get(
1064 inArchiveItemIndex).getLocalRelPath();
1066 unpackedNode.addDerivedInfo(0,
1067 !(Boolean) inArchive.getProperty(inArchiveItemIndex, PropID.IS_FOLDER),
1073 final String localAbsPath = archiveDetailsMap.get(
1074 inArchiveItemIndex).getLocalAbsPath();
1075 if (result != ExtractOperationResult.OK) {
1076 logger.log(Level.WARNING,
"Extraction of : {0} encountered error {1}",
1077 new Object[]{localAbsPath, result});
1078 unpackSuccessful =
false;
1082 unpackedNode.addDerivedInfo(unpackStream.
getSize(),
1083 !(Boolean) inArchive.getProperty(inArchiveItemIndex, PropID.IS_FOLDER),
1087 unpackStream.
close();
1088 }
catch (IOException e) {
1089 logger.log(Level.WARNING,
"Error closing unpack stream for file: {0}", localAbsPath);
1094 public void setTotal(
long value)
throws SevenZipException {
1138 UnpackedTree(String localPathRoot, AbstractFile archiveFile) {
1140 this.rootNode.setFile(archiveFile);
1141 this.rootNode.setFileName(archiveFile.getName());
1142 this.rootNode.setLocalRelPath(localPathRoot);
1154 UnpackedNode addNode(String filePath, byte[] filePathBytes) {
1155 String[] toks = filePath.split(
"[\\/\\\\]");
1156 List<String> tokens =
new ArrayList<>();
1157 for (
int i = 0; i < toks.length; ++i) {
1158 if (!toks[i].isEmpty()) {
1159 tokens.add(toks[i]);
1163 List<byte[]> byteTokens = null;
1164 if (filePathBytes == null) {
1165 return addNode(rootNode, tokens, null);
1167 byteTokens =
new ArrayList<>(tokens.size());
1169 for (
int i = 0; i < filePathBytes.length; i++) {
1170 if (filePathBytes[i] ==
'/') {
1172 byte[] arr =
new byte[len];
1173 System.arraycopy(filePathBytes, last, arr, 0, len);
1174 byteTokens.add(arr);
1178 int len = filePathBytes.length - last;
1180 byte[] arr =
new byte[len];
1181 System.arraycopy(filePathBytes, last, arr, 0, len);
1182 byteTokens.add(arr);
1185 if (tokens.size() != byteTokens.size()) {
1186 logger.log(Level.WARNING,
"Could not map path bytes to path string");
1187 return addNode(rootNode, tokens, null);
1191 return addNode(rootNode, tokens, byteTokens);
1204 List<String> tokenPath, List<byte[]> tokenPathBytes) {
1206 if (tokenPath.isEmpty()) {
1211 String childName = tokenPath.remove(0);
1212 byte[] childNameBytes = null;
1213 if (tokenPathBytes != null) {
1214 childNameBytes = tokenPathBytes.remove(0);
1218 if (child == null) {
1220 child.setFileNameBytes(childNameBytes);
1221 parent.addChild(child);
1225 return addNode(child, tokenPath, tokenPathBytes);
1234 List<AbstractFile> getRootFileObjects() {
1235 List<AbstractFile> ret =
new ArrayList<>();
1236 rootNode.getChildren().forEach((child) -> {
1237 ret.add(child.getFile());
1248 List<AbstractFile> getAllFileObjects() {
1249 List<AbstractFile> ret =
new ArrayList<>();
1250 rootNode.getChildren().forEach((child) -> {
1257 list.add(parent.getFile());
1258 parent.getChildren().forEach((child) -> {
1267 void updateOrAddFileToCaseRec(HashMap<String, ZipFileStatusWrapper> statusMap, String archiveFilePath)
throws TskCoreException,
NoCurrentCaseException {
1269 for (UnpackedNode child : rootNode.getChildren()) {
1270 updateOrAddFileToCaseRec(child, fileManager, statusMap, archiveFilePath);
1290 progress.progress(String.format(
"%s: Adding/updating files in case database (%d of %d)", currentArchiveName, ++nodesProcessed, numItems));
1292 String nameInDatabase = getKeyFromUnpackedNode(node, archiveFilePath);
1293 ZipFileStatusWrapper existingFile = nameInDatabase == null ? null : statusMap.get(nameInDatabase);
1294 if (existingFile == null) {
1295 df = fileManager.
addDerivedFile(node.getFileName(), node.getLocalRelPath(), node.getSize(),
1296 node.getCtime(), node.getCrtime(), node.getAtime(), node.getMtime(),
1297 node.isIsFile(), node.getParent().getFile(),
"", MODULE_NAME,
1298 "",
"", TskData.EncodingType.XOR1);
1301 String key = getKeyAbstractFile(existingFile.
getFile());
1304 statusMap.put(key, existingFile);
1308 String mimeType = existingFile.
getFile().getMIMEType().equalsIgnoreCase(
"application/octet-stream") ? null : existingFile.
getFile().getMIMEType();
1310 node.getCtime(), node.getCrtime(), node.getAtime(), node.getMtime(),
1311 node.isIsFile(), mimeType,
"", MODULE_NAME,
1312 "",
"", TskData.EncodingType.XOR1);
1316 df = (DerivedFile) existingFile.
getFile();
1320 }
catch (TskCoreException ex) {
1321 logger.log(Level.SEVERE,
"Error adding a derived file to db:" + node.getFileName(), ex);
1322 throw new TskCoreException(
1323 NbBundle.getMessage(SevenZipExtractor.class,
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackedTree.exception.msg",
1324 node.getFileName()), ex);
1328 if (node.getChildren().size() > 0) {
1330 ArrayList<byte[]> byteDatas =
new ArrayList<>();
1332 byte[] childBytes = child.getFileNameBytes();
1333 if (childBytes != null) {
1334 byteDatas.add(childBytes);
1336 names += child.getFileName();
1338 Charset detectedCharset = detectFilenamesCharset(byteDatas);
1341 if (detectedCharset != null && detectedCharset.canEncode()) {
1343 byte[] childBytes = child.getFileNameBytes();
1344 if (childBytes != null) {
1345 String decodedName =
new String(childBytes, detectedCharset);
1346 child.setFileName(decodedName);
1354 updateOrAddFileToCaseRec(child, fileManager, statusMap, getKeyFromUnpackedNode(node, archiveFilePath));
1366 private final List<UnpackedNode>
children =
new ArrayList<>();
1367 private String localRelPath =
"";
1369 private long ctime, crtime, atime, mtime;
1381 this.localRelPath = parent.getLocalRelPath() + File.separator +
fileName;
1400 void setFileName(String fileName) {
1409 void addChild(UnpackedNode child) {
1410 children.add(child);
1419 List<UnpackedNode> getChildren() {
1428 UnpackedNode getParent() {
1432 void addDerivedInfo(
long size,
1434 long ctime,
long crtime,
long atime,
long mtime, String relLocalPath) {
1438 this.crtime = crtime;
1441 this.localRelPath = relLocalPath;
1444 void setFile(AbstractFile file) {
1455 UnpackedNode getChild(String childFileName) {
1456 UnpackedNode ret = null;
1457 for (UnpackedNode child : children) {
1458 if (child.getFileName().equals(childFileName)) {
1466 String getFileName() {
1470 AbstractFile getFile() {
1474 String getLocalRelPath() {
1484 void setLocalRelPath(String localRelativePath) {
1485 localRelPath = localRelativePath;
1492 boolean isIsFile() {
1496 void setFileNameBytes(byte[] fileNameBytes) {
1497 if (fileNameBytes != null) {
1498 this.fileNameBytes = Arrays.copyOf(fileNameBytes, fileNameBytes.length);
1502 byte[] getFileNameBytes() {
1503 if (fileNameBytes == null) {
1506 return Arrays.copyOf(fileNameBytes, fileNameBytes.length);
1515 static class Archive {
1518 private final int depth;
1519 private final List<Archive> children;
1520 private final long rootArchiveId;
1521 private boolean flaggedAsZipBomb =
false;
1522 private final AbstractFile archiveFile;
1536 Archive(
int depth,
long rootArchiveId, AbstractFile archiveFile) {
1537 this.children =
new ArrayList<>();
1539 this.rootArchiveId = rootArchiveId;
1540 this.archiveFile = archiveFile;
1549 void addChild(Archive child) {
1550 children.add(child);
1557 synchronized void flagAsZipBomb() {
1558 flaggedAsZipBomb =
true;
1566 synchronized boolean isFlaggedAsZipBomb() {
1567 return flaggedAsZipBomb;
1575 AbstractFile getArchiveFile() {
1584 long getRootArchiveId() {
1585 return rootArchiveId;
1593 long getObjectId() {
1594 return archiveFile.getId();
1626 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)