21 package org.sleuthkit.autopsy.recentactivity;
 
   24 import java.io.FileOutputStream;
 
   25 import java.io.IOException;
 
   26 import java.io.RandomAccessFile;
 
   27 import java.nio.ByteBuffer;
 
   28 import java.nio.ByteOrder;
 
   29 import java.nio.channels.FileChannel;
 
   30 import java.nio.charset.Charset;
 
   31 import java.nio.file.Path;
 
   32 import java.nio.file.Paths;
 
   33 import java.util.ArrayList;
 
   34 import java.util.Collection;
 
   35 import java.util.HashMap;
 
   36 import java.util.List;
 
   38 import java.util.Map.Entry;
 
   39 import java.util.Optional;
 
   40 import java.util.logging.Level;
 
   41 import org.openide.util.NbBundle;
 
   42 import org.openide.util.NbBundle.Messages;
 
   57 import org.
sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
 
   90 final class ChromeCacheExtractor {
 
   92     private final static String DEFAULT_CACHE_PATH_STR = 
"default/cache"; 
 
   93     private final static String BROTLI_MIMETYPE =
"application/x-brotli"; 
 
   95     private final static long UINT32_MASK = 0xFFFFFFFFl;
 
   97     private final static int INDEXFILE_HDR_SIZE = 92*4;
 
   98     private final static int DATAFILE_HDR_SIZE = 8192;
 
  100     private final static Logger logger = Logger.getLogger(ChromeCacheExtractor.class.getName());
 
  102     private static final String VERSION_NUMBER = 
"1.0.0"; 
 
  103     private final String moduleName;
 
  105     private String absOutputFolderName;
 
  106     private String relOutputFolderName;
 
  108     private final Content dataSource;
 
  109     private final IngestJobContext context;
 
  110     private final DataSourceIngestModuleProgress progressBar;
 
  111     private final IngestServices services = IngestServices.getInstance();
 
  112     private Case currentCase;
 
  113     private FileManager fileManager;
 
  116     private final Map<String, FileWrapper> fileCopyCache = 
new HashMap<>();
 
  119     private final Map<String, AbstractFile> externalFilesTable = 
new HashMap<>();
 
  126     final class FileWrapper {       
 
  127         private final AbstractFile abstractFile;
 
  128         private final RandomAccessFile fileCopy;
 
  129         private final ByteBuffer byteBuffer;
 
  131         FileWrapper (AbstractFile abstractFile, RandomAccessFile fileCopy, ByteBuffer buffer ) {
 
  132             this.abstractFile = abstractFile;
 
  133             this.fileCopy = fileCopy;
 
  134             this.byteBuffer = buffer;
 
  137         public RandomAccessFile getFileCopy() {
 
  140         public ByteBuffer getByteBuffer() {
 
  143         AbstractFile getAbstractFile() {
 
  149         "ChromeCacheExtractor.moduleName=ChromeCacheExtractor",
 
  150         "# {0} - module name",
 
  151         "# {1} - row number",
 
  152         "# {2} - table length",
 
  153         "# {3} - cache path",
 
  154         "ChromeCacheExtractor.progressMsg={0}: Extracting cache entry {1} of {2} entries from {3}" 
  156     ChromeCacheExtractor(Content dataSource, IngestJobContext context, DataSourceIngestModuleProgress progressBar ) { 
 
  157         moduleName = Bundle.ChromeCacheExtractor_moduleName();
 
  158         this.dataSource = dataSource;
 
  159         this.context = context;
 
  160         this.progressBar = progressBar;
 
  169     private void moduleInit() throws IngestModuleException {
 
  172             currentCase = Case.getCurrentCaseThrows();
 
  173             fileManager = currentCase.getServices().getFileManager();
 
  176             absOutputFolderName = RAImageIngestModule.getRAOutputPath(currentCase, moduleName);
 
  177             relOutputFolderName = Paths.get( RAImageIngestModule.getRelModuleOutputPath(), moduleName).normalize().toString();
 
  179             File dir = 
new File(absOutputFolderName);
 
  180             if (dir.exists() == 
false) {
 
  183         } 
catch (NoCurrentCaseException ex) {
 
  184             String msg = 
"Failed to get current case."; 
 
  185             throw new IngestModuleException(msg, ex);
 
  196     private void resetForNewCacheFolder(String cachePath) 
throws IngestModuleException {
 
  198         fileCopyCache.clear();
 
  199         externalFilesTable.clear();
 
  201         String cacheAbsOutputFolderName = this.getAbsOutputFolderName() + cachePath;
 
  202         File outDir = 
new File(cacheAbsOutputFolderName);
 
  203         if (outDir.exists() == 
false) {
 
  207         String cacheTempPath = RAImageIngestModule.getRATempPath(currentCase, moduleName) + cachePath;
 
  208         File tempDir = 
new File(cacheTempPath);
 
  209         if (tempDir.exists() == 
false) {
 
  220     private void cleanup () {
 
  222         for (Entry<String, FileWrapper> entry : this.fileCopyCache.entrySet()) {
 
  223             Path tempFilePath = Paths.get(RAImageIngestModule.getRATempPath(currentCase, moduleName), entry.getKey() ); 
 
  225                 entry.getValue().getFileCopy().getChannel().close();
 
  226                 entry.getValue().getFileCopy().close();
 
  228                 File tmpFile = tempFilePath.toFile();
 
  229                 if (!tmpFile.delete()) {
 
  230                     tmpFile.deleteOnExit();
 
  232             } 
catch (IOException ex) {
 
  233                 logger.log(Level.WARNING, String.format(
"Failed to delete cache file copy %s", tempFilePath.toString()), ex); 
 
  243     private String getAbsOutputFolderName() {
 
  244         return absOutputFolderName;
 
  252     private String getRelOutputFolderName() {
 
  253         return relOutputFolderName;
 
  262     void processCaches() {
 
  266         } 
catch (IngestModuleException ex) {
 
  267             String msg = 
"Failed to initialize ChromeCacheExtractor."; 
 
  268             logger.log(Level.SEVERE, msg, ex);
 
  275             List<AbstractFile> indexFiles = findIndexFiles(); 
 
  278             for (AbstractFile indexFile: indexFiles) {  
 
  280                 if (context.dataSourceIngestIsCancelled()) {
 
  284                 processCacheFolder(indexFile);
 
  287         } 
catch (TskCoreException ex) {
 
  288                 String msg = 
"Failed to find cache index files"; 
 
  289                 logger.log(Level.WARNING, msg, ex);
 
  294         "ChromeCacheExtract_adding_extracted_files_msg=Chrome Cache: Adding %d extracted files for analysis.",
 
  295         "ChromeCacheExtract_adding_artifacts_msg=Chrome Cache: Adding %d artifacts for analysis.",
 
  296         "ChromeCacheExtract_loading_files_msg=Chrome Cache: Loading files from %s." 
  305     private void processCacheFolder(AbstractFile indexFile) {
 
  307         String cacheFolderName = indexFile.getParentPath();
 
  308         Optional<FileWrapper> indexFileWrapper;
 
  316             progressBar.progress(String.format(Bundle.ChromeCacheExtract_loading_files_msg(), cacheFolderName));
 
  317             resetForNewCacheFolder(cacheFolderName);
 
  321             indexFileWrapper = findDataOrIndexFile(indexFile.getName(), cacheFolderName);
 
  322             if (!indexFileWrapper.isPresent()) {
 
  323                 String msg = String.format(
"Failed to find copy cache index file %s", indexFile.getUniquePath());
 
  324                 logger.log(Level.WARNING, msg);
 
  331             for (
int i = 0; i < 4; i ++)  {
 
  332                 Optional<FileWrapper> dataFile = findDataOrIndexFile(String.format(
"data_%1d",i), cacheFolderName );
 
  333                 if (!dataFile.isPresent()) {
 
  340             findExternalFiles(cacheFolderName);
 
  342         } 
catch (TskCoreException | IngestModuleException ex) {
 
  343             String msg = 
"Failed to find cache files in path " + cacheFolderName; 
 
  344             logger.log(Level.WARNING, msg, ex);
 
  352         logger.log(Level.INFO, 
"{0}- Now reading Cache index file from path {1}", 
new Object[]{moduleName, cacheFolderName }); 
 
  354         List<AbstractFile> derivedFiles = 
new ArrayList<>();
 
  355         Collection<BlackboardArtifact> artifactsAdded = 
new ArrayList<>();
 
  357         ByteBuffer indexFileROBuffer = indexFileWrapper.get().getByteBuffer();
 
  358         IndexFileHeader indexHdr = 
new IndexFileHeader(indexFileROBuffer);
 
  361         indexFileROBuffer.position(INDEXFILE_HDR_SIZE);
 
  366             for (
int i = 0; i <  indexHdr.getTableLen(); i++) {
 
  368                 if (context.dataSourceIngestIsCancelled()) {
 
  373                 CacheAddress addr = 
new CacheAddress(indexFileROBuffer.getInt() & UINT32_MASK, cacheFolderName);
 
  374                 if (addr.isInitialized()) {
 
  375                     progressBar.progress(NbBundle.getMessage(
this.getClass(),
 
  376                                             "ChromeCacheExtractor.progressMsg",
 
  377                                             moduleName, i, indexHdr.getTableLen(), cacheFolderName)  );
 
  379                         List<DerivedFile> addedFiles = processCacheEntry(addr, artifactsAdded);
 
  380                         derivedFiles.addAll(addedFiles);
 
  382                     catch (TskCoreException | IngestModuleException ex) {
 
  383                        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())); 
 
  387         } 
catch (java.nio.BufferUnderflowException ex) {
 
  388             logger.log(Level.WARNING, String.format(
"Ran out of data unexpectedly reading file %s (ObjID: %d)", indexFile.getName(), indexFile.getId()));
 
  391         if (context.dataSourceIngestIsCancelled()) {
 
  398         progressBar.progress(String.format(Bundle.ChromeCacheExtract_adding_extracted_files_msg(), derivedFiles.size()));
 
  399         derivedFiles.forEach((derived) -> {
 
  400             services.fireModuleContentEvent(
new ModuleContentEvent(derived));
 
  402         context.addFilesToJob(derivedFiles);
 
  405         progressBar.progress(String.format(Bundle.ChromeCacheExtract_adding_artifacts_msg(), artifactsAdded.size()));
 
  406         Blackboard blackboard = currentCase.getSleuthkitCase().getBlackboard();
 
  408             blackboard.postArtifacts(artifactsAdded, moduleName);
 
  409         } 
catch (Blackboard.BlackboardException ex) {
 
  410            logger.log(Level.WARNING, String.format(
"Failed to post cacheIndex artifacts "), ex); 
 
  428     private List<DerivedFile> processCacheEntry(CacheAddress cacheAddress, Collection<BlackboardArtifact> artifactsAdded ) 
throws TskCoreException, IngestModuleException {
 
  430         List<DerivedFile> derivedFiles = 
new ArrayList<>();
 
  433         String cacheEntryFileName = cacheAddress.getFilename(); 
 
  434         String cachePath = cacheAddress.getCachePath();
 
  436         Optional<FileWrapper> cacheEntryFileOptional = findDataOrIndexFile(cacheEntryFileName, cachePath);
 
  437         if (!cacheEntryFileOptional.isPresent()) {
 
  438             String msg = String.format(
"Failed to find data file %s", cacheEntryFileName); 
 
  439             throw new IngestModuleException(msg);
 
  443         CacheEntry cacheEntry = 
new CacheEntry(cacheAddress, cacheEntryFileOptional.get() );
 
  444         List<CacheDataSegment> dataSegments = cacheEntry.getDataSegments();
 
  448         if (dataSegments.size() < 2) {
 
  451         CacheDataSegment dataSegment = dataSegments.get(1);
 
  454         String segmentFileName = dataSegment.getCacheAddress().getFilename();
 
  455         Optional<AbstractFile> segmentFileAbstractFile = findAbstractFile(segmentFileName, cachePath);
 
  456         if (!segmentFileAbstractFile.isPresent()) {
 
  457             logger.log(Level.WARNING, 
"Error finding segment file: " + cachePath + 
"/" + segmentFileName); 
 
  461         boolean isBrotliCompressed = 
false;
 
  462         if (dataSegment.getType() != CacheDataTypeEnum.HTTP_HEADER && cacheEntry.isBrotliCompressed() ) {
 
  463             isBrotliCompressed = 
true;
 
  469             AbstractFile cachedItemFile; 
 
  471             if (dataSegment.isInExternalFile() )  {
 
  472                 cachedItemFile = segmentFileAbstractFile.get();
 
  478                 String filename = dataSegment.save();
 
  479                 String relPathname = getRelOutputFolderName() + dataSegment.getCacheAddress().getCachePath() + filename; 
 
  482                 DerivedFile derivedFile = fileManager.addDerivedFile(filename, relPathname,
 
  483                                                     dataSegment.getDataLength(), 
 
  484                                                     cacheEntry.getCreationTime(), cacheEntry.getCreationTime(), cacheEntry.getCreationTime(), cacheEntry.getCreationTime(), 
 
  486                                                     segmentFileAbstractFile.get(), 
 
  491                                                     TskData.EncodingType.NONE);
 
  493                 derivedFiles.add(derivedFile);
 
  494                 cachedItemFile = derivedFile;
 
  497             addArtifacts(cacheEntry, cacheEntryFileOptional.get().getAbstractFile(), cachedItemFile, artifactsAdded);
 
  500             if (isBrotliCompressed) {
 
  501                 cachedItemFile.setMIMEType(BROTLI_MIMETYPE);
 
  502                 cachedItemFile.save();
 
  505         } 
catch (TskException ex) {
 
  506             logger.log(Level.SEVERE, 
"Error while trying to add an artifact", ex); 
 
  521     private void addArtifacts(CacheEntry cacheEntry, AbstractFile cacheEntryFile, AbstractFile cachedItemFile, Collection<BlackboardArtifact> artifactsAdded) 
throws TskCoreException {
 
  524         BlackboardArtifact webCacheArtifact = cacheEntryFile.newArtifact(ARTIFACT_TYPE.TSK_WEB_CACHE);
 
  525         if (webCacheArtifact != null) {
 
  526             Collection<BlackboardAttribute> webAttr = 
new ArrayList<>();
 
  527             String url = cacheEntry.getKey() != null ? cacheEntry.getKey() : 
"";
 
  528             webAttr.add(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL,
 
  530             webAttr.add(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN,
 
  531                     moduleName, NetworkUtils.extractDomain(url)));
 
  532             webAttr.add(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED,
 
  533                     moduleName, cacheEntry.getCreationTime()));
 
  534             webAttr.add(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_HEADERS,
 
  535                     moduleName, cacheEntry.getHTTPHeaders()));  
 
  536             webAttr.add(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH,
 
  537                     moduleName, cachedItemFile.getUniquePath()));
 
  538             webAttr.add(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_ID,
 
  539                     moduleName, cachedItemFile.getId()));
 
  540             webCacheArtifact.addAttributes(webAttr);
 
  541             artifactsAdded.add(webCacheArtifact);
 
  544             BlackboardArtifact associatedObjectArtifact = cachedItemFile.newArtifact(ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT);
 
  545             if (associatedObjectArtifact != null) {
 
  546                 associatedObjectArtifact.addAttribute(
 
  547                             new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT,
 
  548                                     moduleName, webCacheArtifact.getArtifactID()));
 
  549                 artifactsAdded.add(associatedObjectArtifact);
 
  562     private void findExternalFiles(String cachePath) 
throws TskCoreException {
 
  564         List<AbstractFile> effFiles = fileManager.findFiles(dataSource, 
"f_%", cachePath); 
 
  565         for (AbstractFile abstractFile : effFiles ) {
 
  566             if (cachePath.equals(abstractFile.getParentPath()) && abstractFile.isFile()) {
 
  567                 this.externalFilesTable.put(cachePath + abstractFile.getName(), abstractFile);
 
  579     private Optional<AbstractFile> findAbstractFile(String cacheFileName, String cacheFolderName) 
throws TskCoreException {
 
  582         String fileTableKey = cacheFolderName + cacheFileName;
 
  583         if (cacheFileName.startsWith(
"f_") && externalFilesTable.containsKey(fileTableKey)) {
 
  584             return Optional.of(externalFilesTable.get(fileTableKey));
 
  587         if (fileCopyCache.containsKey(fileTableKey)) {
 
  588             return Optional.of(fileCopyCache.get(fileTableKey).getAbstractFile());
 
  592         List<AbstractFile> cacheFiles = fileManager.findFiles(dataSource, cacheFileName, cacheFolderName); 
 
  593         if (!cacheFiles.isEmpty()) {
 
  594             for (AbstractFile abstractFile: cacheFiles ) {
 
  595                 if (abstractFile.getUniquePath().trim().endsWith(DEFAULT_CACHE_PATH_STR)) {
 
  596                     return Optional.of(abstractFile);
 
  599             return Optional.of(cacheFiles.get(0));
 
  602         return Optional.empty(); 
 
  612     private List<AbstractFile> findIndexFiles() throws TskCoreException {
 
  613         return fileManager.findFiles(dataSource, 
"index", DEFAULT_CACHE_PATH_STR); 
 
  629     private Optional<FileWrapper> findDataOrIndexFile(String cacheFileName, String cacheFolderName) 
throws TskCoreException, IngestModuleException  {
 
  632         String fileTableKey = cacheFolderName + cacheFileName;
 
  633         if (fileCopyCache.containsKey(fileTableKey)) {
 
  634             return Optional.of(fileCopyCache.get(fileTableKey));
 
  638         Optional<AbstractFile> abstractFileOptional = findAbstractFile(cacheFileName, cacheFolderName);
 
  639         if (!abstractFileOptional.isPresent()) {
 
  640             return Optional.empty(); 
 
  648         AbstractFile cacheFile = abstractFileOptional.get();
 
  649         RandomAccessFile randomAccessFile = null;
 
  650         String tempFilePathname = RAImageIngestModule.getRATempPath(currentCase, moduleName) + cacheFolderName + cacheFile.getName(); 
 
  652             File newFile = 
new File(tempFilePathname);
 
  653             ContentUtils.writeToFile(cacheFile, newFile, context::dataSourceIngestIsCancelled);
 
  655             randomAccessFile = 
new RandomAccessFile(tempFilePathname, 
"r");
 
  656             FileChannel roChannel = randomAccessFile.getChannel();
 
  657             ByteBuffer cacheFileROBuf = roChannel.map(FileChannel.MapMode.READ_ONLY, 0,
 
  658                                                         (
int) roChannel.size());
 
  660             cacheFileROBuf.order(ByteOrder.nativeOrder());
 
  661             FileWrapper cacheFileWrapper = 
new FileWrapper(cacheFile, randomAccessFile, cacheFileROBuf );
 
  663             if (!cacheFileName.startsWith(
"f_")) {
 
  664                 fileCopyCache.put(cacheFolderName + cacheFileName, cacheFileWrapper);
 
  667             return Optional.of(cacheFileWrapper);
 
  669         catch (IOException ex) {
 
  672                 if (randomAccessFile != null) {
 
  673                     randomAccessFile.close();
 
  676             catch (IOException ex2) {
 
  677                 logger.log(Level.SEVERE, 
"Error while trying to close temp file after exception.", ex2); 
 
  679             String msg = String.format(
"Error reading/copying Chrome cache file '%s' (id=%d).", 
 
  680                                             cacheFile.getName(), cacheFile.getId()); 
 
  681             throw new IngestModuleException(msg, ex);
 
  688     final class IndexFileHeader {
 
  690         private final long magic;
 
  691         private final int version;
 
  692         private final int numEntries;
 
  693         private final int numBytes;
 
  694         private final int lastFile;
 
  695         private final int tableLen;
 
  697         IndexFileHeader(ByteBuffer indexFileROBuf) {
 
  699             magic = indexFileROBuf.getInt() & UINT32_MASK; 
 
  701             indexFileROBuf.position(indexFileROBuf.position()+2);
 
  703             version = indexFileROBuf.getShort();
 
  704             numEntries = indexFileROBuf.getInt();
 
  705             numBytes = indexFileROBuf.getInt();
 
  706             lastFile = indexFileROBuf.getInt();
 
  708             indexFileROBuf.position(indexFileROBuf.position()+4); 
 
  709             indexFileROBuf.position(indexFileROBuf.position()+4); 
 
  711             tableLen = indexFileROBuf.getInt();
 
  714         public long getMagic() {
 
  718         public int getVersion() {
 
  722         public int getNumEntries() {
 
  726         public int getNumBytes() {
 
  730         public int getLastFile() {
 
  734         public int getTableLen() {
 
  739         public String toString() {
 
  740             StringBuilder sb = 
new StringBuilder();
 
  742             sb.append(String.format(
"Index Header:"))
 
  743                 .append(String.format(
"\tMagic = %x" , getMagic()) )
 
  744                 .append(String.format(
"\tVersion = %x" , getVersion()) )
 
  745                 .append(String.format(
"\tNumEntries = %x" , getNumEntries()) )
 
  746                 .append(String.format(
"\tNumBytes = %x" , getNumBytes()) )
 
  747                 .append(String.format(
"\tLastFile = %x" , getLastFile()) )
 
  748                 .append(String.format(
"\tTableLen = %x" , getTableLen()) );
 
  750             return sb.toString();
 
  757     enum CacheFileTypeEnum {
 
  792     final class CacheAddress {
 
  794         private static final long ADDR_INITIALIZED_MASK    = 0x80000000l;
 
  795         private static final long FILE_TYPE_MASK     = 0x70000000;
 
  796         private static final long FILE_TYPE_OFFSET   = 28;
 
  797         private static final long NUM_BLOCKS_MASK     = 0x03000000;
 
  798         private static final long NUM_BLOCKS_OFFSET   = 24;
 
  799         private static final long FILE_SELECTOR_MASK   = 0x00ff0000;
 
  800         private static final long FILE_SELECTOR_OFFSET = 16;
 
  801         private static final long START_BLOCK_MASK     = 0x0000FFFF;
 
  802         private static final long EXTERNAL_FILE_NAME_MASK = 0x0FFFFFFF;
 
  804         private final long uint32CacheAddr;
 
  805         private final CacheFileTypeEnum fileType;
 
  806         private final int numBlocks;
 
  807         private final int startBlock;
 
  808         private final String fileName;
 
  809         private final int fileNumber;
 
  811         private final String cachePath;
 
  819         CacheAddress(
long uint32, String cachePath) {
 
  821             uint32CacheAddr = uint32;
 
  822             this.cachePath = cachePath;
 
  826             int fileTypeEnc = (int)(uint32CacheAddr &  FILE_TYPE_MASK) >> FILE_TYPE_OFFSET;
 
  827             fileType = CacheFileTypeEnum.values()[fileTypeEnc];
 
  829             if (isInitialized()) {
 
  830                 if (isInExternalFile()) {
 
  831                     fileNumber = (int)(uint32CacheAddr & EXTERNAL_FILE_NAME_MASK);
 
  832                     fileName =  String.format(
"f_%06x", getFileNumber() );
 
  836                     fileNumber = (int)((uint32CacheAddr & FILE_SELECTOR_MASK) >> FILE_SELECTOR_OFFSET);
 
  837                     fileName = String.format(
"data_%d", getFileNumber() );
 
  838                     numBlocks = (int)(uint32CacheAddr &  NUM_BLOCKS_MASK >> NUM_BLOCKS_OFFSET);
 
  839                     startBlock = (int)(uint32CacheAddr &  START_BLOCK_MASK);
 
  850         boolean isInitialized() {
 
  851             return ((uint32CacheAddr & ADDR_INITIALIZED_MASK) != 0);
 
  854         CacheFileTypeEnum getFileType() {
 
  862         String getFilename() {
 
  866         String getCachePath() {
 
  870         boolean isInExternalFile() {
 
  871             return (fileType == CacheFileTypeEnum.EXTERNAL);
 
  874         int getFileNumber() {
 
  878         int getStartBlock() {
 
  907         public long getUint32CacheAddr() {
 
  908             return uint32CacheAddr;
 
  912         public String toString() {
 
  913             StringBuilder sb = 
new StringBuilder();
 
  914             sb.append(String.format(
"CacheAddr %08x : %s : filename %s", 
 
  916                                     isInitialized() ? 
"Initialized" : 
"UnInitialized",
 
  919             if ((fileType == CacheFileTypeEnum.BLOCK_256) || 
 
  920                 (fileType == CacheFileTypeEnum.BLOCK_1K) || 
 
  921                 (fileType == CacheFileTypeEnum.BLOCK_4K) ) {
 
  922                 sb.append(String.format(
" (%d blocks starting at %08X)", 
 
  928             return sb.toString();
 
  936     enum CacheDataTypeEnum {
 
  950     final class CacheDataSegment {
 
  953         private final CacheAddress cacheAddress;
 
  954         private CacheDataTypeEnum type;
 
  956         private boolean isHTTPHeaderHint;
 
  958         private FileWrapper cacheFileCopy = null;
 
  959         private byte[] data = null;
 
  961         private String httpResponse;
 
  962         private final Map<String, String> httpHeaders = 
new HashMap<>();
 
  964         CacheDataSegment(CacheAddress cacheAddress, 
int len) {
 
  965             this(cacheAddress, len, 
false);
 
  968         CacheDataSegment(CacheAddress cacheAddress, 
int len, 
boolean isHTTPHeader ) {
 
  969             this.type = CacheDataTypeEnum.UNKNOWN;
 
  971             this.cacheAddress = cacheAddress;
 
  972             this.isHTTPHeaderHint = isHTTPHeader;
 
  975         boolean isInExternalFile() {
 
  976             return cacheAddress.isInExternalFile();
 
  979         boolean hasHTTPHeaders() {
 
  980             return this.type == CacheDataTypeEnum.HTTP_HEADER;
 
  983         String getHTTPHeader(String key) {
 
  984             return this.httpHeaders.get(key);
 
  992         String getHTTPHeaders() {
 
  993             if (!hasHTTPHeaders()) {
 
  997             StringBuilder sb = 
new StringBuilder();
 
  998             httpHeaders.entrySet().forEach((entry) -> {
 
  999                 if (sb.length() > 0) {
 
 1002                 sb.append(String.format(
"%s : %s",
 
 1003                         entry.getKey(), entry.getValue()));
 
 1006             return sb.toString();
 
 1009         String getHTTPRespone() {
 
 1010             return httpResponse;
 
 1018         void extract() throws TskCoreException, IngestModuleException {
 
 1026             if (!cacheAddress.isInExternalFile()) {
 
 1028                 if (cacheAddress.getFilename() == null) {
 
 1029                     throw new TskCoreException(
"Cache address has no file name");
 
 1032                 cacheFileCopy = findDataOrIndexFile(cacheAddress.getFilename(), cacheAddress.getCachePath()).
get();
 
 1034                 this.data = 
new byte [length];
 
 1035                 ByteBuffer buf = cacheFileCopy.getByteBuffer();
 
 1036                 int dataOffset = DATAFILE_HDR_SIZE + cacheAddress.getStartBlock() * cacheAddress.getBlockSize();
 
 1037                 buf.position(dataOffset);
 
 1038                 buf.get(data, 0, length);
 
 1041                 if ((isHTTPHeaderHint)) {
 
 1042                     String strData = 
new String(data);
 
 1043                     if (strData.contains(
"HTTP")) {
 
 1054                         type = CacheDataTypeEnum.HTTP_HEADER;
 
 1056                         int startOff = strData.indexOf(
"HTTP");
 
 1057                         Charset charset = Charset.forName(
"UTF-8");
 
 1058                         boolean done = 
false;
 
 1065                             while (i < data.length && data[i] != 0)  {
 
 1070                             if (i == data.length || data[i+1] == 0) {
 
 1074                             int len = (i - start);
 
 1075                             String headerLine = 
new String(data, start, len, charset);
 
 1079                                 httpResponse = headerLine;
 
 1081                                 int nPos = headerLine.indexOf(
':');
 
 1083                                     String key = headerLine.substring(0, nPos);
 
 1084                                     String val= headerLine.substring(nPos+1);
 
 1085                                     httpHeaders.put(key.toLowerCase(), val);
 
 1097         String getDataString() throws TskCoreException, IngestModuleException {
 
 1101             return new String(data);
 
 1104         byte[] getDataBytes() throws TskCoreException, IngestModuleException {
 
 1108             return data.clone();
 
 1111         int getDataLength() {
 
 1115         CacheDataTypeEnum getType() {
 
 1119         CacheAddress getCacheAddress() {
 
 1120             return cacheAddress;
 
 1132         String save() throws TskCoreException, IngestModuleException {
 
 1135             if (cacheAddress.isInExternalFile()) {
 
 1136                 fileName = cacheAddress.getFilename();
 
 1138                 fileName = String.format(
"%s__%08x", cacheAddress.getFilename(), cacheAddress.getUint32CacheAddr());
 
 1141             String filePathName = getAbsOutputFolderName() + cacheAddress.getCachePath() + fileName;
 
 1156         void save(String filePathName) 
throws TskCoreException, IngestModuleException {
 
 1164             if (!this.isInExternalFile()) {
 
 1166                 try (FileOutputStream stream = 
new FileOutputStream(filePathName)) {
 
 1168                 } 
catch (IOException ex) {
 
 1169                     throw new TskCoreException(String.format(
"Failed to write output file %s", filePathName), ex);
 
 1175         public String toString() {
 
 1176             StringBuilder strBuilder = 
new StringBuilder();
 
 1177             strBuilder.append(String.format(
"\t\tData type = : %s, Data Len = %d ", 
 
 1178                                     this.type.toString(), this.length ));
 
 1180             if (hasHTTPHeaders()) {
 
 1181                 String str = getHTTPHeader(
"content-encoding");
 
 1183                     strBuilder.append(String.format(
"\t%s=%s", 
"content-encoding", str ));
 
 1187             return strBuilder.toString(); 
 
 1196     enum EntryStateEnum {
 
 1235     final class CacheEntry {
 
 1238         private static final int MAX_KEY_LEN = 256-24*4; 
 
 1240         private final CacheAddress selfAddress; 
 
 1241         private final FileWrapper cacheFileCopy;
 
 1243         private final long hash;
 
 1244         private final CacheAddress nextAddress;
 
 1245         private final CacheAddress rankingsNodeAddress;
 
 1247         private final int reuseCount;
 
 1248         private final int refetchCount;
 
 1249         private final EntryStateEnum state;
 
 1251         private final long creationTime;
 
 1252         private final int keyLen;
 
 1254         private final CacheAddress longKeyAddresses; 
 
 1256         private final int[] dataSegmentSizes;
 
 1257         private final CacheAddress[] dataSegmentIndexFileEntries;
 
 1258         private List<CacheDataSegment> dataSegments;
 
 1260         private final long flags;
 
 1264         CacheEntry(CacheAddress cacheAdress, FileWrapper cacheFileCopy ) 
throws TskCoreException {
 
 1265             this.selfAddress = cacheAdress;
 
 1266             this.cacheFileCopy = cacheFileCopy;
 
 1268             ByteBuffer fileROBuf = cacheFileCopy.getByteBuffer();
 
 1270             int entryOffset = DATAFILE_HDR_SIZE + cacheAdress.getStartBlock() * cacheAdress.getBlockSize();
 
 1273             fileROBuf.position(entryOffset);
 
 1275             hash = fileROBuf.getInt() & UINT32_MASK;
 
 1277             long uint32 = fileROBuf.getInt() & UINT32_MASK;
 
 1278             nextAddress = (uint32 != 0) ?  
new CacheAddress(uint32, selfAddress.getCachePath()) : null;  
 
 1280             uint32 = fileROBuf.getInt() & UINT32_MASK;
 
 1281             rankingsNodeAddress = (uint32 != 0) ?  
new CacheAddress(uint32, selfAddress.getCachePath()) : null;  
 
 1283             reuseCount = fileROBuf.getInt();
 
 1284             refetchCount = fileROBuf.getInt();
 
 1286             int stateVal = fileROBuf.getInt();
 
 1287             if ((stateVal >= 0) && (stateVal < EntryStateEnum.values().length)) {
 
 1288                 state = EntryStateEnum.values()[stateVal];
 
 1290                 throw new TskCoreException(
"Invalid EntryStateEnum value"); 
 
 1292             creationTime = (fileROBuf.getLong() / 1000000) - Long.valueOf(
"11644473600");
 
 1294             keyLen = fileROBuf.getInt();
 
 1296             uint32 = fileROBuf.getInt() & UINT32_MASK;
 
 1297             longKeyAddresses = (uint32 != 0) ?  
new CacheAddress(uint32, selfAddress.getCachePath()) : null;  
 
 1299             dataSegments = null;
 
 1300             dataSegmentSizes= 
new int[4];
 
 1301             for (
int i = 0; i < 4; i++)  {
 
 1302                 dataSegmentSizes[i] = fileROBuf.getInt();
 
 1304             dataSegmentIndexFileEntries = 
new CacheAddress[4];
 
 1305             for (
int i = 0; i < 4; i++)  {
 
 1306                 dataSegmentIndexFileEntries[i] =  
new CacheAddress(fileROBuf.getInt() & UINT32_MASK, selfAddress.getCachePath());
 
 1309             flags = fileROBuf.getInt() & UINT32_MASK;
 
 1311             for (
int i = 0; i < 4; i++)  {
 
 1319             if (longKeyAddresses != null) {
 
 1322                     CacheDataSegment data = 
new CacheDataSegment(longKeyAddresses, this.keyLen, 
true);
 
 1323                     key = data.getDataString();
 
 1324                 } 
catch (TskCoreException | IngestModuleException ex) {
 
 1325                     throw new TskCoreException(String.format(
"Failed to get external key from address %s", longKeyAddresses)); 
 
 1329                 StringBuilder strBuilder = 
new StringBuilder(MAX_KEY_LEN);
 
 1331                 while (fileROBuf.remaining() > 0  && keyLen < MAX_KEY_LEN)  {
 
 1332                     char keyChar = (char)fileROBuf.get();
 
 1333                     if (keyChar == 
'\0') { 
 
 1336                     strBuilder.append(keyChar);
 
 1340                 key = strBuilder.toString();
 
 1344         public CacheAddress getCacheAddress() {
 
 1348         public long getHash() {
 
 1352         public CacheAddress getNextCacheAddress() {
 
 1356         public int getReuseCount() {
 
 1360         public int getRefetchCount() {
 
 1361             return refetchCount;
 
 1364         public EntryStateEnum getState() {
 
 1368         public long getCreationTime() {
 
 1369             return creationTime;
 
 1372         public long getFlags() {
 
 1376         public String getKey() {
 
 1388         public List<CacheDataSegment> getDataSegments() throws TskCoreException, IngestModuleException {
 
 1390             if (dataSegments == null) { 
 
 1391                 dataSegments = 
new ArrayList<>();
 
 1392                  for (
int i = 0; i < 4; i++)  {
 
 1393                      if (dataSegmentSizes[i] > 0) {
 
 1394                          CacheDataSegment cacheData = 
new CacheDataSegment(dataSegmentIndexFileEntries[i], dataSegmentSizes[i], 
true );
 
 1396                          cacheData.extract();
 
 1397                          dataSegments.add(cacheData);
 
 1401             return dataSegments; 
 
 1411         boolean hasHTTPHeaders() {
 
 1412             if ((dataSegments == null) || dataSegments.isEmpty()) {
 
 1415             return dataSegments.get(0).hasHTTPHeaders();
 
 1424         String getHTTPHeader(String key) {
 
 1425             if ((dataSegments == null) || dataSegments.isEmpty()) {
 
 1429             return dataSegments.get(0).getHTTPHeader(key);
 
 1437         String getHTTPHeaders() {
 
 1438             if ((dataSegments == null) || dataSegments.isEmpty()) {
 
 1442             return dataSegments.get(0).getHTTPHeaders();
 
 1453         boolean isBrotliCompressed() {
 
 1455             if (hasHTTPHeaders() ) {
 
 1456                 String encodingHeader = getHTTPHeader(
"content-encoding");
 
 1457                 if (encodingHeader!= null) {
 
 1458                     return encodingHeader.trim().equalsIgnoreCase(
"br");
 
 1466         public String toString() {
 
 1467             StringBuilder sb = 
new StringBuilder();
 
 1468             sb.append(String.format(
"Entry = Hash: %08x,  State: %s, ReuseCount: %d, RefetchCount: %d", 
 
 1469                                     this.hash, 
this.state.toString(), this.reuseCount, this.refetchCount ))
 
 1470                 .append(String.format(
"\n\tKey: %s, Keylen: %d", 
 
 1471                                     this.key, 
this.keyLen, 
this.reuseCount, 
this.refetchCount ))
 
 1472                 .append(String.format(
"\n\tCreationTime: %s", 
 
 1473                                     TimeUtilities.epochToTime(
this.creationTime) ))
 
 1474                 .append(String.format(
"\n\tNext Address: %s", 
 
 1475                                     (nextAddress != null) ? nextAddress.toString() : 
"None"));
 
 1477             for (
int i = 0; i < 4; i++) {
 
 1478                 if (dataSegmentSizes[i] > 0) {
 
 1479                     sb.append(String.format(
"\n\tData %d: cache address = %s, Data = %s", 
 
 1480                                          i, dataSegmentIndexFileEntries[i].toString(), 
 
 1481                                          (dataSegments != null)
 
 1482                                                  ? dataSegments.get(i).toString() 
 
 1483                                                  : 
"Data not retrived yet."));
 
 1487             return sb.toString();