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();