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.Collection;
35 import java.util.HashMap;
36 import java.util.List;
38 import java.util.Map.Entry;
39 import java.util.Optional;
40 import java.util.logging.Level;
41 import org.openide.util.NbBundle;
42 import org.openide.util.NbBundle.Messages;
56 import org.
sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
89 final class ChromeCacheExtractor {
91 private final static String DEFAULT_CACHE_PATH_STR =
"default/cache";
92 private final static String BROTLI_MIMETYPE =
"application/x-brotli";
94 private final static long UINT32_MASK = 0xFFFFFFFFl;
96 private final static int INDEXFILE_HDR_SIZE = 92*4;
97 private final static int DATAFILE_HDR_SIZE = 8192;
99 private final static Logger logger = Logger.getLogger(ChromeCacheExtractor.class.getName());
101 private static final String VERSION_NUMBER =
"1.0.0";
102 private final String moduleName;
104 private String absOutputFolderName;
105 private String relOutputFolderName;
107 private final Content dataSource;
108 private final IngestJobContext context;
109 private final DataSourceIngestModuleProgress progressBar;
110 private final IngestServices services = IngestServices.getInstance();
111 private Case currentCase;
112 private FileManager fileManager;
115 private final Map<String, FileWrapper> fileCopyCache =
new HashMap<>();
118 private final Map<String, AbstractFile> externalFilesTable =
new HashMap<>();
125 final class FileWrapper {
126 private final AbstractFile abstractFile;
127 private final RandomAccessFile fileCopy;
128 private final ByteBuffer byteBuffer;
130 FileWrapper (AbstractFile abstractFile, RandomAccessFile fileCopy, ByteBuffer buffer ) {
131 this.abstractFile = abstractFile;
132 this.fileCopy = fileCopy;
133 this.byteBuffer = buffer;
136 public RandomAccessFile getFileCopy() {
139 public ByteBuffer getByteBuffer() {
142 AbstractFile getAbstractFile() {
148 "ChromeCacheExtractor.moduleName=ChromeCacheExtractor",
149 "# {0} - module name",
150 "# {1} - row number",
151 "# {2} - table length",
152 "# {3} - cache path",
153 "ChromeCacheExtractor.progressMsg={0}: Extracting cache entry {1} of {2} entries from {3}"
155 ChromeCacheExtractor(Content dataSource, IngestJobContext context, DataSourceIngestModuleProgress progressBar ) {
156 moduleName = Bundle.ChromeCacheExtractor_moduleName();
157 this.dataSource = dataSource;
158 this.context = context;
159 this.progressBar = progressBar;
168 private void moduleInit() throws IngestModuleException {
171 currentCase = Case.getCurrentCaseThrows();
172 fileManager = currentCase.getServices().getFileManager();
175 absOutputFolderName = RAImageIngestModule.getRAOutputPath(currentCase, moduleName);
176 relOutputFolderName = Paths.get( RAImageIngestModule.getRelModuleOutputPath(), moduleName).normalize().toString();
178 File dir =
new File(absOutputFolderName);
179 if (dir.exists() ==
false) {
182 }
catch (NoCurrentCaseException ex) {
183 String msg =
"Failed to get current case.";
184 throw new IngestModuleException(msg, ex);
195 private void resetForNewCacheFolder(String cachePath)
throws IngestModuleException {
197 fileCopyCache.clear();
198 externalFilesTable.clear();
200 String cacheAbsOutputFolderName = this.getAbsOutputFolderName() + cachePath;
201 File outDir =
new File(cacheAbsOutputFolderName);
202 if (outDir.exists() ==
false) {
206 String cacheTempPath = RAImageIngestModule.getRATempPath(currentCase, moduleName) + cachePath;
207 File tempDir =
new File(cacheTempPath);
208 if (tempDir.exists() ==
false) {
219 private void cleanup () {
221 for (Entry<String, FileWrapper> entry : this.fileCopyCache.entrySet()) {
222 Path tempFilePath = Paths.get(RAImageIngestModule.getRATempPath(currentCase, moduleName), entry.getKey() );
224 entry.getValue().getFileCopy().getChannel().close();
225 entry.getValue().getFileCopy().close();
227 File tmpFile = tempFilePath.toFile();
228 if (!tmpFile.delete()) {
229 tmpFile.deleteOnExit();
231 }
catch (IOException ex) {
232 logger.log(Level.WARNING, String.format(
"Failed to delete cache file copy %s", tempFilePath.toString()), ex);
242 private String getAbsOutputFolderName() {
243 return absOutputFolderName;
251 private String getRelOutputFolderName() {
252 return relOutputFolderName;
261 void processCaches() {
265 }
catch (IngestModuleException ex) {
266 String msg =
"Failed to initialize ChromeCacheExtractor.";
267 logger.log(Level.SEVERE, msg, ex);
274 List<AbstractFile> indexFiles = findIndexFiles();
277 for (AbstractFile indexFile: indexFiles) {
279 if (context.dataSourceIngestIsCancelled()) {
283 processCacheFolder(indexFile);
286 }
catch (TskCoreException ex) {
287 String msg =
"Failed to find cache index files";
288 logger.log(Level.WARNING, msg, ex);
293 "ChromeCacheExtract_adding_extracted_files_msg=Chrome Cache: Adding %d extracted files for analysis.",
294 "ChromeCacheExtract_adding_artifacts_msg=Chrome Cache: Adding %d artifacts for analysis.",
295 "ChromeCacheExtract_loading_files_msg=Chrome Cache: Loading files from %s."
304 private void processCacheFolder(AbstractFile indexFile) {
306 String cacheFolderName = indexFile.getParentPath();
307 Optional<FileWrapper> indexFileWrapper;
315 progressBar.progress(String.format(Bundle.ChromeCacheExtract_loading_files_msg(), cacheFolderName));
316 resetForNewCacheFolder(cacheFolderName);
320 indexFileWrapper = findDataOrIndexFile(indexFile.getName(), cacheFolderName);
321 if (!indexFileWrapper.isPresent()) {
322 String msg = String.format(
"Failed to find copy cache index file %s", indexFile.getUniquePath());
323 logger.log(Level.WARNING, msg);
330 for (
int i = 0; i < 4; i ++) {
331 Optional<FileWrapper> dataFile = findDataOrIndexFile(String.format(
"data_%1d",i), cacheFolderName );
332 if (!dataFile.isPresent()) {
339 findExternalFiles(cacheFolderName);
341 }
catch (TskCoreException | IngestModuleException ex) {
342 String msg =
"Failed to find cache files in path " + cacheFolderName;
343 logger.log(Level.WARNING, msg, ex);
351 logger.log(Level.INFO,
"{0}- Now reading Cache index file from path {1}",
new Object[]{moduleName, cacheFolderName });
353 List<AbstractFile> derivedFiles =
new ArrayList<>();
354 Collection<BlackboardArtifact> artifactsAdded =
new ArrayList<>();
356 ByteBuffer indexFileROBuffer = indexFileWrapper.get().getByteBuffer();
357 IndexFileHeader indexHdr =
new IndexFileHeader(indexFileROBuffer);
360 indexFileROBuffer.position(INDEXFILE_HDR_SIZE);
364 for (
int i = 0; i < indexHdr.getTableLen(); i++) {
366 if (context.dataSourceIngestIsCancelled()) {
371 CacheAddress addr =
new CacheAddress(indexFileROBuffer.getInt() & UINT32_MASK, cacheFolderName);
372 if (addr.isInitialized()) {
373 progressBar.progress(NbBundle.getMessage(
this.getClass(),
374 "ChromeCacheExtractor.progressMsg",
375 moduleName, i, indexHdr.getTableLen(), cacheFolderName) );
377 List<DerivedFile> addedFiles = processCacheEntry(addr, artifactsAdded);
378 derivedFiles.addAll(addedFiles);
380 catch (TskCoreException | IngestModuleException ex) {
381 logger.log(Level.WARNING, String.format(
"Failed to get cache entry at address %s", addr), ex);
386 if (context.dataSourceIngestIsCancelled()) {
393 progressBar.progress(String.format(Bundle.ChromeCacheExtract_adding_extracted_files_msg(), derivedFiles.size()));
394 derivedFiles.forEach((derived) -> {
395 services.fireModuleContentEvent(
new ModuleContentEvent(derived));
397 context.addFilesToJob(derivedFiles);
400 progressBar.progress(String.format(Bundle.ChromeCacheExtract_adding_artifacts_msg(), artifactsAdded.size()));
401 Blackboard blackboard = currentCase.getSleuthkitCase().getBlackboard();
403 blackboard.postArtifacts(artifactsAdded, moduleName);
404 }
catch (Blackboard.BlackboardException ex) {
405 logger.log(Level.WARNING, String.format(
"Failed to post cacheIndex artifacts "), ex);
423 private List<DerivedFile> processCacheEntry(CacheAddress cacheAddress, Collection<BlackboardArtifact> artifactsAdded )
throws TskCoreException, IngestModuleException {
425 List<DerivedFile> derivedFiles =
new ArrayList<>();
428 String cacheEntryFileName = cacheAddress.getFilename();
429 String cachePath = cacheAddress.getCachePath();
431 Optional<FileWrapper> cacheEntryFileOptional = findDataOrIndexFile(cacheEntryFileName, cachePath);
432 if (!cacheEntryFileOptional.isPresent()) {
433 String msg = String.format(
"Failed to find data file %s", cacheEntryFileName);
434 throw new IngestModuleException(msg);
438 CacheEntry cacheEntry =
new CacheEntry(cacheAddress, cacheEntryFileOptional.get() );
439 List<CacheDataSegment> dataSegments = cacheEntry.getDataSegments();
443 if (dataSegments.size() < 2) {
446 CacheDataSegment dataSegment = dataSegments.get(1);
449 String segmentFileName = dataSegment.getCacheAddress().getFilename();
450 Optional<AbstractFile> segmentFileAbstractFile = findAbstractFile(segmentFileName, cachePath);
451 if (!segmentFileAbstractFile.isPresent()) {
452 logger.log(Level.WARNING,
"Error finding segment file: " + cachePath +
"/" + segmentFileName);
456 boolean isBrotliCompressed =
false;
457 if (dataSegment.getType() != CacheDataTypeEnum.HTTP_HEADER && cacheEntry.isBrotliCompressed() ) {
458 isBrotliCompressed =
true;
464 AbstractFile cachedItemFile;
466 if (dataSegment.isInExternalFile() ) {
467 cachedItemFile = segmentFileAbstractFile.get();
473 String filename = dataSegment.save();
474 String relPathname = getRelOutputFolderName() + dataSegment.getCacheAddress().getCachePath() + filename;
477 DerivedFile derivedFile = fileManager.addDerivedFile(filename, relPathname,
478 dataSegment.getDataLength(),
479 cacheEntry.getCreationTime(), cacheEntry.getCreationTime(), cacheEntry.getCreationTime(), cacheEntry.getCreationTime(),
481 segmentFileAbstractFile.get(),
486 TskData.EncodingType.NONE);
488 derivedFiles.add(derivedFile);
489 cachedItemFile = derivedFile;
492 addArtifacts(cacheEntry, cacheEntryFileOptional.get().getAbstractFile(), cachedItemFile, artifactsAdded);
495 if (isBrotliCompressed) {
496 cachedItemFile.setMIMEType(BROTLI_MIMETYPE);
497 cachedItemFile.save();
500 }
catch (TskException ex) {
501 logger.log(Level.SEVERE,
"Error while trying to add an artifact", ex);
516 private void addArtifacts(CacheEntry cacheEntry, AbstractFile cacheEntryFile, AbstractFile cachedItemFile, Collection<BlackboardArtifact> artifactsAdded)
throws TskCoreException {
519 BlackboardArtifact webCacheArtifact = cacheEntryFile.newArtifact(ARTIFACT_TYPE.TSK_WEB_CACHE);
520 if (webCacheArtifact != null) {
521 Collection<BlackboardAttribute> webAttr =
new ArrayList<>();
522 webAttr.add(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL,
524 ((cacheEntry.getKey() != null) ? cacheEntry.getKey() :
"")));
525 webAttr.add(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED,
526 moduleName, cacheEntry.getCreationTime()));
527 webAttr.add(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_HEADERS,
528 moduleName, cacheEntry.getHTTPHeaders()));
529 webAttr.add(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH,
530 moduleName, cachedItemFile.getUniquePath()));
531 webAttr.add(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_ID,
532 moduleName, cachedItemFile.getId()));
533 webCacheArtifact.addAttributes(webAttr);
534 artifactsAdded.add(webCacheArtifact);
537 BlackboardArtifact associatedObjectArtifact = cachedItemFile.newArtifact(ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT);
538 if (associatedObjectArtifact != null) {
539 associatedObjectArtifact.addAttribute(
540 new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT,
541 moduleName, webCacheArtifact.getArtifactID()));
542 artifactsAdded.add(associatedObjectArtifact);
555 private void findExternalFiles(String cachePath)
throws TskCoreException {
557 List<AbstractFile> effFiles = fileManager.findFiles(dataSource,
"f_%", cachePath);
558 for (AbstractFile abstractFile : effFiles ) {
559 this.externalFilesTable.put(cachePath + abstractFile.getName(), abstractFile);
570 private Optional<AbstractFile> findAbstractFile(String cacheFileName, String cacheFolderName)
throws TskCoreException {
573 String fileTableKey = cacheFolderName + cacheFileName;
574 if (cacheFileName.startsWith(
"f_") && externalFilesTable.containsKey(fileTableKey)) {
575 return Optional.of(externalFilesTable.get(fileTableKey));
578 if (fileCopyCache.containsKey(fileTableKey)) {
579 return Optional.of(fileCopyCache.get(fileTableKey).getAbstractFile());
583 List<AbstractFile> cacheFiles = fileManager.findFiles(dataSource, cacheFileName, cacheFolderName);
584 if (!cacheFiles.isEmpty()) {
585 for (AbstractFile abstractFile: cacheFiles ) {
586 if (abstractFile.getUniquePath().trim().endsWith(DEFAULT_CACHE_PATH_STR)) {
587 return Optional.of(abstractFile);
590 return Optional.of(cacheFiles.get(0));
593 return Optional.empty();
603 private List<AbstractFile> findIndexFiles() throws TskCoreException {
604 return fileManager.findFiles(dataSource,
"index", DEFAULT_CACHE_PATH_STR);
620 private Optional<FileWrapper> findDataOrIndexFile(String cacheFileName, String cacheFolderName)
throws TskCoreException, IngestModuleException {
623 String fileTableKey = cacheFolderName + cacheFileName;
624 if (fileCopyCache.containsKey(fileTableKey)) {
625 return Optional.of(fileCopyCache.get(fileTableKey));
629 Optional<AbstractFile> abstractFileOptional = findAbstractFile(cacheFileName, cacheFolderName);
630 if (!abstractFileOptional.isPresent()) {
631 return Optional.empty();
639 AbstractFile cacheFile = abstractFileOptional.get();
640 RandomAccessFile randomAccessFile = null;
641 String tempFilePathname = RAImageIngestModule.getRATempPath(currentCase, moduleName) + cacheFolderName + cacheFile.getName();
643 File newFile =
new File(tempFilePathname);
644 ContentUtils.writeToFile(cacheFile, newFile, context::dataSourceIngestIsCancelled);
646 randomAccessFile =
new RandomAccessFile(tempFilePathname,
"r");
647 FileChannel roChannel = randomAccessFile.getChannel();
648 ByteBuffer cacheFileROBuf = roChannel.map(FileChannel.MapMode.READ_ONLY, 0,
649 (
int) roChannel.size());
651 cacheFileROBuf.order(ByteOrder.nativeOrder());
652 FileWrapper cacheFileWrapper =
new FileWrapper(cacheFile, randomAccessFile, cacheFileROBuf );
654 if (!cacheFileName.startsWith(
"f_")) {
655 fileCopyCache.put(cacheFolderName + cacheFileName, cacheFileWrapper);
658 return Optional.of(cacheFileWrapper);
660 catch (IOException ex) {
663 if (randomAccessFile != null) {
664 randomAccessFile.close();
667 catch (IOException ex2) {
668 logger.log(Level.SEVERE,
"Error while trying to close temp file after exception.", ex2);
670 String msg = String.format(
"Error reading/copying Chrome cache file '%s' (id=%d).",
671 cacheFile.getName(), cacheFile.getId());
672 throw new IngestModuleException(msg, ex);
679 final class IndexFileHeader {
681 private final long magic;
682 private final int version;
683 private final int numEntries;
684 private final int numBytes;
685 private final int lastFile;
686 private final int tableLen;
688 IndexFileHeader(ByteBuffer indexFileROBuf) {
690 magic = indexFileROBuf.getInt() & UINT32_MASK;
692 indexFileROBuf.position(indexFileROBuf.position()+2);
694 version = indexFileROBuf.getShort();
695 numEntries = indexFileROBuf.getInt();
696 numBytes = indexFileROBuf.getInt();
697 lastFile = indexFileROBuf.getInt();
699 indexFileROBuf.position(indexFileROBuf.position()+4);
700 indexFileROBuf.position(indexFileROBuf.position()+4);
702 tableLen = indexFileROBuf.getInt();
705 public long getMagic() {
709 public int getVersion() {
713 public int getNumEntries() {
717 public int getNumBytes() {
721 public int getLastFile() {
725 public int getTableLen() {
730 public String toString() {
731 StringBuilder sb =
new StringBuilder();
733 sb.append(String.format(
"Index Header:"))
734 .append(String.format(
"\tMagic = %x" , getMagic()) )
735 .append(String.format(
"\tVersion = %x" , getVersion()) )
736 .append(String.format(
"\tNumEntries = %x" , getNumEntries()) )
737 .append(String.format(
"\tNumBytes = %x" , getNumBytes()) )
738 .append(String.format(
"\tLastFile = %x" , getLastFile()) )
739 .append(String.format(
"\tTableLen = %x" , getTableLen()) );
741 return sb.toString();
748 enum CacheFileTypeEnum {
783 final class CacheAddress {
785 private static final long ADDR_INITIALIZED_MASK = 0x80000000l;
786 private static final long FILE_TYPE_MASK = 0x70000000;
787 private static final long FILE_TYPE_OFFSET = 28;
788 private static final long NUM_BLOCKS_MASK = 0x03000000;
789 private static final long NUM_BLOCKS_OFFSET = 24;
790 private static final long FILE_SELECTOR_MASK = 0x00ff0000;
791 private static final long FILE_SELECTOR_OFFSET = 16;
792 private static final long START_BLOCK_MASK = 0x0000FFFF;
793 private static final long EXTERNAL_FILE_NAME_MASK = 0x0FFFFFFF;
795 private final long uint32CacheAddr;
796 private final CacheFileTypeEnum fileType;
797 private final int numBlocks;
798 private final int startBlock;
799 private final String fileName;
800 private final int fileNumber;
802 private final String cachePath;
810 CacheAddress(
long uint32, String cachePath) {
812 uint32CacheAddr = uint32;
813 this.cachePath = cachePath;
817 int fileTypeEnc = (int)(uint32CacheAddr & FILE_TYPE_MASK) >> FILE_TYPE_OFFSET;
818 fileType = CacheFileTypeEnum.values()[fileTypeEnc];
820 if (isInitialized()) {
821 if (isInExternalFile()) {
822 fileNumber = (int)(uint32CacheAddr & EXTERNAL_FILE_NAME_MASK);
823 fileName = String.format(
"f_%06x", getFileNumber() );
827 fileNumber = (int)((uint32CacheAddr & FILE_SELECTOR_MASK) >> FILE_SELECTOR_OFFSET);
828 fileName = String.format(
"data_%d", getFileNumber() );
829 numBlocks = (int)(uint32CacheAddr & NUM_BLOCKS_MASK >> NUM_BLOCKS_OFFSET);
830 startBlock = (int)(uint32CacheAddr & START_BLOCK_MASK);
841 boolean isInitialized() {
842 return ((uint32CacheAddr & ADDR_INITIALIZED_MASK) != 0);
845 CacheFileTypeEnum getFileType() {
853 String getFilename() {
857 String getCachePath() {
861 boolean isInExternalFile() {
862 return (fileType == CacheFileTypeEnum.EXTERNAL);
865 int getFileNumber() {
869 int getStartBlock() {
898 public long getUint32CacheAddr() {
899 return uint32CacheAddr;
903 public String toString() {
904 StringBuilder sb =
new StringBuilder();
905 sb.append(String.format(
"CacheAddr %08x : %s : filename %s",
907 isInitialized() ?
"Initialized" :
"UnInitialized",
910 if ((fileType == CacheFileTypeEnum.BLOCK_256) ||
911 (fileType == CacheFileTypeEnum.BLOCK_1K) ||
912 (fileType == CacheFileTypeEnum.BLOCK_4K) ) {
913 sb.append(String.format(
" (%d blocks starting at %08X)",
919 return sb.toString();
927 enum CacheDataTypeEnum {
941 final class CacheDataSegment {
944 private final CacheAddress cacheAddress;
945 private CacheDataTypeEnum type;
947 private boolean isHTTPHeaderHint;
949 private FileWrapper cacheFileCopy = null;
950 private byte[] data = null;
952 private String httpResponse;
953 private final Map<String, String> httpHeaders =
new HashMap<>();
955 CacheDataSegment(CacheAddress cacheAddress,
int len) {
956 this(cacheAddress, len,
false);
959 CacheDataSegment(CacheAddress cacheAddress,
int len,
boolean isHTTPHeader ) {
960 this.type = CacheDataTypeEnum.UNKNOWN;
962 this.cacheAddress = cacheAddress;
963 this.isHTTPHeaderHint = isHTTPHeader;
966 boolean isInExternalFile() {
967 return cacheAddress.isInExternalFile();
970 boolean hasHTTPHeaders() {
971 return this.type == CacheDataTypeEnum.HTTP_HEADER;
974 String getHTTPHeader(String key) {
975 return this.httpHeaders.get(key);
983 String getHTTPHeaders() {
984 if (!hasHTTPHeaders()) {
988 StringBuilder sb =
new StringBuilder();
989 httpHeaders.entrySet().forEach((entry) -> {
990 if (sb.length() > 0) {
993 sb.append(String.format(
"%s : %s",
994 entry.getKey(), entry.getValue()));
997 return sb.toString();
1000 String getHTTPRespone() {
1001 return httpResponse;
1009 void extract() throws TskCoreException, IngestModuleException {
1017 if (!cacheAddress.isInExternalFile() ) {
1019 cacheFileCopy = findDataOrIndexFile(cacheAddress.getFilename(), cacheAddress.getCachePath()).
get();
1021 this.data =
new byte [length];
1022 ByteBuffer buf = cacheFileCopy.getByteBuffer();
1023 int dataOffset = DATAFILE_HDR_SIZE + cacheAddress.getStartBlock() * cacheAddress.getBlockSize();
1024 buf.position(dataOffset);
1025 buf.get(data, 0, length);
1028 if ((isHTTPHeaderHint)) {
1029 String strData =
new String(data);
1030 if (strData.contains(
"HTTP")) {
1041 type = CacheDataTypeEnum.HTTP_HEADER;
1043 int startOff = strData.indexOf(
"HTTP");
1044 Charset charset = Charset.forName(
"UTF-8");
1045 boolean done =
false;
1052 while (i < data.length && data[i] != 0) {
1057 if (i == data.length || data[i+1] == 0) {
1061 int len = (i - start);
1062 String headerLine =
new String(data, start, len, charset);
1066 httpResponse = headerLine;
1068 int nPos = headerLine.indexOf(
':');
1070 String key = headerLine.substring(0, nPos);
1071 String val= headerLine.substring(nPos+1);
1072 httpHeaders.put(key.toLowerCase(), val);
1084 String getDataString() throws TskCoreException, IngestModuleException {
1088 return new String(data);
1091 byte[] getDataBytes() throws TskCoreException, IngestModuleException {
1095 return data.clone();
1098 int getDataLength() {
1102 CacheDataTypeEnum getType() {
1106 CacheAddress getCacheAddress() {
1107 return cacheAddress;
1119 String save() throws TskCoreException, IngestModuleException {
1122 if (cacheAddress.isInExternalFile()) {
1123 fileName = cacheAddress.getFilename();
1125 fileName = String.format(
"%s__%08x", cacheAddress.getFilename(), cacheAddress.getUint32CacheAddr());
1128 String filePathName = getAbsOutputFolderName() + cacheAddress.getCachePath() + fileName;
1143 void save(String filePathName)
throws TskCoreException, IngestModuleException {
1151 if (!this.isInExternalFile()) {
1153 try (FileOutputStream stream =
new FileOutputStream(filePathName)) {
1155 }
catch (IOException ex) {
1156 throw new TskCoreException(String.format(
"Failed to write output file %s", filePathName), ex);
1162 public String toString() {
1163 StringBuilder strBuilder =
new StringBuilder();
1164 strBuilder.append(String.format(
"\t\tData type = : %s, Data Len = %d ",
1165 this.type.toString(), this.length ));
1167 if (hasHTTPHeaders()) {
1168 String str = getHTTPHeader(
"content-encoding");
1170 strBuilder.append(String.format(
"\t%s=%s",
"content-encoding", str ));
1174 return strBuilder.toString();
1183 enum EntryStateEnum {
1222 final class CacheEntry {
1225 private static final int MAX_KEY_LEN = 256-24*4;
1227 private final CacheAddress selfAddress;
1228 private final FileWrapper cacheFileCopy;
1230 private final long hash;
1231 private final CacheAddress nextAddress;
1232 private final CacheAddress rankingsNodeAddress;
1234 private final int reuseCount;
1235 private final int refetchCount;
1236 private final EntryStateEnum state;
1238 private final long creationTime;
1239 private final int keyLen;
1241 private final CacheAddress longKeyAddresses;
1243 private final int[] dataSegmentSizes;
1244 private final CacheAddress[] dataSegmentIndexFileEntries;
1245 private List<CacheDataSegment> dataSegments;
1247 private final long flags;
1251 CacheEntry(CacheAddress cacheAdress, FileWrapper cacheFileCopy ) {
1252 this.selfAddress = cacheAdress;
1253 this.cacheFileCopy = cacheFileCopy;
1255 ByteBuffer fileROBuf = cacheFileCopy.getByteBuffer();
1257 int entryOffset = DATAFILE_HDR_SIZE + cacheAdress.getStartBlock() * cacheAdress.getBlockSize();
1260 fileROBuf.position(entryOffset);
1262 hash = fileROBuf.getInt() & UINT32_MASK;
1264 long uint32 = fileROBuf.getInt() & UINT32_MASK;
1265 nextAddress = (uint32 != 0) ?
new CacheAddress(uint32, selfAddress.getCachePath()) : null;
1267 uint32 = fileROBuf.getInt() & UINT32_MASK;
1268 rankingsNodeAddress = (uint32 != 0) ?
new CacheAddress(uint32, selfAddress.getCachePath()) : null;
1270 reuseCount = fileROBuf.getInt();
1271 refetchCount = fileROBuf.getInt();
1273 state = EntryStateEnum.values()[fileROBuf.getInt()];
1274 creationTime = (fileROBuf.getLong() / 1000000) - Long.valueOf(
"11644473600");
1276 keyLen = fileROBuf.getInt();
1278 uint32 = fileROBuf.getInt() & UINT32_MASK;
1279 longKeyAddresses = (uint32 != 0) ?
new CacheAddress(uint32, selfAddress.getCachePath()) : null;
1281 dataSegments = null;
1282 dataSegmentSizes=
new int[4];
1283 for (
int i = 0; i < 4; i++) {
1284 dataSegmentSizes[i] = fileROBuf.getInt();
1286 dataSegmentIndexFileEntries =
new CacheAddress[4];
1287 for (
int i = 0; i < 4; i++) {
1288 dataSegmentIndexFileEntries[i] =
new CacheAddress(fileROBuf.getInt() & UINT32_MASK, selfAddress.getCachePath());
1291 flags = fileROBuf.getInt() & UINT32_MASK;
1293 for (
int i = 0; i < 4; i++) {
1301 if (longKeyAddresses != null) {
1304 CacheDataSegment data =
new CacheDataSegment(longKeyAddresses, this.keyLen,
true);
1305 key = data.getDataString();
1306 }
catch (TskCoreException | IngestModuleException ex) {
1307 logger.log(Level.WARNING, String.format(
"Failed to get external key from address %s", longKeyAddresses));
1311 StringBuilder strBuilder =
new StringBuilder(MAX_KEY_LEN);
1313 while (fileROBuf.remaining() > 0 && keyLen < MAX_KEY_LEN) {
1314 char keyChar = (char)fileROBuf.get();
1315 if (keyChar ==
'\0') {
1318 strBuilder.append(keyChar);
1322 key = strBuilder.toString();
1326 public CacheAddress getCacheAddress() {
1330 public long getHash() {
1334 public CacheAddress getNextCacheAddress() {
1338 public int getReuseCount() {
1342 public int getRefetchCount() {
1343 return refetchCount;
1346 public EntryStateEnum getState() {
1350 public long getCreationTime() {
1351 return creationTime;
1354 public long getFlags() {
1358 public String getKey() {
1370 public List<CacheDataSegment> getDataSegments() throws TskCoreException, IngestModuleException {
1372 if (dataSegments == null) {
1373 dataSegments =
new ArrayList<>();
1374 for (
int i = 0; i < 4; i++) {
1375 if (dataSegmentSizes[i] > 0) {
1376 CacheDataSegment cacheData =
new CacheDataSegment(dataSegmentIndexFileEntries[i], dataSegmentSizes[i],
true );
1378 cacheData.extract();
1379 dataSegments.add(cacheData);
1383 return dataSegments;
1393 boolean hasHTTPHeaders() {
1394 if ((dataSegments == null) || dataSegments.isEmpty()) {
1397 return dataSegments.get(0).hasHTTPHeaders();
1406 String getHTTPHeader(String key) {
1407 if ((dataSegments == null) || dataSegments.isEmpty()) {
1411 return dataSegments.get(0).getHTTPHeader(key);
1419 String getHTTPHeaders() {
1420 if ((dataSegments == null) || dataSegments.isEmpty()) {
1424 return dataSegments.get(0).getHTTPHeaders();
1435 boolean isBrotliCompressed() {
1437 if (hasHTTPHeaders() ) {
1438 String encodingHeader = getHTTPHeader(
"content-encoding");
1439 if (encodingHeader!= null) {
1440 return encodingHeader.trim().equalsIgnoreCase(
"br");
1448 public String toString() {
1449 StringBuilder sb =
new StringBuilder();
1450 sb.append(String.format(
"Entry = Hash: %08x, State: %s, ReuseCount: %d, RefetchCount: %d",
1451 this.hash,
this.state.toString(), this.reuseCount, this.refetchCount ))
1452 .append(String.format(
"\n\tKey: %s, Keylen: %d",
1453 this.key,
this.keyLen,
this.reuseCount,
this.refetchCount ))
1454 .append(String.format(
"\n\tCreationTime: %s",
1455 TimeUtilities.epochToTime(
this.creationTime) ))
1456 .append(String.format(
"\n\tNext Address: %s",
1457 (nextAddress != null) ? nextAddress.toString() :
"None"));
1459 for (
int i = 0; i < 4; i++) {
1460 if (dataSegmentSizes[i] > 0) {
1461 sb.append(String.format(
"\n\tData %d: cache address = %s, Data = %s",
1462 i, dataSegmentIndexFileEntries[i].toString(),
1463 (dataSegments != null)
1464 ? dataSegments.get(i).toString()
1465 :
"Data not retrived yet."));
1469 return sb.toString();