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();
 
  181             absOutputFolderName = RAImageIngestModule.getRAOutputPath(currentCase, moduleName, context.getJobId());
 
  182             relOutputFolderName = Paths.get(RAImageIngestModule.getRelModuleOutputPath(currentCase, moduleName, context.getJobId())).normalize().toString();
 
  184             File dir = 
new File(absOutputFolderName);
 
  185             if (dir.exists() == 
false) {
 
  188         } 
catch (NoCurrentCaseException ex) {
 
  189             String msg = 
"Failed to get current case."; 
 
  190             throw new IngestModuleException(msg, ex);
 
  201     private void resetForNewCacheFolder(String cachePath) 
throws IngestModuleException {
 
  203         fileCopyCache.clear();
 
  204         externalFilesTable.clear();
 
  206         String cacheAbsOutputFolderName = this.getAbsOutputFolderName() + cachePath;
 
  207         File outDir = 
new File(cacheAbsOutputFolderName);
 
  208         if (outDir.exists() == 
false) {
 
  212         String cacheTempPath = RAImageIngestModule.getRATempPath(currentCase, moduleName, context.getJobId()) + cachePath;
 
  213         File tempDir = 
new File(cacheTempPath);
 
  214         if (tempDir.exists() == 
false) {
 
  225     private void cleanup () {
 
  227         for (Entry<String, FileWrapper> entry : this.fileCopyCache.entrySet()) {
 
  228             Path tempFilePath = Paths.get(RAImageIngestModule.getRATempPath(currentCase, moduleName, context.getJobId()), entry.getKey() ); 
 
  230                 entry.getValue().getFileCopy().getChannel().close();
 
  231                 entry.getValue().getFileCopy().close();
 
  233                 File tmpFile = tempFilePath.toFile();
 
  234                 if (!tmpFile.delete()) {
 
  235                     tmpFile.deleteOnExit();
 
  237             } 
catch (IOException ex) {
 
  238                 logger.log(Level.WARNING, String.format(
"Failed to delete cache file copy %s", tempFilePath.toString()), ex); 
 
  248     private String getAbsOutputFolderName() {
 
  249         return absOutputFolderName;
 
  257     private String getRelOutputFolderName() {
 
  258         return relOutputFolderName;
 
  267     void processCaches() {
 
  271         } 
catch (IngestModuleException ex) {
 
  272             String msg = 
"Failed to initialize ChromeCacheExtractor."; 
 
  273             logger.log(Level.SEVERE, msg, ex);
 
  280             List<AbstractFile> indexFiles = findIndexFiles(); 
 
  283             for (AbstractFile indexFile: indexFiles) {  
 
  285                 if (context.dataSourceIngestIsCancelled()) {
 
  289                 if (indexFile.getSize() > 0) {
 
  290                     processCacheFolder(indexFile);
 
  294         } 
catch (TskCoreException ex) {
 
  295                 String msg = 
"Failed to find cache index files"; 
 
  296                 logger.log(Level.WARNING, msg, ex);
 
  301         "ChromeCacheExtract_adding_extracted_files_msg=Chrome Cache: Adding %d extracted files for analysis.",
 
  302         "ChromeCacheExtract_adding_artifacts_msg=Chrome Cache: Adding %d artifacts for analysis.",
 
  303         "ChromeCacheExtract_loading_files_msg=Chrome Cache: Loading files from %s." 
  312     private void processCacheFolder(AbstractFile indexFile) {
 
  314         String cacheFolderName = indexFile.getParentPath();
 
  315         Optional<FileWrapper> indexFileWrapper;
 
  323             progressBar.progress(String.format(Bundle.ChromeCacheExtract_loading_files_msg(), cacheFolderName));
 
  324             resetForNewCacheFolder(cacheFolderName);
 
  328             indexFileWrapper = findDataOrIndexFile(indexFile.getName(), cacheFolderName);
 
  329             if (!indexFileWrapper.isPresent()) {
 
  330                 String msg = String.format(
"Failed to find copy cache index file %s", indexFile.getUniquePath());
 
  331                 logger.log(Level.WARNING, msg);
 
  338             for (
int i = 0; i < 4; i ++)  {
 
  339                 Optional<FileWrapper> dataFile = findDataOrIndexFile(String.format(
"data_%1d",i), cacheFolderName );
 
  340                 if (!dataFile.isPresent()) {
 
  347             findExternalFiles(cacheFolderName);
 
  349         } 
catch (TskCoreException | IngestModuleException ex) {
 
  350             String msg = 
"Failed to find cache files in path " + cacheFolderName; 
 
  351             logger.log(Level.WARNING, msg, ex);
 
  359         logger.log(Level.INFO, 
"{0}- Now reading Cache index file from path {1}", 
new Object[]{moduleName, cacheFolderName }); 
 
  361         List<AbstractFile> derivedFiles = 
new ArrayList<>();
 
  362         Collection<BlackboardArtifact> artifactsAdded = 
new ArrayList<>();
 
  364         ByteBuffer indexFileROBuffer = indexFileWrapper.get().getByteBuffer();
 
  365         IndexFileHeader indexHdr = 
new IndexFileHeader(indexFileROBuffer);
 
  368         indexFileROBuffer.position(INDEXFILE_HDR_SIZE);
 
  373             for (
int i = 0; i <  indexHdr.getTableLen(); i++) {
 
  375                 if (context.dataSourceIngestIsCancelled()) {
 
  380                 CacheAddress addr = 
new CacheAddress(indexFileROBuffer.getInt() & UINT32_MASK, cacheFolderName);
 
  381                 if (addr.isInitialized()) {
 
  382                     progressBar.progress(NbBundle.getMessage(
this.getClass(),
 
  383                                             "ChromeCacheExtractor.progressMsg",
 
  384                                             moduleName, i, indexHdr.getTableLen(), cacheFolderName)  );
 
  386                         List<DerivedFile> addedFiles = processCacheEntry(addr, artifactsAdded);
 
  387                         derivedFiles.addAll(addedFiles);
 
  389                     catch (TskCoreException | IngestModuleException ex) {
 
  390                        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())); 
 
  394         } 
catch (java.nio.BufferUnderflowException ex) {
 
  395             logger.log(Level.WARNING, String.format(
"Ran out of data unexpectedly reading file %s (ObjID: %d)", indexFile.getName(), indexFile.getId()));
 
  398         if (context.dataSourceIngestIsCancelled()) {
 
  405         progressBar.progress(String.format(Bundle.ChromeCacheExtract_adding_extracted_files_msg(), derivedFiles.size()));
 
  406         derivedFiles.forEach((derived) -> {
 
  407             services.fireModuleContentEvent(
new ModuleContentEvent(derived));
 
  409         context.addFilesToJob(derivedFiles);
 
  412         progressBar.progress(String.format(Bundle.ChromeCacheExtract_adding_artifacts_msg(), artifactsAdded.size()));
 
  413         Blackboard blackboard = currentCase.getSleuthkitCase().getBlackboard();
 
  415             blackboard.postArtifacts(artifactsAdded, moduleName);
 
  416         } 
catch (Blackboard.BlackboardException ex) {
 
  417            logger.log(Level.WARNING, String.format(
"Failed to post cacheIndex artifacts "), ex); 
 
  435     private List<DerivedFile> processCacheEntry(CacheAddress cacheAddress, Collection<BlackboardArtifact> artifactsAdded ) 
throws TskCoreException, IngestModuleException {
 
  437         List<DerivedFile> derivedFiles = 
new ArrayList<>();
 
  440         String cacheEntryFileName = cacheAddress.getFilename(); 
 
  441         String cachePath = cacheAddress.getCachePath();
 
  443         Optional<FileWrapper> cacheEntryFileOptional = findDataOrIndexFile(cacheEntryFileName, cachePath);
 
  444         if (!cacheEntryFileOptional.isPresent()) {
 
  445             String msg = String.format(
"Failed to find data file %s", cacheEntryFileName); 
 
  446             throw new IngestModuleException(msg);
 
  450         CacheEntry cacheEntry = 
new CacheEntry(cacheAddress, cacheEntryFileOptional.get() );
 
  451         List<CacheDataSegment> dataSegments = cacheEntry.getDataSegments();
 
  455         if (dataSegments.size() < 2) {
 
  458         CacheDataSegment dataSegment = dataSegments.get(1);
 
  461         String segmentFileName = dataSegment.getCacheAddress().getFilename();
 
  462         Optional<AbstractFile> segmentFileAbstractFile = findAbstractFile(segmentFileName, cachePath);
 
  463         if (!segmentFileAbstractFile.isPresent()) {
 
  464             logger.log(Level.WARNING, 
"Error finding segment file: " + cachePath + 
"/" + segmentFileName); 
 
  468         boolean isBrotliCompressed = 
false;
 
  469         if (dataSegment.getType() != CacheDataTypeEnum.HTTP_HEADER && cacheEntry.isBrotliCompressed() ) {
 
  470             isBrotliCompressed = 
true;
 
  476             AbstractFile cachedItemFile; 
 
  478             if (dataSegment.isInExternalFile() )  {
 
  479                 cachedItemFile = segmentFileAbstractFile.get();
 
  485                 String filename = dataSegment.save();
 
  486                 String relPathname = getRelOutputFolderName() + dataSegment.getCacheAddress().getCachePath() + filename; 
 
  489                 DerivedFile derivedFile = fileManager.addDerivedFile(filename, relPathname,
 
  490                                                     dataSegment.getDataLength(), 
 
  491                                                     cacheEntry.getCreationTime(), cacheEntry.getCreationTime(), cacheEntry.getCreationTime(), cacheEntry.getCreationTime(), 
 
  493                                                     segmentFileAbstractFile.get(), 
 
  498                                                     TskData.EncodingType.NONE);
 
  500                 derivedFiles.add(derivedFile);
 
  501                 cachedItemFile = derivedFile;
 
  504             addArtifacts(cacheEntry, cacheEntryFileOptional.get().getAbstractFile(), cachedItemFile, artifactsAdded);
 
  507             if (isBrotliCompressed) {
 
  508                 cachedItemFile.setMIMEType(BROTLI_MIMETYPE);
 
  509                 cachedItemFile.save();
 
  512         } 
catch (TskException ex) {
 
  513             logger.log(Level.SEVERE, 
"Error while trying to add an artifact", ex); 
 
  528     private void addArtifacts(CacheEntry cacheEntry, AbstractFile cacheEntryFile, AbstractFile cachedItemFile, Collection<BlackboardArtifact> artifactsAdded) 
throws TskCoreException {
 
  531         Collection<BlackboardAttribute> webAttr = 
new ArrayList<>();
 
  532         String url = cacheEntry.getKey() != null ? cacheEntry.getKey() : 
"";
 
  533         webAttr.add(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL,
 
  535         webAttr.add(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN,
 
  536                 moduleName, NetworkUtils.extractDomain(url)));
 
  537         webAttr.add(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED,
 
  538                 moduleName, cacheEntry.getCreationTime()));
 
  539         webAttr.add(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_HEADERS,
 
  540                 moduleName, cacheEntry.getHTTPHeaders()));  
 
  541         webAttr.add(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH,
 
  542                 moduleName, cachedItemFile.getUniquePath()));
 
  543         webAttr.add(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_ID,
 
  544                 moduleName, cachedItemFile.getId()));
 
  546         BlackboardArtifact webCacheArtifact = cacheEntryFile.newDataArtifact(
new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_WEB_CACHE), webAttr);
 
  547         artifactsAdded.add(webCacheArtifact);
 
  550         BlackboardArtifact associatedObjectArtifact = cachedItemFile.newDataArtifact(
 
  551                 new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT), 
 
  552                 Arrays.asList(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT, 
 
  553                         moduleName, webCacheArtifact.getArtifactID())));
 
  555         artifactsAdded.add(associatedObjectArtifact);
 
  566     private void findExternalFiles(String cachePath) 
throws TskCoreException {
 
  568         List<AbstractFile> effFiles = fileManager.findFiles(dataSource, 
"f_%", cachePath); 
 
  569         for (AbstractFile abstractFile : effFiles ) {
 
  570             String cacheKey = cachePath + abstractFile.getName();
 
  571             if (cachePath.equals(abstractFile.getParentPath()) && abstractFile.isFile()) {
 
  573                 if (abstractFile.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC)
 
  574                         || !externalFilesTable.containsKey(cacheKey)) {
 
  575                     this.externalFilesTable.put(cacheKey, abstractFile);
 
  588     private Optional<AbstractFile> findAbstractFile(String cacheFileName, String cacheFolderName) 
throws TskCoreException {
 
  591         String fileTableKey = cacheFolderName + cacheFileName;
 
  592         if (cacheFileName.startsWith(
"f_") && externalFilesTable.containsKey(fileTableKey)) {
 
  593             return Optional.of(externalFilesTable.get(fileTableKey));
 
  596         if (fileCopyCache.containsKey(fileTableKey)) {
 
  597             return Optional.of(fileCopyCache.get(fileTableKey).getAbstractFile());
 
  600         List<AbstractFile> cacheFiles = currentCase.getSleuthkitCase().getFileManager().findFilesExactNameExactPath(dataSource, 
 
  601                 cacheFileName, cacheFolderName);
 
  602         if (!cacheFiles.isEmpty()) {
 
  607             Collections.sort(cacheFiles, 
new Comparator<AbstractFile>() {
 
  609                 public int compare(AbstractFile file1, AbstractFile file2) {
 
  611                         if (file1.getUniquePath().trim().endsWith(DEFAULT_CACHE_PATH_STR)
 
  612                                 && ! file2.getUniquePath().trim().endsWith(DEFAULT_CACHE_PATH_STR)) {
 
  616                         if (file2.getUniquePath().trim().endsWith(DEFAULT_CACHE_PATH_STR)
 
  617                                 && ! file1.getUniquePath().trim().endsWith(DEFAULT_CACHE_PATH_STR)) {
 
  620                     } 
catch (TskCoreException ex) {
 
  621                         logger.log(Level.WARNING, 
"Error getting unique path for file with ID " + file1.getId() + 
" or " + file2.getId(), ex);
 
  624                     if (file1.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC)
 
  625                             && ! file2.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC)) {
 
  628                     if (file2.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC)
 
  629                             && ! file1.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC)) {
 
  633                     return Long.compare(file1.getId(), file2.getId());
 
  638             return Optional.of(cacheFiles.get(0));
 
  641         return Optional.empty(); 
 
  651     private List<AbstractFile> findIndexFiles() throws TskCoreException {
 
  652         return fileManager.findFiles(dataSource, 
"index", DEFAULT_CACHE_PATH_STR); 
 
  668     private Optional<FileWrapper> findDataOrIndexFile(String cacheFileName, String cacheFolderName) 
throws TskCoreException, IngestModuleException  {
 
  671         String fileTableKey = cacheFolderName + cacheFileName;
 
  672         if (fileCopyCache.containsKey(fileTableKey)) {
 
  673             return Optional.of(fileCopyCache.get(fileTableKey));
 
  677         Optional<AbstractFile> abstractFileOptional = findAbstractFile(cacheFileName, cacheFolderName);
 
  678         if (!abstractFileOptional.isPresent()) {
 
  679             return Optional.empty(); 
 
  687         AbstractFile cacheFile = abstractFileOptional.get();
 
  688         RandomAccessFile randomAccessFile = null;
 
  689         String tempFilePathname = RAImageIngestModule.getRATempPath(currentCase, moduleName, context.getJobId()) + cacheFolderName + cacheFile.getName(); 
 
  691             File newFile = 
new File(tempFilePathname);
 
  692             ContentUtils.writeToFile(cacheFile, newFile, context::dataSourceIngestIsCancelled);
 
  694             randomAccessFile = 
new RandomAccessFile(tempFilePathname, 
"r");
 
  695             FileChannel roChannel = randomAccessFile.getChannel();
 
  696             ByteBuffer cacheFileROBuf = roChannel.map(FileChannel.MapMode.READ_ONLY, 0,
 
  697                                                         (
int) roChannel.size());
 
  699             cacheFileROBuf.order(ByteOrder.nativeOrder());
 
  700             FileWrapper cacheFileWrapper = 
new FileWrapper(cacheFile, randomAccessFile, cacheFileROBuf );
 
  702             if (!cacheFileName.startsWith(
"f_")) {
 
  703                 fileCopyCache.put(cacheFolderName + cacheFileName, cacheFileWrapper);
 
  706             return Optional.of(cacheFileWrapper);
 
  708         catch (IOException ex) {
 
  711                 if (randomAccessFile != null) {
 
  712                     randomAccessFile.close();
 
  715             catch (IOException ex2) {
 
  716                 logger.log(Level.SEVERE, 
"Error while trying to close temp file after exception.", ex2); 
 
  718             String msg = String.format(
"Error reading/copying Chrome cache file '%s' (id=%d).", 
 
  719                                             cacheFile.getName(), cacheFile.getId()); 
 
  720             throw new IngestModuleException(msg, ex);
 
  727     final class IndexFileHeader {
 
  729         private final long magic;
 
  730         private final int version;
 
  731         private final int numEntries;
 
  732         private final int numBytes;
 
  733         private final int lastFile;
 
  734         private final int tableLen;
 
  736         IndexFileHeader(ByteBuffer indexFileROBuf) {
 
  738             magic = indexFileROBuf.getInt() & UINT32_MASK; 
 
  740             indexFileROBuf.position(indexFileROBuf.position()+2);
 
  742             version = indexFileROBuf.getShort();
 
  743             numEntries = indexFileROBuf.getInt();
 
  744             numBytes = indexFileROBuf.getInt();
 
  745             lastFile = indexFileROBuf.getInt();
 
  747             indexFileROBuf.position(indexFileROBuf.position()+4); 
 
  748             indexFileROBuf.position(indexFileROBuf.position()+4); 
 
  750             tableLen = indexFileROBuf.getInt();
 
  753         public long getMagic() {
 
  757         public int getVersion() {
 
  761         public int getNumEntries() {
 
  765         public int getNumBytes() {
 
  769         public int getLastFile() {
 
  773         public int getTableLen() {
 
  778         public String toString() {
 
  779             StringBuilder sb = 
new StringBuilder();
 
  781             sb.append(String.format(
"Index Header:"))
 
  782                 .append(String.format(
"\tMagic = %x" , getMagic()) )
 
  783                 .append(String.format(
"\tVersion = %x" , getVersion()) )
 
  784                 .append(String.format(
"\tNumEntries = %x" , getNumEntries()) )
 
  785                 .append(String.format(
"\tNumBytes = %x" , getNumBytes()) )
 
  786                 .append(String.format(
"\tLastFile = %x" , getLastFile()) )
 
  787                 .append(String.format(
"\tTableLen = %x" , getTableLen()) );
 
  789             return sb.toString();
 
  796     enum CacheFileTypeEnum {
 
  831     final class CacheAddress {
 
  833         private static final long ADDR_INITIALIZED_MASK    = 0x80000000l;
 
  834         private static final long FILE_TYPE_MASK     = 0x70000000;
 
  835         private static final long FILE_TYPE_OFFSET   = 28;
 
  836         private static final long NUM_BLOCKS_MASK     = 0x03000000;
 
  837         private static final long NUM_BLOCKS_OFFSET   = 24;
 
  838         private static final long FILE_SELECTOR_MASK   = 0x00ff0000;
 
  839         private static final long FILE_SELECTOR_OFFSET = 16;
 
  840         private static final long START_BLOCK_MASK     = 0x0000FFFF;
 
  841         private static final long EXTERNAL_FILE_NAME_MASK = 0x0FFFFFFF;
 
  843         private final long uint32CacheAddr;
 
  844         private final CacheFileTypeEnum fileType;
 
  845         private final int numBlocks;
 
  846         private final int startBlock;
 
  847         private final String fileName;
 
  848         private final int fileNumber;
 
  850         private final String cachePath;
 
  858         CacheAddress(
long uint32, String cachePath) {
 
  860             uint32CacheAddr = uint32;
 
  861             this.cachePath = cachePath;
 
  865             int fileTypeEnc = (int)(uint32CacheAddr &  FILE_TYPE_MASK) >> FILE_TYPE_OFFSET;
 
  866             fileType = CacheFileTypeEnum.values()[fileTypeEnc];
 
  868             if (isInitialized()) {
 
  869                 if (isInExternalFile()) {
 
  870                     fileNumber = (int)(uint32CacheAddr & EXTERNAL_FILE_NAME_MASK);
 
  871                     fileName =  String.format(
"f_%06x", getFileNumber() );
 
  875                     fileNumber = (int)((uint32CacheAddr & FILE_SELECTOR_MASK) >> FILE_SELECTOR_OFFSET);
 
  876                     fileName = String.format(
"data_%d", getFileNumber() );
 
  877                     numBlocks = (int)(uint32CacheAddr &  NUM_BLOCKS_MASK >> NUM_BLOCKS_OFFSET);
 
  878                     startBlock = (int)(uint32CacheAddr &  START_BLOCK_MASK);
 
  889         boolean isInitialized() {
 
  890             return ((uint32CacheAddr & ADDR_INITIALIZED_MASK) != 0);
 
  893         CacheFileTypeEnum getFileType() {
 
  901         String getFilename() {
 
  905         String getCachePath() {
 
  909         boolean isInExternalFile() {
 
  910             return (fileType == CacheFileTypeEnum.EXTERNAL);
 
  913         int getFileNumber() {
 
  917         int getStartBlock() {
 
  946         public long getUint32CacheAddr() {
 
  947             return uint32CacheAddr;
 
  951         public String toString() {
 
  952             StringBuilder sb = 
new StringBuilder();
 
  953             sb.append(String.format(
"CacheAddr %08x : %s : filename %s", 
 
  955                                     isInitialized() ? 
"Initialized" : 
"UnInitialized",
 
  958             if ((fileType == CacheFileTypeEnum.BLOCK_256) || 
 
  959                 (fileType == CacheFileTypeEnum.BLOCK_1K) || 
 
  960                 (fileType == CacheFileTypeEnum.BLOCK_4K) ) {
 
  961                 sb.append(String.format(
" (%d blocks starting at %08X)", 
 
  967             return sb.toString();
 
  975     enum CacheDataTypeEnum {
 
  989     final class CacheDataSegment {
 
  992         private final CacheAddress cacheAddress;
 
  993         private CacheDataTypeEnum type;
 
  995         private boolean isHTTPHeaderHint;
 
  997         private FileWrapper cacheFileCopy = null;
 
  998         private byte[] data = null;
 
 1000         private String httpResponse;
 
 1001         private final Map<String, String> httpHeaders = 
new HashMap<>();
 
 1003         CacheDataSegment(CacheAddress cacheAddress, 
int len) {
 
 1004             this(cacheAddress, len, 
false);
 
 1007         CacheDataSegment(CacheAddress cacheAddress, 
int len, 
boolean isHTTPHeader ) {
 
 1008             this.type = CacheDataTypeEnum.UNKNOWN;
 
 1010             this.cacheAddress = cacheAddress;
 
 1011             this.isHTTPHeaderHint = isHTTPHeader;
 
 1014         boolean isInExternalFile() {
 
 1015             return cacheAddress.isInExternalFile();
 
 1018         boolean hasHTTPHeaders() {
 
 1019             return this.type == CacheDataTypeEnum.HTTP_HEADER;
 
 1022         String getHTTPHeader(String key) {
 
 1023             return this.httpHeaders.get(key);
 
 1031         String getHTTPHeaders() {
 
 1032             if (!hasHTTPHeaders()) {
 
 1036             StringBuilder sb = 
new StringBuilder();
 
 1037             httpHeaders.entrySet().forEach((entry) -> {
 
 1038                 if (sb.length() > 0) {
 
 1041                 sb.append(String.format(
"%s : %s",
 
 1042                         entry.getKey(), entry.getValue()));
 
 1045             return sb.toString();
 
 1048         String getHTTPRespone() {
 
 1049             return httpResponse;
 
 1057         void extract() throws TskCoreException, IngestModuleException {
 
 1065             if (!cacheAddress.isInExternalFile()) {
 
 1067                 if (cacheAddress.getFilename() == null) {
 
 1068                     throw new TskCoreException(
"Cache address has no file name");
 
 1071                 cacheFileCopy = findDataOrIndexFile(cacheAddress.getFilename(), cacheAddress.getCachePath()).
get();
 
 1073                 this.data = 
new byte [length];
 
 1074                 ByteBuffer buf = cacheFileCopy.getByteBuffer();
 
 1075                 int dataOffset = DATAFILE_HDR_SIZE + cacheAddress.getStartBlock() * cacheAddress.getBlockSize();
 
 1076                 if (dataOffset > buf.capacity()) {
 
 1079                 buf.position(dataOffset);
 
 1080                 buf.get(data, 0, length);
 
 1083                 if ((isHTTPHeaderHint)) {
 
 1084                     String strData = 
new String(data);
 
 1085                     if (strData.contains(
"HTTP")) {
 
 1096                         type = CacheDataTypeEnum.HTTP_HEADER;
 
 1098                         int startOff = strData.indexOf(
"HTTP");
 
 1099                         Charset charset = Charset.forName(
"UTF-8");
 
 1100                         boolean done = 
false;
 
 1107                             while (i < data.length && data[i] != 0)  {
 
 1112                             if (i == data.length || data[i+1] == 0) {
 
 1116                             int len = (i - start);
 
 1117                             String headerLine = 
new String(data, start, len, charset);
 
 1121                                 httpResponse = headerLine;
 
 1123                                 int nPos = headerLine.indexOf(
':');
 
 1125                                     String key = headerLine.substring(0, nPos);
 
 1126                                     String val= headerLine.substring(nPos+1);
 
 1127                                     httpHeaders.put(key.toLowerCase(), val);
 
 1139         String getDataString() throws TskCoreException, IngestModuleException {
 
 1143             return new String(data);
 
 1146         byte[] getDataBytes() throws TskCoreException, IngestModuleException {
 
 1150             return data.clone();
 
 1153         int getDataLength() {
 
 1157         CacheDataTypeEnum getType() {
 
 1161         CacheAddress getCacheAddress() {
 
 1162             return cacheAddress;
 
 1174         String save() throws TskCoreException, IngestModuleException {
 
 1177             if (cacheAddress.isInExternalFile()) {
 
 1178                 fileName = cacheAddress.getFilename();
 
 1180                 fileName = String.format(
"%s__%08x", cacheAddress.getFilename(), cacheAddress.getUint32CacheAddr());
 
 1183             String filePathName = getAbsOutputFolderName() + cacheAddress.getCachePath() + fileName;
 
 1198         void save(String filePathName) 
throws TskCoreException, IngestModuleException {
 
 1206             if (!this.isInExternalFile()) {
 
 1208                 try (FileOutputStream stream = 
new FileOutputStream(filePathName)) {
 
 1210                 } 
catch (IOException ex) {
 
 1211                     throw new TskCoreException(String.format(
"Failed to write output file %s", filePathName), ex);
 
 1217         public String toString() {
 
 1218             StringBuilder strBuilder = 
new StringBuilder();
 
 1219             strBuilder.append(String.format(
"\t\tData type = : %s, Data Len = %d ", 
 
 1220                                     this.type.toString(), this.length ));
 
 1222             if (hasHTTPHeaders()) {
 
 1223                 String str = getHTTPHeader(
"content-encoding");
 
 1225                     strBuilder.append(String.format(
"\t%s=%s", 
"content-encoding", str ));
 
 1229             return strBuilder.toString(); 
 
 1238     enum EntryStateEnum {
 
 1277     final class CacheEntry {
 
 1280         private static final int MAX_KEY_LEN = 256-24*4; 
 
 1282         private final CacheAddress selfAddress; 
 
 1283         private final FileWrapper cacheFileCopy;
 
 1285         private final long hash;
 
 1286         private final CacheAddress nextAddress;
 
 1287         private final CacheAddress rankingsNodeAddress;
 
 1289         private final int reuseCount;
 
 1290         private final int refetchCount;
 
 1291         private final EntryStateEnum state;
 
 1293         private final long creationTime;
 
 1294         private final int keyLen;
 
 1296         private final CacheAddress longKeyAddresses; 
 
 1298         private final int[] dataSegmentSizes;
 
 1299         private final CacheAddress[] dataSegmentIndexFileEntries;
 
 1300         private List<CacheDataSegment> dataSegments;
 
 1302         private final long flags;
 
 1306         CacheEntry(CacheAddress cacheAdress, FileWrapper cacheFileCopy ) 
throws TskCoreException {
 
 1307             this.selfAddress = cacheAdress;
 
 1308             this.cacheFileCopy = cacheFileCopy;
 
 1310             ByteBuffer fileROBuf = cacheFileCopy.getByteBuffer();
 
 1312             int entryOffset = DATAFILE_HDR_SIZE + cacheAdress.getStartBlock() * cacheAdress.getBlockSize();
 
 1315             fileROBuf.position(entryOffset);
 
 1317             hash = fileROBuf.getInt() & UINT32_MASK;
 
 1319             long uint32 = fileROBuf.getInt() & UINT32_MASK;
 
 1320             nextAddress = (uint32 != 0) ?  
new CacheAddress(uint32, selfAddress.getCachePath()) : null;  
 
 1322             uint32 = fileROBuf.getInt() & UINT32_MASK;
 
 1323             rankingsNodeAddress = (uint32 != 0) ?  
new CacheAddress(uint32, selfAddress.getCachePath()) : null;  
 
 1325             reuseCount = fileROBuf.getInt();
 
 1326             refetchCount = fileROBuf.getInt();
 
 1328             int stateVal = fileROBuf.getInt();
 
 1329             if ((stateVal >= 0) && (stateVal < EntryStateEnum.values().length)) {
 
 1330                 state = EntryStateEnum.values()[stateVal];
 
 1332                 throw new TskCoreException(
"Invalid EntryStateEnum value"); 
 
 1334             creationTime = (fileROBuf.getLong() / 1000000) - Long.valueOf(
"11644473600");
 
 1336             keyLen = fileROBuf.getInt();
 
 1338             uint32 = fileROBuf.getInt() & UINT32_MASK;
 
 1339             longKeyAddresses = (uint32 != 0) ?  
new CacheAddress(uint32, selfAddress.getCachePath()) : null;  
 
 1341             dataSegments = null;
 
 1342             dataSegmentSizes= 
new int[4];
 
 1343             for (
int i = 0; i < 4; i++)  {
 
 1344                 dataSegmentSizes[i] = fileROBuf.getInt();
 
 1346             dataSegmentIndexFileEntries = 
new CacheAddress[4];
 
 1347             for (
int i = 0; i < 4; i++)  {
 
 1348                 dataSegmentIndexFileEntries[i] =  
new CacheAddress(fileROBuf.getInt() & UINT32_MASK, selfAddress.getCachePath());
 
 1351             flags = fileROBuf.getInt() & UINT32_MASK;
 
 1353             for (
int i = 0; i < 4; i++)  {
 
 1361             if (longKeyAddresses != null) {
 
 1364                     CacheDataSegment data = 
new CacheDataSegment(longKeyAddresses, this.keyLen, 
true);
 
 1365                     key = data.getDataString();
 
 1366                 } 
catch (TskCoreException | IngestModuleException ex) {
 
 1367                     throw new TskCoreException(String.format(
"Failed to get external key from address %s", longKeyAddresses)); 
 
 1371                 StringBuilder strBuilder = 
new StringBuilder(MAX_KEY_LEN);
 
 1373                 while (fileROBuf.remaining() > 0  && keyLen < MAX_KEY_LEN)  {
 
 1374                     char keyChar = (char)fileROBuf.get();
 
 1375                     if (keyChar == 
'\0') { 
 
 1378                     strBuilder.append(keyChar);
 
 1382                 key = strBuilder.toString();
 
 1386         public CacheAddress getCacheAddress() {
 
 1390         public long getHash() {
 
 1394         public CacheAddress getNextCacheAddress() {
 
 1398         public int getReuseCount() {
 
 1402         public int getRefetchCount() {
 
 1403             return refetchCount;
 
 1406         public EntryStateEnum getState() {
 
 1410         public long getCreationTime() {
 
 1411             return creationTime;
 
 1414         public long getFlags() {
 
 1418         public String getKey() {
 
 1430         public List<CacheDataSegment> getDataSegments() throws TskCoreException, IngestModuleException {
 
 1432             if (dataSegments == null) { 
 
 1433                 dataSegments = 
new ArrayList<>();
 
 1434                  for (
int i = 0; i < 4; i++)  {
 
 1435                      if (dataSegmentSizes[i] > 0) {
 
 1436                          CacheDataSegment cacheData = 
new CacheDataSegment(dataSegmentIndexFileEntries[i], dataSegmentSizes[i], 
true );
 
 1438                          cacheData.extract();
 
 1439                          dataSegments.add(cacheData);
 
 1443             return dataSegments; 
 
 1453         boolean hasHTTPHeaders() {
 
 1454             if ((dataSegments == null) || dataSegments.isEmpty()) {
 
 1457             return dataSegments.get(0).hasHTTPHeaders();
 
 1466         String getHTTPHeader(String key) {
 
 1467             if ((dataSegments == null) || dataSegments.isEmpty()) {
 
 1471             return dataSegments.get(0).getHTTPHeader(key);
 
 1479         String getHTTPHeaders() {
 
 1480             if ((dataSegments == null) || dataSegments.isEmpty()) {
 
 1484             return dataSegments.get(0).getHTTPHeaders();
 
 1495         boolean isBrotliCompressed() {
 
 1497             if (hasHTTPHeaders() ) {
 
 1498                 String encodingHeader = getHTTPHeader(
"content-encoding");
 
 1499                 if (encodingHeader!= null) {
 
 1500                     return encodingHeader.trim().equalsIgnoreCase(
"br");
 
 1508         public String toString() {
 
 1509             StringBuilder sb = 
new StringBuilder();
 
 1510             sb.append(String.format(
"Entry = Hash: %08x,  State: %s, ReuseCount: %d, RefetchCount: %d", 
 
 1511                                     this.hash, 
this.state.toString(), this.reuseCount, this.refetchCount ))
 
 1512                 .append(String.format(
"\n\tKey: %s, Keylen: %d", 
 
 1513                                     this.key, 
this.keyLen, 
this.reuseCount, 
this.refetchCount ))
 
 1514                 .append(String.format(
"\n\tCreationTime: %s", 
 
 1515                                     TimeUtilities.epochToTime(
this.creationTime) ))
 
 1516                 .append(String.format(
"\n\tNext Address: %s", 
 
 1517                                     (nextAddress != null) ? nextAddress.toString() : 
"None"));
 
 1519             for (
int i = 0; i < 4; i++) {
 
 1520                 if (dataSegmentSizes[i] > 0) {
 
 1521                     sb.append(String.format(
"\n\tData %d: cache address = %s, Data = %s", 
 
 1522                                          i, dataSegmentIndexFileEntries[i].toString(), 
 
 1523                                          (dataSegments != null)
 
 1524                                                  ? dataSegments.get(i).toString() 
 
 1525                                                  : 
"Data not retrived yet."));
 
 1529             return sb.toString();