21 package org.sleuthkit.autopsy.recentactivity;
24 import java.io.FileOutputStream;
25 import java.io.IOException;
26 import java.io.RandomAccessFile;
27 import java.nio.ByteBuffer;
28 import java.nio.ByteOrder;
29 import java.nio.channels.FileChannel;
30 import java.nio.charset.Charset;
31 import java.nio.file.Path;
32 import java.nio.file.Paths;
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 import java.util.Collection;
36 import java.util.Collections;
37 import java.util.Comparator;
38 import java.util.HashMap;
39 import java.util.List;
41 import java.util.Map.Entry;
42 import java.util.Optional;
43 import java.util.logging.Level;
44 import org.openide.util.NbBundle;
45 import org.openide.util.NbBundle.Messages;
60 import org.
sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
61 import static org.
sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_CACHE;
95 final class ChromeCacheExtractor {
97 private final static String DEFAULT_CACHE_PATH_STR =
"default/cache";
98 private final static String BROTLI_MIMETYPE =
"application/x-brotli";
100 private final static long UINT32_MASK = 0xFFFFFFFFl;
102 private final static int INDEXFILE_HDR_SIZE = 92*4;
103 private final static int DATAFILE_HDR_SIZE = 8192;
105 private final static Logger logger = Logger.getLogger(ChromeCacheExtractor.class.getName());
107 private static final String VERSION_NUMBER =
"1.0.0";
108 private final String moduleName;
110 private String absOutputFolderName;
111 private String relOutputFolderName;
113 private final Content dataSource;
114 private final IngestJobContext context;
115 private final DataSourceIngestModuleProgress progressBar;
116 private final IngestServices services = IngestServices.getInstance();
117 private Case currentCase;
118 private FileManager fileManager;
121 private final Map<String, FileWrapper> fileCopyCache =
new HashMap<>();
124 private final Map<String, AbstractFile> externalFilesTable =
new HashMap<>();
131 final class FileWrapper {
132 private final AbstractFile abstractFile;
133 private final RandomAccessFile fileCopy;
134 private final ByteBuffer byteBuffer;
136 FileWrapper (AbstractFile abstractFile, RandomAccessFile fileCopy, ByteBuffer buffer ) {
137 this.abstractFile = abstractFile;
138 this.fileCopy = fileCopy;
139 this.byteBuffer = buffer;
142 public RandomAccessFile getFileCopy() {
145 public ByteBuffer getByteBuffer() {
148 AbstractFile getAbstractFile() {
154 "ChromeCacheExtractor.moduleName=ChromeCacheExtractor",
155 "# {0} - module name",
156 "# {1} - row number",
157 "# {2} - table length",
158 "# {3} - cache path",
159 "ChromeCacheExtractor.progressMsg={0}: Extracting cache entry {1} of {2} entries from {3}"
161 ChromeCacheExtractor(Content dataSource, IngestJobContext context, DataSourceIngestModuleProgress progressBar ) {
162 moduleName = Bundle.ChromeCacheExtractor_moduleName();
163 this.dataSource = dataSource;
164 this.context = context;
165 this.progressBar = progressBar;
174 private void moduleInit() throws IngestModuleException {
177 currentCase = Case.getCurrentCaseThrows();
178 fileManager = currentCase.getServices().getFileManager();
180 }
catch (NoCurrentCaseException ex) {
181 String msg =
"Failed to get current case.";
182 throw new IngestModuleException(msg, ex);
193 private void resetForNewCacheFolder(String cachePath)
throws IngestModuleException {
195 fileCopyCache.clear();
196 externalFilesTable.clear();
198 String cacheAbsOutputFolderName = this.getAbsOutputFolderName() + cachePath;
199 File outDir =
new File(cacheAbsOutputFolderName);
200 if (outDir.exists() ==
false) {
204 String cacheTempPath = RAImageIngestModule.getRATempPath(currentCase, moduleName, context.getJobId()) + cachePath;
205 File tempDir =
new File(cacheTempPath);
206 if (tempDir.exists() ==
false) {
217 private void cleanup () {
219 for (Entry<String, FileWrapper> entry : this.fileCopyCache.entrySet()) {
220 Path tempFilePath = Paths.get(RAImageIngestModule.getRATempPath(currentCase, moduleName, context.getJobId()), entry.getKey() );
222 entry.getValue().getFileCopy().getChannel().close();
223 entry.getValue().getFileCopy().close();
225 File tmpFile = tempFilePath.toFile();
226 if (!tmpFile.delete()) {
227 tmpFile.deleteOnExit();
229 }
catch (IOException ex) {
230 logger.log(Level.WARNING, String.format(
"Failed to delete cache file copy %s", tempFilePath.toString()), ex);
240 private String getAbsOutputFolderName() {
241 return absOutputFolderName;
249 private String getRelOutputFolderName() {
250 return relOutputFolderName;
259 void processCaches() {
263 }
catch (IngestModuleException ex) {
264 String msg =
"Failed to initialize ChromeCacheExtractor.";
265 logger.log(Level.SEVERE, msg, ex);
272 List<AbstractFile> indexFiles = findIndexFiles();
274 if (indexFiles.size() > 0) {
276 absOutputFolderName = RAImageIngestModule.getRAOutputPath(currentCase, moduleName, context.getJobId());
277 relOutputFolderName = Paths.get(RAImageIngestModule.getRelModuleOutputPath(currentCase, moduleName, context.getJobId())).normalize().toString();
279 File dir =
new File(absOutputFolderName);
280 if (dir.exists() ==
false) {
286 for (AbstractFile indexFile: indexFiles) {
288 if (context.dataSourceIngestIsCancelled()) {
292 if (indexFile.getSize() > 0) {
293 processCacheFolder(indexFile);
297 }
catch (TskCoreException ex) {
298 String msg =
"Failed to find cache index files";
299 logger.log(Level.WARNING, msg, ex);
304 "ChromeCacheExtract_adding_extracted_files_msg=Chrome Cache: Adding %d extracted files for analysis.",
305 "ChromeCacheExtract_adding_artifacts_msg=Chrome Cache: Adding %d artifacts for analysis.",
306 "ChromeCacheExtract_loading_files_msg=Chrome Cache: Loading files from %s."
315 private void processCacheFolder(AbstractFile indexFile) {
317 String cacheFolderName = indexFile.getParentPath();
318 Optional<FileWrapper> indexFileWrapper;
326 progressBar.progress(String.format(Bundle.ChromeCacheExtract_loading_files_msg(), cacheFolderName));
327 resetForNewCacheFolder(cacheFolderName);
331 indexFileWrapper = findDataOrIndexFile(indexFile.getName(), cacheFolderName);
332 if (!indexFileWrapper.isPresent()) {
333 String msg = String.format(
"Failed to find copy cache index file %s", indexFile.getUniquePath());
334 logger.log(Level.WARNING, msg);
341 for (
int i = 0; i < 4; i ++) {
342 Optional<FileWrapper> dataFile = findDataOrIndexFile(String.format(
"data_%1d",i), cacheFolderName );
343 if (!dataFile.isPresent()) {
350 findExternalFiles(cacheFolderName);
352 }
catch (TskCoreException | IngestModuleException ex) {
353 String msg =
"Failed to find cache files in path " + cacheFolderName;
354 logger.log(Level.WARNING, msg, ex);
362 logger.log(Level.INFO,
"{0}- Now reading Cache index file from path {1}",
new Object[]{moduleName, cacheFolderName });
364 List<AbstractFile> derivedFiles =
new ArrayList<>();
365 Collection<BlackboardArtifact> artifactsAdded =
new ArrayList<>();
367 ByteBuffer indexFileROBuffer = indexFileWrapper.get().getByteBuffer();
368 IndexFileHeader indexHdr =
new IndexFileHeader(indexFileROBuffer);
371 indexFileROBuffer.position(INDEXFILE_HDR_SIZE);
376 for (
int i = 0; i < indexHdr.getTableLen(); i++) {
378 if (context.dataSourceIngestIsCancelled()) {
383 CacheAddress addr =
new CacheAddress(indexFileROBuffer.getInt() & UINT32_MASK, cacheFolderName);
384 if (addr.isInitialized()) {
385 progressBar.progress(NbBundle.getMessage(
this.getClass(),
386 "ChromeCacheExtractor.progressMsg",
387 moduleName, i, indexHdr.getTableLen(), cacheFolderName) );
389 List<DerivedFile> addedFiles = processCacheEntry(addr, artifactsAdded);
390 derivedFiles.addAll(addedFiles);
392 catch (TskCoreException | IngestModuleException ex) {
393 logger.log(Level.WARNING, String.format(
"Failed to get cache entry at address %s for file with object ID %d (%s)", addr, indexFile.getId(), ex.getLocalizedMessage()));
397 }
catch (java.nio.BufferUnderflowException ex) {
398 logger.log(Level.WARNING, String.format(
"Ran out of data unexpectedly reading file %s (ObjID: %d)", indexFile.getName(), indexFile.getId()));
401 if (context.dataSourceIngestIsCancelled()) {
408 progressBar.progress(String.format(Bundle.ChromeCacheExtract_adding_extracted_files_msg(), derivedFiles.size()));
409 derivedFiles.forEach((derived) -> {
410 services.fireModuleContentEvent(
new ModuleContentEvent(derived));
412 context.addFilesToJob(derivedFiles);
415 progressBar.progress(String.format(Bundle.ChromeCacheExtract_adding_artifacts_msg(), artifactsAdded.size()));
416 Blackboard blackboard = currentCase.getSleuthkitCase().getBlackboard();
418 blackboard.postArtifacts(artifactsAdded, moduleName);
419 }
catch (Blackboard.BlackboardException ex) {
420 logger.log(Level.WARNING, String.format(
"Failed to post cacheIndex artifacts "), ex);
438 private List<DerivedFile> processCacheEntry(CacheAddress cacheAddress, Collection<BlackboardArtifact> artifactsAdded )
throws TskCoreException, IngestModuleException {
440 List<DerivedFile> derivedFiles =
new ArrayList<>();
443 String cacheEntryFileName = cacheAddress.getFilename();
444 String cachePath = cacheAddress.getCachePath();
446 Optional<FileWrapper> cacheEntryFileOptional = findDataOrIndexFile(cacheEntryFileName, cachePath);
447 if (!cacheEntryFileOptional.isPresent()) {
448 String msg = String.format(
"Failed to find data file %s", cacheEntryFileName);
449 throw new IngestModuleException(msg);
453 CacheEntry cacheEntry =
new CacheEntry(cacheAddress, cacheEntryFileOptional.get() );
454 List<CacheDataSegment> dataSegments = cacheEntry.getDataSegments();
458 if (dataSegments.size() < 2) {
461 CacheDataSegment dataSegment = dataSegments.get(1);
464 String segmentFileName = dataSegment.getCacheAddress().getFilename();
465 Optional<AbstractFile> segmentFileAbstractFile = findAbstractFile(segmentFileName, cachePath);
466 if (!segmentFileAbstractFile.isPresent()) {
467 logger.log(Level.WARNING,
"Error finding segment file: " + cachePath +
"/" + segmentFileName);
471 boolean isBrotliCompressed =
false;
472 if (dataSegment.getType() != CacheDataTypeEnum.HTTP_HEADER && cacheEntry.isBrotliCompressed() ) {
473 isBrotliCompressed =
true;
479 AbstractFile cachedItemFile;
481 if (dataSegment.isInExternalFile() ) {
482 cachedItemFile = segmentFileAbstractFile.get();
488 String filename = dataSegment.save();
489 String relPathname = getRelOutputFolderName() + dataSegment.getCacheAddress().getCachePath() + filename;
492 DerivedFile derivedFile = fileManager.addDerivedFile(filename, relPathname,
493 dataSegment.getDataLength(),
494 cacheEntry.getCreationTime(), cacheEntry.getCreationTime(), cacheEntry.getCreationTime(), cacheEntry.getCreationTime(),
496 segmentFileAbstractFile.get(),
501 TskData.EncodingType.NONE);
503 derivedFiles.add(derivedFile);
504 cachedItemFile = derivedFile;
507 addArtifacts(cacheEntry, cacheEntryFileOptional.get().getAbstractFile(), cachedItemFile, artifactsAdded);
510 if (isBrotliCompressed) {
511 cachedItemFile.setMIMEType(BROTLI_MIMETYPE);
512 cachedItemFile.save();
515 }
catch (TskException ex) {
516 logger.log(Level.SEVERE,
"Error while trying to add an artifact", ex);
531 private void addArtifacts(CacheEntry cacheEntry, AbstractFile cacheEntryFile, AbstractFile cachedItemFile, Collection<BlackboardArtifact> artifactsAdded)
throws TskCoreException {
534 Collection<BlackboardAttribute> webAttr =
new ArrayList<>();
535 String url = cacheEntry.getKey() != null ? cacheEntry.getKey() :
"";
536 webAttr.add(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL,
538 webAttr.add(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN,
539 moduleName, NetworkUtils.extractDomain(url)));
540 webAttr.add(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED,
541 moduleName, cacheEntry.getCreationTime()));
542 webAttr.add(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_HEADERS,
543 moduleName, cacheEntry.getHTTPHeaders()));
544 webAttr.add(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH,
545 moduleName, cachedItemFile.getUniquePath()));
546 webAttr.add(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_ID,
547 moduleName, cachedItemFile.getId()));
549 BlackboardArtifact webCacheArtifact = cacheEntryFile.newDataArtifact(
new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_WEB_CACHE), webAttr);
550 artifactsAdded.add(webCacheArtifact);
553 BlackboardArtifact associatedObjectArtifact = cachedItemFile.newDataArtifact(
554 new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT),
555 Arrays.asList(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT,
556 moduleName, webCacheArtifact.getArtifactID())));
558 artifactsAdded.add(associatedObjectArtifact);
569 private void findExternalFiles(String cachePath)
throws TskCoreException {
571 List<AbstractFile> effFiles = fileManager.findFiles(dataSource,
"f_%", cachePath);
572 for (AbstractFile abstractFile : effFiles ) {
573 String cacheKey = cachePath + abstractFile.getName();
574 if (cachePath.equals(abstractFile.getParentPath()) && abstractFile.isFile()) {
576 if (abstractFile.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC)
577 || !externalFilesTable.containsKey(cacheKey)) {
578 this.externalFilesTable.put(cacheKey, abstractFile);
591 private Optional<AbstractFile> findAbstractFile(String cacheFileName, String cacheFolderName)
throws TskCoreException {
594 String fileTableKey = cacheFolderName + cacheFileName;
596 if (cacheFileName != null) {
597 if (cacheFileName.startsWith(
"f_") && externalFilesTable.containsKey(fileTableKey)) {
598 return Optional.of(externalFilesTable.get(fileTableKey));
601 return Optional.empty();
604 if (fileCopyCache.containsKey(fileTableKey)) {
605 return Optional.of(fileCopyCache.get(fileTableKey).getAbstractFile());
608 List<AbstractFile> cacheFiles = currentCase.getSleuthkitCase().getFileManager().findFilesExactNameExactPath(dataSource,
609 cacheFileName, cacheFolderName);
610 if (!cacheFiles.isEmpty()) {
615 Collections.sort(cacheFiles,
new Comparator<AbstractFile>() {
617 public int compare(AbstractFile file1, AbstractFile file2) {
619 if (file1.getUniquePath().trim().endsWith(DEFAULT_CACHE_PATH_STR)
620 && ! file2.getUniquePath().trim().endsWith(DEFAULT_CACHE_PATH_STR)) {
624 if (file2.getUniquePath().trim().endsWith(DEFAULT_CACHE_PATH_STR)
625 && ! file1.getUniquePath().trim().endsWith(DEFAULT_CACHE_PATH_STR)) {
628 }
catch (TskCoreException ex) {
629 logger.log(Level.WARNING,
"Error getting unique path for file with ID " + file1.getId() +
" or " + file2.getId(), ex);
632 if (file1.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC)
633 && ! file2.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC)) {
636 if (file2.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC)
637 && ! file1.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC)) {
641 return Long.compare(file1.getId(), file2.getId());
646 return Optional.of(cacheFiles.get(0));
649 return Optional.empty();
659 private List<AbstractFile> findIndexFiles() throws TskCoreException {
660 return fileManager.findFiles(dataSource,
"index", DEFAULT_CACHE_PATH_STR);
676 private Optional<FileWrapper> findDataOrIndexFile(String cacheFileName, String cacheFolderName)
throws TskCoreException, IngestModuleException {
679 String fileTableKey = cacheFolderName + cacheFileName;
680 if (fileCopyCache.containsKey(fileTableKey)) {
681 return Optional.of(fileCopyCache.get(fileTableKey));
685 Optional<AbstractFile> abstractFileOptional = findAbstractFile(cacheFileName, cacheFolderName);
686 if (!abstractFileOptional.isPresent()) {
687 return Optional.empty();
695 AbstractFile cacheFile = abstractFileOptional.get();
696 RandomAccessFile randomAccessFile = null;
697 String tempFilePathname = RAImageIngestModule.getRATempPath(currentCase, moduleName, context.getJobId()) + cacheFolderName + cacheFile.getName();
699 File newFile =
new File(tempFilePathname);
700 ContentUtils.writeToFile(cacheFile, newFile, context::dataSourceIngestIsCancelled);
702 randomAccessFile =
new RandomAccessFile(tempFilePathname,
"r");
703 FileChannel roChannel = randomAccessFile.getChannel();
704 ByteBuffer cacheFileROBuf = roChannel.map(FileChannel.MapMode.READ_ONLY, 0,
705 (
int) roChannel.size());
707 cacheFileROBuf.order(ByteOrder.nativeOrder());
708 FileWrapper cacheFileWrapper =
new FileWrapper(cacheFile, randomAccessFile, cacheFileROBuf );
710 if (!cacheFileName.startsWith(
"f_")) {
711 fileCopyCache.put(cacheFolderName + cacheFileName, cacheFileWrapper);
714 return Optional.of(cacheFileWrapper);
716 catch (IOException ex) {
719 if (randomAccessFile != null) {
720 randomAccessFile.close();
723 catch (IOException ex2) {
724 logger.log(Level.SEVERE,
"Error while trying to close temp file after exception.", ex2);
726 String msg = String.format(
"Error reading/copying Chrome cache file '%s' (id=%d).",
727 cacheFile.getName(), cacheFile.getId());
728 throw new IngestModuleException(msg, ex);
735 final class IndexFileHeader {
737 private final long magic;
738 private final int version;
739 private final int numEntries;
740 private final int numBytes;
741 private final int lastFile;
742 private final int tableLen;
744 IndexFileHeader(ByteBuffer indexFileROBuf) {
746 magic = indexFileROBuf.getInt() & UINT32_MASK;
748 indexFileROBuf.position(indexFileROBuf.position()+2);
750 version = indexFileROBuf.getShort();
751 numEntries = indexFileROBuf.getInt();
752 numBytes = indexFileROBuf.getInt();
753 lastFile = indexFileROBuf.getInt();
755 indexFileROBuf.position(indexFileROBuf.position()+4);
756 indexFileROBuf.position(indexFileROBuf.position()+4);
758 tableLen = indexFileROBuf.getInt();
761 public long getMagic() {
765 public int getVersion() {
769 public int getNumEntries() {
773 public int getNumBytes() {
777 public int getLastFile() {
781 public int getTableLen() {
786 public String toString() {
787 StringBuilder sb =
new StringBuilder();
789 sb.append(String.format(
"Index Header:"))
790 .append(String.format(
"\tMagic = %x" , getMagic()) )
791 .append(String.format(
"\tVersion = %x" , getVersion()) )
792 .append(String.format(
"\tNumEntries = %x" , getNumEntries()) )
793 .append(String.format(
"\tNumBytes = %x" , getNumBytes()) )
794 .append(String.format(
"\tLastFile = %x" , getLastFile()) )
795 .append(String.format(
"\tTableLen = %x" , getTableLen()) );
797 return sb.toString();
804 enum CacheFileTypeEnum {
839 final class CacheAddress {
841 private static final long ADDR_INITIALIZED_MASK = 0x80000000l;
842 private static final long FILE_TYPE_MASK = 0x70000000;
843 private static final long FILE_TYPE_OFFSET = 28;
844 private static final long NUM_BLOCKS_MASK = 0x03000000;
845 private static final long NUM_BLOCKS_OFFSET = 24;
846 private static final long FILE_SELECTOR_MASK = 0x00ff0000;
847 private static final long FILE_SELECTOR_OFFSET = 16;
848 private static final long START_BLOCK_MASK = 0x0000FFFF;
849 private static final long EXTERNAL_FILE_NAME_MASK = 0x0FFFFFFF;
851 private final long uint32CacheAddr;
852 private final CacheFileTypeEnum fileType;
853 private final int numBlocks;
854 private final int startBlock;
855 private final String fileName;
856 private final int fileNumber;
858 private final String cachePath;
866 CacheAddress(
long uint32, String cachePath) {
868 uint32CacheAddr = uint32;
869 this.cachePath = cachePath;
873 int fileTypeEnc = (int)(uint32CacheAddr & FILE_TYPE_MASK) >> FILE_TYPE_OFFSET;
874 fileType = CacheFileTypeEnum.values()[fileTypeEnc];
876 if (isInitialized()) {
877 if (isInExternalFile()) {
878 fileNumber = (int)(uint32CacheAddr & EXTERNAL_FILE_NAME_MASK);
879 fileName = String.format(
"f_%06x", getFileNumber() );
883 fileNumber = (int)((uint32CacheAddr & FILE_SELECTOR_MASK) >> FILE_SELECTOR_OFFSET);
884 fileName = String.format(
"data_%d", getFileNumber() );
885 numBlocks = (int)(uint32CacheAddr & NUM_BLOCKS_MASK >> NUM_BLOCKS_OFFSET);
886 startBlock = (int)(uint32CacheAddr & START_BLOCK_MASK);
897 boolean isInitialized() {
898 return ((uint32CacheAddr & ADDR_INITIALIZED_MASK) != 0);
901 CacheFileTypeEnum getFileType() {
909 String getFilename() {
913 String getCachePath() {
917 boolean isInExternalFile() {
918 return (fileType == CacheFileTypeEnum.EXTERNAL);
921 int getFileNumber() {
925 int getStartBlock() {
954 public long getUint32CacheAddr() {
955 return uint32CacheAddr;
959 public String toString() {
960 StringBuilder sb =
new StringBuilder();
961 sb.append(String.format(
"CacheAddr %08x : %s : filename %s",
963 isInitialized() ?
"Initialized" :
"UnInitialized",
966 if ((fileType == CacheFileTypeEnum.BLOCK_256) ||
967 (fileType == CacheFileTypeEnum.BLOCK_1K) ||
968 (fileType == CacheFileTypeEnum.BLOCK_4K) ) {
969 sb.append(String.format(
" (%d blocks starting at %08X)",
975 return sb.toString();
983 enum CacheDataTypeEnum {
997 final class CacheDataSegment {
1000 private final CacheAddress cacheAddress;
1001 private CacheDataTypeEnum type;
1003 private boolean isHTTPHeaderHint;
1005 private FileWrapper cacheFileCopy = null;
1006 private byte[] data = null;
1008 private String httpResponse;
1009 private final Map<String, String> httpHeaders =
new HashMap<>();
1011 CacheDataSegment(CacheAddress cacheAddress,
int len) {
1012 this(cacheAddress, len,
false);
1015 CacheDataSegment(CacheAddress cacheAddress,
int len,
boolean isHTTPHeader ) {
1016 this.type = CacheDataTypeEnum.UNKNOWN;
1018 this.cacheAddress = cacheAddress;
1019 this.isHTTPHeaderHint = isHTTPHeader;
1022 boolean isInExternalFile() {
1023 return cacheAddress.isInExternalFile();
1026 boolean hasHTTPHeaders() {
1027 return this.type == CacheDataTypeEnum.HTTP_HEADER;
1030 String getHTTPHeader(String key) {
1031 return this.httpHeaders.get(key);
1039 String getHTTPHeaders() {
1040 if (!hasHTTPHeaders()) {
1044 StringBuilder sb =
new StringBuilder();
1045 httpHeaders.entrySet().forEach((entry) -> {
1046 if (sb.length() > 0) {
1049 sb.append(String.format(
"%s : %s",
1050 entry.getKey(), entry.getValue()));
1053 return sb.toString();
1056 String getHTTPRespone() {
1057 return httpResponse;
1065 void extract() throws TskCoreException, IngestModuleException {
1073 if (!cacheAddress.isInExternalFile()) {
1075 if (cacheAddress.getFilename() == null) {
1076 throw new TskCoreException(
"Cache address has no file name");
1079 cacheFileCopy = findDataOrIndexFile(cacheAddress.getFilename(), cacheAddress.getCachePath()).
get();
1081 this.data =
new byte [length];
1082 ByteBuffer buf = cacheFileCopy.getByteBuffer();
1083 int dataOffset = DATAFILE_HDR_SIZE + cacheAddress.getStartBlock() * cacheAddress.getBlockSize();
1084 if (dataOffset > buf.capacity()) {
1087 buf.position(dataOffset);
1088 buf.get(data, 0, length);
1091 if ((isHTTPHeaderHint)) {
1092 String strData =
new String(data);
1093 if (strData.contains(
"HTTP")) {
1104 type = CacheDataTypeEnum.HTTP_HEADER;
1106 int startOff = strData.indexOf(
"HTTP");
1107 Charset charset = Charset.forName(
"UTF-8");
1108 boolean done =
false;
1115 while (i < data.length && data[i] != 0) {
1120 if (i == data.length || data[i+1] == 0) {
1124 int len = (i - start);
1125 String headerLine =
new String(data, start, len, charset);
1129 httpResponse = headerLine;
1131 int nPos = headerLine.indexOf(
':');
1133 String key = headerLine.substring(0, nPos);
1134 String val= headerLine.substring(nPos+1);
1135 httpHeaders.put(key.toLowerCase(), val);
1147 String getDataString() throws TskCoreException, IngestModuleException {
1151 return new String(data);
1154 byte[] getDataBytes() throws TskCoreException, IngestModuleException {
1158 return data.clone();
1161 int getDataLength() {
1165 CacheDataTypeEnum getType() {
1169 CacheAddress getCacheAddress() {
1170 return cacheAddress;
1182 String save() throws TskCoreException, IngestModuleException {
1185 if (cacheAddress.isInExternalFile()) {
1186 fileName = cacheAddress.getFilename();
1188 fileName = String.format(
"%s__%08x", cacheAddress.getFilename(), cacheAddress.getUint32CacheAddr());
1191 String filePathName = getAbsOutputFolderName() + cacheAddress.getCachePath() + fileName;
1206 void save(String filePathName)
throws TskCoreException, IngestModuleException {
1214 if (!this.isInExternalFile()) {
1216 try (FileOutputStream stream =
new FileOutputStream(filePathName)) {
1218 }
catch (IOException ex) {
1219 throw new TskCoreException(String.format(
"Failed to write output file %s", filePathName), ex);
1225 public String toString() {
1226 StringBuilder strBuilder =
new StringBuilder();
1227 strBuilder.append(String.format(
"\t\tData type = : %s, Data Len = %d ",
1228 this.type.toString(), this.length ));
1230 if (hasHTTPHeaders()) {
1231 String str = getHTTPHeader(
"content-encoding");
1233 strBuilder.append(String.format(
"\t%s=%s",
"content-encoding", str ));
1237 return strBuilder.toString();
1246 enum EntryStateEnum {
1285 final class CacheEntry {
1288 private static final int MAX_KEY_LEN = 256-24*4;
1290 private final CacheAddress selfAddress;
1291 private final FileWrapper cacheFileCopy;
1293 private final long hash;
1294 private final CacheAddress nextAddress;
1295 private final CacheAddress rankingsNodeAddress;
1297 private final int reuseCount;
1298 private final int refetchCount;
1299 private final EntryStateEnum state;
1301 private final long creationTime;
1302 private final int keyLen;
1304 private final CacheAddress longKeyAddresses;
1306 private final int[] dataSegmentSizes;
1307 private final CacheAddress[] dataSegmentIndexFileEntries;
1308 private List<CacheDataSegment> dataSegments;
1310 private final long flags;
1314 CacheEntry(CacheAddress cacheAdress, FileWrapper cacheFileCopy )
throws TskCoreException, IngestModuleException {
1315 this.selfAddress = cacheAdress;
1316 this.cacheFileCopy = cacheFileCopy;
1318 ByteBuffer fileROBuf = cacheFileCopy.getByteBuffer();
1320 int entryOffset = DATAFILE_HDR_SIZE + cacheAdress.getStartBlock() * cacheAdress.getBlockSize();
1323 if (entryOffset < fileROBuf.capacity()) {
1324 fileROBuf.position(entryOffset);
1326 throw new IngestModuleException(
"Position seeked in Buffer to big");
1329 hash = fileROBuf.getInt() & UINT32_MASK;
1331 long uint32 = fileROBuf.getInt() & UINT32_MASK;
1332 nextAddress = (uint32 != 0) ?
new CacheAddress(uint32, selfAddress.getCachePath()) : null;
1334 uint32 = fileROBuf.getInt() & UINT32_MASK;
1335 rankingsNodeAddress = (uint32 != 0) ?
new CacheAddress(uint32, selfAddress.getCachePath()) : null;
1337 reuseCount = fileROBuf.getInt();
1338 refetchCount = fileROBuf.getInt();
1340 int stateVal = fileROBuf.getInt();
1341 if ((stateVal >= 0) && (stateVal < EntryStateEnum.values().length)) {
1342 state = EntryStateEnum.values()[stateVal];
1344 throw new TskCoreException(
"Invalid EntryStateEnum value");
1346 creationTime = (fileROBuf.getLong() / 1000000) - Long.valueOf(
"11644473600");
1348 keyLen = fileROBuf.getInt();
1350 uint32 = fileROBuf.getInt() & UINT32_MASK;
1351 longKeyAddresses = (uint32 != 0) ?
new CacheAddress(uint32, selfAddress.getCachePath()) : null;
1353 dataSegments = null;
1354 dataSegmentSizes=
new int[4];
1355 for (
int i = 0; i < 4; i++) {
1356 dataSegmentSizes[i] = fileROBuf.getInt();
1358 dataSegmentIndexFileEntries =
new CacheAddress[4];
1359 for (
int i = 0; i < 4; i++) {
1360 dataSegmentIndexFileEntries[i] =
new CacheAddress(fileROBuf.getInt() & UINT32_MASK, selfAddress.getCachePath());
1363 flags = fileROBuf.getInt() & UINT32_MASK;
1365 for (
int i = 0; i < 4; i++) {
1373 if (longKeyAddresses != null) {
1376 if (longKeyAddresses.getFilename() != null) {
1377 CacheDataSegment data =
new CacheDataSegment(longKeyAddresses, this.keyLen,
true);
1378 key = data.getDataString();
1380 }
catch (TskCoreException | IngestModuleException ex) {
1381 throw new TskCoreException(String.format(
"Failed to get external key from address %s", longKeyAddresses));
1385 StringBuilder strBuilder =
new StringBuilder(MAX_KEY_LEN);
1387 while (fileROBuf.remaining() > 0 && keyLen < MAX_KEY_LEN) {
1388 char keyChar = (char)fileROBuf.get();
1389 if (keyChar ==
'\0') {
1392 strBuilder.append(keyChar);
1396 key = strBuilder.toString();
1400 public CacheAddress getCacheAddress() {
1404 public long getHash() {
1408 public CacheAddress getNextCacheAddress() {
1412 public int getReuseCount() {
1416 public int getRefetchCount() {
1417 return refetchCount;
1420 public EntryStateEnum getState() {
1424 public long getCreationTime() {
1425 return creationTime;
1428 public long getFlags() {
1432 public String getKey() {
1444 public List<CacheDataSegment> getDataSegments() throws TskCoreException, IngestModuleException {
1446 if (dataSegments == null) {
1447 dataSegments =
new ArrayList<>();
1448 for (
int i = 0; i < 4; i++) {
1449 if (dataSegmentSizes[i] > 0) {
1450 CacheDataSegment cacheData =
new CacheDataSegment(dataSegmentIndexFileEntries[i], dataSegmentSizes[i],
true );
1452 cacheData.extract();
1453 dataSegments.add(cacheData);
1457 return dataSegments;
1467 boolean hasHTTPHeaders() {
1468 if ((dataSegments == null) || dataSegments.isEmpty()) {
1471 return dataSegments.get(0).hasHTTPHeaders();
1480 String getHTTPHeader(String key) {
1481 if ((dataSegments == null) || dataSegments.isEmpty()) {
1485 return dataSegments.get(0).getHTTPHeader(key);
1493 String getHTTPHeaders() {
1494 if ((dataSegments == null) || dataSegments.isEmpty()) {
1498 return dataSegments.get(0).getHTTPHeaders();
1509 boolean isBrotliCompressed() {
1511 if (hasHTTPHeaders() ) {
1512 String encodingHeader = getHTTPHeader(
"content-encoding");
1513 if (encodingHeader!= null) {
1514 return encodingHeader.trim().equalsIgnoreCase(
"br");
1522 public String toString() {
1523 StringBuilder sb =
new StringBuilder();
1524 sb.append(String.format(
"Entry = Hash: %08x, State: %s, ReuseCount: %d, RefetchCount: %d",
1525 this.hash,
this.state.toString(), this.reuseCount, this.refetchCount ))
1526 .append(String.format(
"\n\tKey: %s, Keylen: %d",
1527 this.key,
this.keyLen,
this.reuseCount,
this.refetchCount ))
1528 .append(String.format(
"\n\tCreationTime: %s",
1529 TimeUtilities.epochToTime(
this.creationTime) ))
1530 .append(String.format(
"\n\tNext Address: %s",
1531 (nextAddress != null) ? nextAddress.toString() :
"None"));
1533 for (
int i = 0; i < 4; i++) {
1534 if (dataSegmentSizes[i] > 0) {
1535 sb.append(String.format(
"\n\tData %d: cache address = %s, Data = %s",
1536 i, dataSegmentIndexFileEntries[i].toString(),
1537 (dataSegments != null)
1538 ? dataSegments.get(i).toString()
1539 :
"Data not retrived yet."));
1543 return sb.toString();