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;
 
   55 import org.
sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
 
   76 final class ChromeCacheExtractor {
 
   78     private final static String DEFAULT_CACHE_STR = 
"default/cache"; 
 
   79     private final static String BROTLI_MIMETYPE =
"application/x-brotli"; 
 
   81     private final static long UINT32_MASK = 0xFFFFFFFFl;
 
   83     private final static int INDEXFILE_HDR_SIZE = 92*4;
 
   84     private final static int DATAFILE_HDR_SIZE = 8192;
 
   86     private final static Logger logger = Logger.getLogger(ChromeCacheExtractor.class.getName());
 
   88     private static final String VERSION_NUMBER = 
"1.0.0"; 
 
   89     private final String moduleName;
 
   91     private String absOutputFolderName;
 
   92     private String relOutputFolderName;
 
   94     private final Content dataSource;
 
   95     private final IngestJobContext context;
 
   96     private final DataSourceIngestModuleProgress progressBar;
 
   97     private final IngestServices services = IngestServices.getInstance();
 
   98     private Case currentCase;
 
   99     private FileManager fileManager;
 
  102     private final Map<String, CacheFileCopy> filesTable = 
new HashMap<>();
 
  105     private final Map<String, AbstractFile> externalFilesTable = 
new HashMap<>();
 
  111     final class CacheFileCopy {
 
  113         private final AbstractFile abstractFile;
 
  114         private final RandomAccessFile fileCopy;
 
  115         private final ByteBuffer byteBuffer;
 
  117         CacheFileCopy (AbstractFile abstractFile, RandomAccessFile fileCopy, ByteBuffer buffer ) {
 
  118             this.abstractFile = abstractFile;
 
  119             this.fileCopy = fileCopy;
 
  120             this.byteBuffer = buffer;
 
  123         public RandomAccessFile getFileCopy() {
 
  126         public ByteBuffer getByteBuffer() {
 
  129         AbstractFile getAbstractFile() {
 
  135         "ChromeCacheExtractor.moduleName=ChromeCacheExtractor",
 
  136         "ChromeCacheExtractor.progressMsg={0}: Extracting cache entry {1} of {2} entries from {3}" 
  138     ChromeCacheExtractor(Content dataSource, IngestJobContext context, DataSourceIngestModuleProgress progressBar ) { 
 
  139         moduleName = Bundle.ChromeCacheExtractor_moduleName();
 
  140         this.dataSource = dataSource;
 
  141         this.context = context;
 
  142         this.progressBar = progressBar;
 
  151     void moduleInit() throws IngestModuleException {
 
  154             currentCase = Case.getCurrentCaseThrows();
 
  155             fileManager = currentCase.getServices().getFileManager();
 
  158             absOutputFolderName = RAImageIngestModule.getRAOutputPath(currentCase, moduleName);
 
  159             relOutputFolderName = Paths.get( RAImageIngestModule.getRelModuleOutputPath(), moduleName).normalize().toString();
 
  161             File dir = 
new File(absOutputFolderName);
 
  162             if (dir.exists() == 
false) {
 
  165         } 
catch (NoCurrentCaseException ex) {
 
  166             String msg = 
"Failed to get current case."; 
 
  167             throw new IngestModuleException(msg, ex);
 
  178     void subInit(String cachePath) 
throws IngestModuleException {
 
  181         externalFilesTable.clear();
 
  183         String cacheAbsOutputFolderName = this.getAbsOutputFolderName() + cachePath;
 
  184         File outDir = 
new File(cacheAbsOutputFolderName);
 
  185         if (outDir.exists() == 
false) {
 
  189         String cacheTempPath = RAImageIngestModule.getRATempPath(currentCase, moduleName) + cachePath;
 
  190         File tempDir = 
new File(cacheTempPath);
 
  191         if (tempDir.exists() == 
false) {
 
  204         for (Entry<String, CacheFileCopy> entry : this.filesTable.entrySet()) {
 
  205             Path tempFilePath = Paths.get(RAImageIngestModule.getRATempPath(currentCase, moduleName), entry.getKey() ); 
 
  207                 entry.getValue().getFileCopy().getChannel().close();
 
  208                 entry.getValue().getFileCopy().close();
 
  210                 File tmpFile = tempFilePath.toFile();
 
  211                 if (!tmpFile.delete()) {
 
  212                     tmpFile.deleteOnExit();
 
  214             } 
catch (IOException ex) {
 
  215                 logger.log(Level.WARNING, String.format(
"Failed to delete cache file copy %s", tempFilePath.toString()), ex); 
 
  225     private String getAbsOutputFolderName() {
 
  226         return absOutputFolderName;
 
  234     private String getRelOutputFolderName() {
 
  235         return relOutputFolderName;
 
  248         } 
catch (IngestModuleException ex) {
 
  249             String msg = 
"Failed to initialize ChromeCacheExtractor."; 
 
  250             logger.log(Level.SEVERE, msg, ex);
 
  255         List<AbstractFile> indexFiles;
 
  257             indexFiles = findCacheFiles(
"index"); 
 
  260             for (AbstractFile indexFile: indexFiles) {       
 
  264         } 
catch (TskCoreException ex) {
 
  265                 String msg = 
"Failed to find cache index files"; 
 
  266                 logger.log(Level.SEVERE, msg, ex);
 
  275     void getCache(AbstractFile indexAbstractFile) {
 
  277         String cachePath = indexAbstractFile.getParentPath();
 
  278         Optional<CacheFileCopy> indexFile;
 
  282             indexFile = this.getCacheFileCopy(indexAbstractFile.getName(), cachePath);
 
  283             if (!indexFile.isPresent()) {
 
  284                 String msg = String.format(
"Failed to find copy cache index file %s", indexAbstractFile.getUniquePath());
 
  285                 logger.log(Level.SEVERE, msg);
 
  289             for (
int i = 0; i < 4; i ++)  {
 
  290                 Optional<CacheFileCopy> dataFile = findAndCopyCacheFile(String.format(
"data_%1d",i), cachePath );
 
  291                 if (!dataFile.isPresent()) {
 
  297             findExternalFiles(cachePath);
 
  299         } 
catch (TskCoreException | IngestModuleException ex) {
 
  300             String msg = 
"Failed to find cache files in path " + cachePath; 
 
  301             logger.log(Level.SEVERE, msg, ex);
 
  305         logger.log(Level.INFO, 
"{0}- Now reading Cache index file from path {1}", 
new Object[]{moduleName, cachePath }); 
 
  307         List<AbstractFile> derivedFiles = 
new ArrayList<>();
 
  308         Collection<BlackboardArtifact> sourceArtifacts = 
new ArrayList<>();
 
  309         Collection<BlackboardArtifact> webCacheArtifacts = 
new ArrayList<>();
 
  311         ByteBuffer indexFileROBuffer = indexFile.get().getByteBuffer();
 
  312         IndexFileHeader indexHdr = 
new IndexFileHeader(indexFileROBuffer);
 
  315         indexFileROBuffer.position(INDEXFILE_HDR_SIZE);
 
  318         for (
int i = 0; i <  indexHdr.getTableLen(); i++) {
 
  319             CacheAddress addr = 
new CacheAddress(indexFileROBuffer.getInt() & UINT32_MASK, cachePath);
 
  320             if (addr.isInitialized()) {
 
  321                 progressBar.progress( NbBundle.getMessage(
this.getClass(),
 
  322                                         "ChromeCacheExtractor.progressMsg",
 
  323                                         moduleName, i, indexHdr.getTableLen(), cachePath)  );
 
  325                     List<DerivedFile> addedFiles = this.getCacheEntry(addr, sourceArtifacts, webCacheArtifacts);
 
  326                     derivedFiles.addAll(addedFiles);
 
  328                 catch (TskCoreException | IngestModuleException ex) {
 
  329                    logger.log(Level.SEVERE, String.format(
"Failed to get cache entry at address %s", addr), ex); 
 
  334         derivedFiles.forEach((derived) -> {
 
  335             services.fireModuleContentEvent(
new ModuleContentEvent(derived));
 
  338         context.addFilesToJob(derivedFiles);
 
  340         services.fireModuleDataEvent(
new ModuleDataEvent(moduleName, BlackboardArtifact.ARTIFACT_TYPE.TSK_DOWNLOAD_SOURCE, !sourceArtifacts.isEmpty() ? sourceArtifacts : null));
 
  341         services.fireModuleDataEvent(
new ModuleDataEvent(moduleName, BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_CACHE, !webCacheArtifacts.isEmpty() ? webCacheArtifacts : null));
 
  357     List<DerivedFile> getCacheEntry(CacheAddress cacheEntryAddress, Collection<BlackboardArtifact> sourceArtifacts, Collection<BlackboardArtifact> webCacheArtifacts ) 
throws TskCoreException, IngestModuleException {
 
  359         List<DerivedFile> derivedFiles = 
new ArrayList<>();
 
  361         String cacheEntryFileName = cacheEntryAddress.getFilename(); 
 
  362         String cachePath = cacheEntryAddress.getCachePath();
 
  365         Optional<CacheFileCopy> cacheEntryFile = this.getCacheFileCopy(cacheEntryFileName, cachePath);
 
  366         if (!cacheEntryFile.isPresent()) {
 
  367             String msg = String.format(
"Failed to get cache entry at address %s", cacheEntryAddress); 
 
  368             throw new IngestModuleException(msg);
 
  373         CacheEntry cacheEntry = 
new CacheEntry(cacheEntryAddress, cacheEntryFile.get() );
 
  374         List<CacheData> dataEntries = cacheEntry.getData();
 
  376         BlackboardAttribute urlAttr = 
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL,
 
  378                                                     ((cacheEntry.getKey() != null) ? cacheEntry.getKey() : 
""));
 
  380         BlackboardAttribute createTimeAttr = 
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED,
 
  382                                                     cacheEntry.getCreationTime());
 
  384         BlackboardAttribute hhtpHeaderAttr = 
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_HEADERS,
 
  386                                                     cacheEntry.getHTTPHeaders());
 
  391         for (
int j = 1; j < dataEntries.size() && j < 2; j++) {
 
  392             CacheData data = dataEntries.get(j);
 
  393             String dataFilename = data.getAddress().getFilename();
 
  394             Optional<AbstractFile> dataFile = this.findCacheFile(dataFilename, cachePath);
 
  396             boolean isBrotliCompressed = 
false;
 
  397             if (data.getType() != CacheDataTypeEnum.HTTP_HEADER && cacheEntry.isBrotliCompressed() ) {
 
  398                 isBrotliCompressed = 
true;
 
  401             Collection<BlackboardAttribute> sourceArtifactAttributes = 
new ArrayList<>();
 
  402             sourceArtifactAttributes.add(urlAttr);
 
  403             sourceArtifactAttributes.add(createTimeAttr);
 
  405             Collection<BlackboardAttribute> webCacheAttributes = 
new ArrayList<>();
 
  406             webCacheAttributes.add(urlAttr);
 
  407             webCacheAttributes.add(createTimeAttr);
 
  408             webCacheAttributes.add(hhtpHeaderAttr);
 
  410             if (dataFile.isPresent()) {
 
  411                 if (data.isInExternalFile() )  {
 
  413                         BlackboardArtifact sourceArtifact = dataFile.get().newArtifact(ARTIFACT_TYPE.TSK_DOWNLOAD_SOURCE);
 
  414                         if (sourceArtifact != null) {
 
  415                             sourceArtifact.addAttributes(sourceArtifactAttributes);
 
  416                             sourceArtifacts.add(sourceArtifact);
 
  419                         BlackboardArtifact webCacheArtifact = cacheEntryFile.get().getAbstractFile().newArtifact(ARTIFACT_TYPE.TSK_WEB_CACHE);
 
  420                         if (webCacheArtifact != null) {
 
  421                             webCacheArtifact.addAttributes(webCacheAttributes);
 
  424                             webCacheArtifact.addAttribute(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH,
 
  426                                 dataFile.get().getUniquePath()));
 
  428                             webCacheArtifact.addAttribute(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_ID,
 
  429                                         moduleName, dataFile.get().getId()));
 
  431                             webCacheArtifacts.add(webCacheArtifact);
 
  434                         if (isBrotliCompressed) {
 
  435                             dataFile.get().setMIMEType(BROTLI_MIMETYPE);
 
  436                             dataFile.get().save();
 
  438                     } 
catch (TskException ex) {
 
  439                         logger.log(Level.SEVERE, 
"Error while trying to add an artifact", ex); 
 
  444                     String filename = data.save();
 
  445                     String relPathname = getRelOutputFolderName() + data.getAddress().getCachePath() + filename; 
 
  447                         DerivedFile derivedFile = fileManager.addDerivedFile(filename, relPathname,
 
  448                                                             data.getDataLength(), 
 
  449                                                             cacheEntry.getCreationTime(), cacheEntry.getCreationTime(), cacheEntry.getCreationTime(), cacheEntry.getCreationTime(), 
 
  456                                                             TskData.EncodingType.NONE);
 
  458                         BlackboardArtifact sourceArtifact = derivedFile.newArtifact(ARTIFACT_TYPE.TSK_DOWNLOAD_SOURCE);
 
  459                         if (sourceArtifact != null) {
 
  460                             sourceArtifact.addAttributes(sourceArtifactAttributes);
 
  461                             sourceArtifacts.add(sourceArtifact);
 
  464                         BlackboardArtifact webCacheArtifact =  cacheEntryFile.get().getAbstractFile().newArtifact(ARTIFACT_TYPE.TSK_WEB_CACHE); 
 
  465                         if (webCacheArtifact != null) {
 
  466                             webCacheArtifact.addAttributes(webCacheAttributes);
 
  469                             webCacheArtifact.addAttribute(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH,
 
  471                                 derivedFile.getUniquePath()));
 
  473                             webCacheArtifact.addAttribute(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_ID,
 
  474                                     moduleName, derivedFile.getId()));
 
  476                             webCacheArtifacts.add(webCacheArtifact);
 
  479                         if (isBrotliCompressed) {
 
  480                             derivedFile.setMIMEType(BROTLI_MIMETYPE);
 
  484                         derivedFiles.add(derivedFile);
 
  485                     } 
catch (TskException ex) {
 
  486                         logger.log(Level.SEVERE, 
"Error while trying to add an artifact", ex); 
 
  503     private void findExternalFiles(String cachePath) 
throws TskCoreException {
 
  505         List<AbstractFile> effFiles = fileManager.findFiles(dataSource, 
"f_%", cachePath); 
 
  506         for (AbstractFile abstractFile : effFiles ) {
 
  507             this.externalFilesTable.put(cachePath + abstractFile.getName(), abstractFile);
 
  518     Optional<AbstractFile> findCacheFile(String cacheFileName, String cachePath) 
throws TskCoreException {
 
  520         String fileTableKey = cachePath + cacheFileName;
 
  521         if (cacheFileName.startsWith(
"f_") && externalFilesTable.containsKey(fileTableKey)) {
 
  522             return Optional.of(externalFilesTable.get(fileTableKey));
 
  524         if (filesTable.containsKey(fileTableKey)) {
 
  525             return Optional.of(filesTable.get(fileTableKey).getAbstractFile());
 
  528         List<AbstractFile> cacheFiles = fileManager.findFiles(dataSource, cacheFileName, cachePath); 
 
  529         if (!cacheFiles.isEmpty()) {
 
  530             for (AbstractFile abstractFile: cacheFiles ) {
 
  531                 if (abstractFile.getUniquePath().trim().endsWith(DEFAULT_CACHE_STR)) {
 
  532                     return Optional.of(abstractFile);
 
  535             return Optional.of(cacheFiles.get(0));
 
  538         return Optional.empty(); 
 
  548     List<AbstractFile> findCacheFiles(String cacheFileName) 
throws TskCoreException {
 
  549         return fileManager.findFiles(dataSource, cacheFileName, DEFAULT_CACHE_STR); 
 
  561     Optional<CacheFileCopy> getCacheFileCopy(String cacheFileName, String cachePath) 
throws TskCoreException, IngestModuleException {
 
  564         String fileTableKey = cachePath + cacheFileName;
 
  565         if (filesTable.containsKey(fileTableKey)) {
 
  566             return Optional.of(filesTable.get(fileTableKey));
 
  569         return findAndCopyCacheFile(cacheFileName, cachePath);
 
  579     Optional<CacheFileCopy> findAndCopyCacheFile(String cacheFileName, String cachePath) 
throws TskCoreException, IngestModuleException  {
 
  581         Optional<AbstractFile> cacheFileOptional = findCacheFile(cacheFileName, cachePath);
 
  582         if (!cacheFileOptional.isPresent()) {
 
  583             return Optional.empty(); 
 
  586         AbstractFile cacheFile = cacheFileOptional.get();
 
  587         RandomAccessFile randomAccessFile = null;
 
  588         String tempFilePathname = RAImageIngestModule.getRATempPath(currentCase, moduleName) + cachePath + cacheFile.getName(); 
 
  590             File newFile = 
new File(tempFilePathname);
 
  591             ContentUtils.writeToFile(cacheFile, newFile, context::dataSourceIngestIsCancelled);
 
  593             randomAccessFile = 
new RandomAccessFile(tempFilePathname, 
"r");
 
  594             FileChannel roChannel = randomAccessFile.getChannel();
 
  595             ByteBuffer cacheFileROBuf = roChannel.map(FileChannel.MapMode.READ_ONLY, 0,
 
  596                                                         (
int) roChannel.size());
 
  598             cacheFileROBuf.order(ByteOrder.nativeOrder());
 
  599             CacheFileCopy cacheFileCopy = 
new CacheFileCopy(cacheFile, randomAccessFile, cacheFileROBuf );
 
  601             if (!cacheFileName.startsWith(
"f_")) {
 
  602                 filesTable.put(cachePath + cacheFileName, cacheFileCopy);
 
  605             return Optional.of(cacheFileCopy);
 
  607         catch (IOException ex) {
 
  610                 if (randomAccessFile != null) {
 
  611                     randomAccessFile.close();
 
  614             catch (IOException ex2) {
 
  615                 logger.log(Level.SEVERE, 
"Error while trying to close temp file after exception.", ex2); 
 
  617             String msg = String.format(
"Error reading/copying Chrome cache file '%s' (id=%d).", 
 
  618                                             cacheFile.getName(), cacheFile.getId()); 
 
  619             throw new IngestModuleException(msg, ex);
 
  626     final class IndexFileHeader {
 
  628         private final long magic;
 
  629         private final int version;
 
  630         private final int numEntries;
 
  631         private final int numBytes;
 
  632         private final int lastFile;
 
  633         private final int tableLen;
 
  635         IndexFileHeader(ByteBuffer indexFileROBuf) {
 
  637             magic = indexFileROBuf.getInt() & UINT32_MASK; 
 
  639             indexFileROBuf.position(indexFileROBuf.position()+2);
 
  641             version = indexFileROBuf.getShort();
 
  642             numEntries = indexFileROBuf.getInt();
 
  643             numBytes = indexFileROBuf.getInt();
 
  644             lastFile = indexFileROBuf.getInt();
 
  646             indexFileROBuf.position(indexFileROBuf.position()+4); 
 
  647             indexFileROBuf.position(indexFileROBuf.position()+4); 
 
  649             tableLen = indexFileROBuf.getInt();
 
  652         public long getMagic() {
 
  656         public int getVersion() {
 
  660         public int getNumEntries() {
 
  664         public int getNumBytes() {
 
  668         public int getLastFile() {
 
  672         public int getTableLen() {
 
  677         public String toString() {
 
  678             StringBuilder sb = 
new StringBuilder();
 
  680             sb.append(String.format(
"Index Header:"))
 
  681                 .append(String.format(
"\tMagic = %x" , getMagic()) )
 
  682                 .append(String.format(
"\tVersion = %x" , getVersion()) )
 
  683                 .append(String.format(
"\tNumEntries = %x" , getNumEntries()) )
 
  684                 .append(String.format(
"\tNumBytes = %x" , getNumBytes()) )
 
  685                 .append(String.format(
"\tLastFile = %x" , getLastFile()) )
 
  686                 .append(String.format(
"\tTableLen = %x" , getTableLen()) );
 
  688             return sb.toString();
 
  695     enum CacheFileTypeEnum {
 
  727     final class CacheAddress {
 
  729         private static final long ADDR_INITIALIZED_MASK    = 0x80000000l;
 
  730         private static final long FILE_TYPE_MASK     = 0x70000000;
 
  731         private static final long FILE_TYPE_OFFSET   = 28;
 
  732         private static final long NUM_BLOCKS_MASK     = 0x03000000;
 
  733         private static final long NUM_BLOCKS_OFFSET   = 24;
 
  734         private static final long FILE_SELECTOR_MASK   = 0x00ff0000;
 
  735         private static final long FILE_SELECTOR_OFFSET = 16;
 
  736         private static final long START_BLOCK_MASK     = 0x0000FFFF;
 
  737         private static final long EXTERNAL_FILE_NAME_MASK = 0x0FFFFFFF;
 
  739         private final long uint32CacheAddr;
 
  740         private final CacheFileTypeEnum fileType;
 
  741         private final int numBlocks;
 
  742         private final int startBlock;
 
  743         private final String fileName;
 
  744         private final int fileNumber;
 
  746         private final String cachePath;
 
  749         CacheAddress(
long uint32, String cachePath) {
 
  751             uint32CacheAddr = uint32;
 
  752             this.cachePath = cachePath;
 
  754             int fileTypeEnc = (int)(uint32CacheAddr &  FILE_TYPE_MASK) >> FILE_TYPE_OFFSET;
 
  755             fileType = CacheFileTypeEnum.values()[fileTypeEnc];
 
  757             if (isInitialized()) {
 
  758                 if (isInExternalFile()) {
 
  759                     fileNumber = (int)(uint32CacheAddr & EXTERNAL_FILE_NAME_MASK);
 
  760                     fileName =  String.format(
"f_%06x", getFileNumber() );
 
  764                     fileNumber = (int)((uint32CacheAddr & FILE_SELECTOR_MASK) >> FILE_SELECTOR_OFFSET);
 
  765                     fileName = String.format(
"data_%d", getFileNumber() );
 
  766                     numBlocks = (int)(uint32CacheAddr &  NUM_BLOCKS_MASK >> NUM_BLOCKS_OFFSET);
 
  767                     startBlock = (int)(uint32CacheAddr &  START_BLOCK_MASK);
 
  778         boolean isInitialized() {
 
  779             return ((uint32CacheAddr & ADDR_INITIALIZED_MASK) != 0);
 
  782         CacheFileTypeEnum getFileType() {
 
  786         String getFilename() {
 
  790         String getCachePath() {
 
  794         boolean isInExternalFile() {
 
  795             return (fileType == CacheFileTypeEnum.EXTERNAL);
 
  798         int getFileNumber() {
 
  802         int getStartBlock() {
 
  831         public long getUint32CacheAddr() {
 
  832             return uint32CacheAddr;
 
  836         public String toString() {
 
  837             StringBuilder sb = 
new StringBuilder();
 
  838             sb.append(String.format(
"CacheAddr %08x : %s : filename %s", 
 
  840                                     isInitialized() ? 
"Initialized" : 
"UnInitialized",
 
  843             if ((fileType == CacheFileTypeEnum.BLOCK_256) || 
 
  844                 (fileType == CacheFileTypeEnum.BLOCK_1K) || 
 
  845                 (fileType == CacheFileTypeEnum.BLOCK_4K) ) {
 
  846                 sb.append(String.format(
" (%d blocks starting at %08X)", 
 
  852             return sb.toString();
 
  860     enum CacheDataTypeEnum {
 
  874     final class CacheData {
 
  877         private final CacheAddress address;
 
  878         private CacheDataTypeEnum type;
 
  880         private boolean isHTTPHeaderHint;
 
  882         private CacheFileCopy cacheFileCopy = null;
 
  883         private byte[] data = null;
 
  885         private String httpResponse;
 
  886         private final Map<String, String> httpHeaders = 
new HashMap<>();
 
  888         CacheData(CacheAddress cacheAdress, 
int len) {
 
  889             this(cacheAdress, len, 
false);
 
  892         CacheData(CacheAddress cacheAdress, 
int len, 
boolean isHTTPHeader ) {
 
  893             this.type = CacheDataTypeEnum.UNKNOWN;
 
  895             this.address = cacheAdress;
 
  896             this.isHTTPHeaderHint = isHTTPHeader;
 
  899         boolean isInExternalFile() {
 
  900             return address.isInExternalFile();
 
  903         boolean hasHTTPHeaders() {
 
  904             return this.type == CacheDataTypeEnum.HTTP_HEADER;
 
  907         String getHTTPHeader(String key) {
 
  908             return this.httpHeaders.get(key);
 
  916         String getHTTPHeaders() {
 
  917             if (!hasHTTPHeaders()) {
 
  921             StringBuilder sb = 
new StringBuilder();
 
  922             httpHeaders.entrySet().forEach((entry) -> {
 
  923                 if (sb.length() > 0) {
 
  926                 sb.append(String.format(
"%s : %s",
 
  927                         entry.getKey(), entry.getValue()));
 
  930             return sb.toString();
 
  933         String getHTTPRespone() {
 
  942         void extract() throws TskCoreException, IngestModuleException {
 
  950             if (!address.isInExternalFile() ) {
 
  952                 cacheFileCopy = getCacheFileCopy(address.getFilename(), address.getCachePath()).
get();
 
  954                 this.data = 
new byte [length];
 
  955                 ByteBuffer buf = cacheFileCopy.getByteBuffer();
 
  956                 int dataOffset = DATAFILE_HDR_SIZE + address.getStartBlock() * address.getBlockSize();
 
  957                 buf.position(dataOffset);
 
  958                 buf.get(data, 0, length);
 
  961                 if ((isHTTPHeaderHint)) {
 
  962                     String strData = 
new String(data);
 
  963                     if (strData.contains(
"HTTP")) {
 
  974                         type = CacheDataTypeEnum.HTTP_HEADER;
 
  976                         int startOff = strData.indexOf(
"HTTP");
 
  977                         Charset charset = Charset.forName(
"UTF-8");
 
  978                         boolean done = 
false;
 
  985                             while (i < data.length && data[i] != 0)  {
 
  990                             if (i == data.length || data[i+1] == 0) {
 
  994                             int len = (i - start);
 
  995                             String headerLine = 
new String(data, start, len, charset);
 
  999                                 httpResponse = headerLine;
 
 1001                                 int nPos = headerLine.indexOf(
':');
 
 1003                                     String key = headerLine.substring(0, nPos);
 
 1004                                     String val= headerLine.substring(nPos+1);
 
 1005                                     httpHeaders.put(key.toLowerCase(), val);
 
 1017         String getDataString() throws TskCoreException, IngestModuleException {
 
 1021             return new String(data);
 
 1024         byte[] getDataBytes() throws TskCoreException, IngestModuleException {
 
 1028             return data.clone();
 
 1031         int getDataLength() {
 
 1035         CacheDataTypeEnum getType() {
 
 1039         CacheAddress getAddress() {
 
 1052         String save() throws TskCoreException, IngestModuleException {
 
 1055             if (address.isInExternalFile()) {
 
 1056                 fileName = address.getFilename();
 
 1058                 fileName = String.format(
"%s__%08x", address.getFilename(), address.getUint32CacheAddr());
 
 1061             String filePathName = getAbsOutputFolderName() + address.getCachePath() + fileName;
 
 1076         void save(String filePathName) 
throws TskCoreException, IngestModuleException {
 
 1084             if (!this.isInExternalFile()) {
 
 1086                 try (FileOutputStream stream = 
new FileOutputStream(filePathName)) {
 
 1088                 } 
catch (IOException ex) {
 
 1089                     throw new TskCoreException(String.format(
"Failed to write output file %s", filePathName), ex);
 
 1095         public String toString() {
 
 1096             StringBuilder strBuilder = 
new StringBuilder();
 
 1097             strBuilder.append(String.format(
"\t\tData type = : %s, Data Len = %d ", 
 
 1098                                     this.type.toString(), this.length ));
 
 1100             if (hasHTTPHeaders()) {
 
 1101                 String str = getHTTPHeader(
"content-encoding");
 
 1103                     strBuilder.append(String.format(
"\t%s=%s", 
"content-encoding", str ));
 
 1107             return strBuilder.toString(); 
 
 1116     enum EntryStateEnum {
 
 1155     final class CacheEntry {
 
 1158         private static final int MAX_KEY_LEN = 256-24*4; 
 
 1160         private final CacheAddress selfAddress; 
 
 1161         private final CacheFileCopy cacheFileCopy;
 
 1163         private final long hash;
 
 1164         private final CacheAddress nextAddress;
 
 1165         private final CacheAddress rankingsNodeAddress;
 
 1167         private final int reuseCount;
 
 1168         private final int refetchCount;
 
 1169         private final EntryStateEnum state;
 
 1171         private final long creationTime;
 
 1172         private final int keyLen;
 
 1174         private final CacheAddress longKeyAddresses; 
 
 1176         private final int dataSizes[];
 
 1177         private final CacheAddress dataAddresses[];
 
 1178         private List<CacheData> dataList;
 
 1180         private final long flags;
 
 1184         CacheEntry(CacheAddress cacheAdress, CacheFileCopy cacheFileCopy ) {
 
 1185             this.selfAddress = cacheAdress;
 
 1186             this.cacheFileCopy = cacheFileCopy;
 
 1188             ByteBuffer fileROBuf = cacheFileCopy.getByteBuffer();
 
 1190             int entryOffset = DATAFILE_HDR_SIZE + cacheAdress.getStartBlock() * cacheAdress.getBlockSize();
 
 1193             fileROBuf.position(entryOffset);
 
 1195             hash = fileROBuf.getInt() & UINT32_MASK;
 
 1197             long uint32 = fileROBuf.getInt() & UINT32_MASK;
 
 1198             nextAddress = (uint32 != 0) ?  
new CacheAddress(uint32, selfAddress.getCachePath()) : null;  
 
 1200             uint32 = fileROBuf.getInt() & UINT32_MASK;
 
 1201             rankingsNodeAddress = (uint32 != 0) ?  
new CacheAddress(uint32, selfAddress.getCachePath()) : null;  
 
 1203             reuseCount = fileROBuf.getInt();
 
 1204             refetchCount = fileROBuf.getInt();
 
 1206             state = EntryStateEnum.values()[fileROBuf.getInt()];
 
 1207             creationTime = (fileROBuf.getLong() / 1000000) - Long.valueOf(
"11644473600");
 
 1209             keyLen = fileROBuf.getInt();
 
 1211             uint32 = fileROBuf.getInt() & UINT32_MASK;
 
 1212             longKeyAddresses = (uint32 != 0) ?  
new CacheAddress(uint32, selfAddress.getCachePath()) : null;  
 
 1215             dataSizes= 
new int[4];
 
 1216             for (
int i = 0; i < 4; i++)  {
 
 1217                 dataSizes[i] = fileROBuf.getInt();
 
 1219             dataAddresses = 
new CacheAddress[4];
 
 1220             for (
int i = 0; i < 4; i++)  {
 
 1221                 dataAddresses[i] =  
new CacheAddress(fileROBuf.getInt() & UINT32_MASK, selfAddress.getCachePath());
 
 1224             flags = fileROBuf.getInt() & UINT32_MASK;
 
 1226             for (
int i = 0; i < 4; i++)  {
 
 1234             if (longKeyAddresses != null) {
 
 1237                     CacheData data = 
new CacheData(longKeyAddresses, this.keyLen, 
true);
 
 1238                     key = data.getDataString();
 
 1239                 } 
catch (TskCoreException | IngestModuleException ex) {
 
 1240                     logger.log(Level.SEVERE, String.format(
"Failed to get external key from address %s", longKeyAddresses)); 
 
 1244                 StringBuilder strBuilder = 
new StringBuilder(MAX_KEY_LEN);
 
 1246                 while (fileROBuf.remaining() > 0  && keyLen < MAX_KEY_LEN)  {
 
 1247                     char keyChar = (char)fileROBuf.get();
 
 1248                     if (keyChar == 
'\0') { 
 
 1251                     strBuilder.append(keyChar);
 
 1255                 key = strBuilder.toString();
 
 1259         public CacheAddress getAddress() {
 
 1263         public long getHash() {
 
 1267         public CacheAddress getNextAddress() {
 
 1271         public int getReuseCount() {
 
 1275         public int getRefetchCount() {
 
 1276             return refetchCount;
 
 1279         public EntryStateEnum getState() {
 
 1283         public long getCreationTime() {
 
 1284             return creationTime;
 
 1287         public long getFlags() {
 
 1291         public String getKey() {
 
 1303         public List<CacheData> getData() throws TskCoreException, IngestModuleException {
 
 1305             if (dataList == null) { 
 
 1306                 dataList = 
new ArrayList<>();
 
 1307                  for (
int i = 0; i < 4; i++)  {
 
 1308                      if (dataSizes[i] > 0) {
 
 1309                          CacheData cacheData = 
new CacheData(dataAddresses[i], dataSizes[i], 
true );
 
 1311                          cacheData.extract();
 
 1312                          dataList.add(cacheData);
 
 1326         boolean hasHTTPHeaders() {
 
 1327             if ((dataList == null) || dataList.isEmpty()) {
 
 1330             return dataList.get(0).hasHTTPHeaders();
 
 1339         String getHTTPHeader(String key) {
 
 1340             if ((dataList == null) || dataList.isEmpty()) {
 
 1344             return dataList.get(0).getHTTPHeader(key);
 
 1352         String getHTTPHeaders() {
 
 1353             if ((dataList == null) || dataList.isEmpty()) {
 
 1357             return dataList.get(0).getHTTPHeaders();
 
 1368         boolean isBrotliCompressed() {
 
 1370             if (hasHTTPHeaders() ) {
 
 1371                 String encodingHeader = getHTTPHeader(
"content-encoding");
 
 1372                 if (encodingHeader!= null) {
 
 1373                     return encodingHeader.trim().equalsIgnoreCase(
"br");
 
 1381         public String toString() {
 
 1382             StringBuilder sb = 
new StringBuilder();
 
 1383             sb.append(String.format(
"Entry = Hash: %08x,  State: %s, ReuseCount: %d, RefetchCount: %d", 
 
 1384                                     this.hash, 
this.state.toString(), this.reuseCount, this.refetchCount ))
 
 1385                 .append(String.format(
"\n\tKey: %s, Keylen: %d", 
 
 1386                                     this.key, 
this.keyLen, 
this.reuseCount, 
this.refetchCount ))
 
 1387                 .append(String.format(
"\n\tCreationTime: %s", 
 
 1388                                     TimeUtilities.epochToTime(
this.creationTime) ))
 
 1389                 .append(String.format(
"\n\tNext Address: %s", 
 
 1390                                     (nextAddress != null) ? nextAddress.toString() : 
"None"));
 
 1392             for (
int i = 0; i < 4; i++) {
 
 1393                 if (dataSizes[i] > 0) {
 
 1394                     sb.append(String.format(
"\n\tData %d: cache address = %s, Data = %s", 
 
 1395                                          i, dataAddresses[i].toString(), 
 
 1397                                                  ? dataList.get(i).toString() 
 
 1398                                                  : 
"Data not retrived yet."));
 
 1402             return sb.toString();