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;
 
   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         "# {0} - module name",
 
  155         "# {1} - row number",
 
  156         "# {2} - table length",
 
  157         "# {3} - cache path",
 
  158         "ChromeCacheExtractor.progressMsg={0}: Extracting cache entry {1} of {2} entries from {3}" 
  160     ChromeCacheExtractor(Content dataSource, IngestJobContext context, DataSourceIngestModuleProgress progressBar) { 
 
  161         moduleName = NbBundle.getMessage(Chromium.class, 
"Chrome.moduleName");
 
  162         this.dataSource = dataSource;
 
  163         this.context = context;
 
  164         this.progressBar = progressBar;
 
  173     private void moduleInit() throws IngestModuleException {
 
  176             currentCase = Case.getCurrentCaseThrows();
 
  177             fileManager = currentCase.getServices().getFileManager();
 
  179         } 
catch (NoCurrentCaseException ex) {
 
  180             String msg = 
"Failed to get current case."; 
 
  181             throw new IngestModuleException(msg, ex);
 
  192     private void resetForNewCacheFolder(String cachePath) 
throws IngestModuleException {
 
  194         fileCopyCache.clear();
 
  195         externalFilesTable.clear();
 
  197         String cacheAbsOutputFolderName = this.getAbsOutputFolderName() + cachePath;
 
  198         File outDir = 
new File(cacheAbsOutputFolderName);
 
  199         if (outDir.exists() == 
false) {
 
  203         String cacheTempPath = RAImageIngestModule.getRATempPath(currentCase, moduleName, context.getJobId()) + cachePath;
 
  204         File tempDir = 
new File(cacheTempPath);
 
  205         if (tempDir.exists() == 
false) {
 
  216     private void cleanup () {
 
  218         for (Entry<String, FileWrapper> entry : this.fileCopyCache.entrySet()) {
 
  219             Path tempFilePath = Paths.get(RAImageIngestModule.getRATempPath(currentCase, moduleName, context.getJobId()), entry.getKey() ); 
 
  221                 entry.getValue().getFileCopy().getChannel().close();
 
  222                 entry.getValue().getFileCopy().close();
 
  224                 File tmpFile = tempFilePath.toFile();
 
  225                 if (!tmpFile.delete()) {
 
  226                     tmpFile.deleteOnExit();
 
  228             } 
catch (IOException ex) {
 
  229                 logger.log(Level.WARNING, String.format(
"Failed to delete cache file copy %s", tempFilePath.toString()), ex); 
 
  239     private String getAbsOutputFolderName() {
 
  240         return absOutputFolderName;
 
  248     private String getRelOutputFolderName() {
 
  249         return relOutputFolderName;
 
  258     void processCaches() {
 
  262         } 
catch (IngestModuleException ex) {
 
  263             String msg = 
"Failed to initialize ChromeCacheExtractor."; 
 
  264             logger.log(Level.SEVERE, msg, ex);
 
  271             List<AbstractFile> indexFiles = findIndexFiles(); 
 
  273             if (indexFiles.size() > 0) {
 
  275                 absOutputFolderName = RAImageIngestModule.getRAOutputPath(currentCase, moduleName, context.getJobId());
 
  276                 relOutputFolderName = Paths.get(RAImageIngestModule.getRelModuleOutputPath(currentCase, moduleName, context.getJobId())).normalize().toString();
 
  278                 File dir = 
new File(absOutputFolderName);
 
  279                 if (dir.exists() == 
false) {
 
  285             for (AbstractFile indexFile: indexFiles) {  
 
  287                 if (context.dataSourceIngestIsCancelled()) {
 
  291                 if (indexFile.getSize() > 0) {
 
  292                     processCacheFolder(indexFile);
 
  296         } 
catch (TskCoreException ex) {
 
  297                 String msg = 
"Failed to find cache index files"; 
 
  298                 logger.log(Level.WARNING, msg, ex);
 
  303         "ChromeCacheExtract_adding_extracted_files_msg=Chrome Cache: Adding %d extracted files for analysis.",
 
  304         "ChromeCacheExtract_adding_artifacts_msg=Chrome Cache: Adding %d artifacts for analysis.",
 
  305         "ChromeCacheExtract_loading_files_msg=Chrome Cache: Loading files from %s." 
  314     private void processCacheFolder(AbstractFile indexFile) {
 
  316         String cacheFolderName = indexFile.getParentPath();
 
  317         Optional<FileWrapper> indexFileWrapper;
 
  325             progressBar.progress(String.format(Bundle.ChromeCacheExtract_loading_files_msg(), cacheFolderName));
 
  326             resetForNewCacheFolder(cacheFolderName);
 
  330             indexFileWrapper = findDataOrIndexFile(indexFile.getName(), cacheFolderName);
 
  331             if (!indexFileWrapper.isPresent()) {
 
  332                 String msg = String.format(
"Failed to find copy cache index file %s", indexFile.getUniquePath());
 
  333                 logger.log(Level.WARNING, msg);
 
  340             for (
int i = 0; i < 4; i ++)  {
 
  341                 Optional<FileWrapper> dataFile = findDataOrIndexFile(String.format(
"data_%1d",i), cacheFolderName );
 
  342                 if (!dataFile.isPresent()) {
 
  349             findExternalFiles(cacheFolderName);
 
  351         } 
catch (TskCoreException | IngestModuleException ex) {
 
  352             String msg = 
"Failed to find cache files in path " + cacheFolderName; 
 
  353             logger.log(Level.WARNING, msg, ex);
 
  361         logger.log(Level.INFO, 
"{0}- Now reading Cache index file from path {1}", 
new Object[]{moduleName, cacheFolderName }); 
 
  363         List<AbstractFile> derivedFiles = 
new ArrayList<>();
 
  364         Collection<BlackboardArtifact> artifactsAdded = 
new ArrayList<>();
 
  366         ByteBuffer indexFileROBuffer = indexFileWrapper.get().getByteBuffer();
 
  367         IndexFileHeader indexHdr = 
new IndexFileHeader(indexFileROBuffer);
 
  370         indexFileROBuffer.position(INDEXFILE_HDR_SIZE);
 
  375             for (
int i = 0; i <  indexHdr.getTableLen(); i++) {
 
  377                 if (context.dataSourceIngestIsCancelled()) {
 
  382                 CacheAddress addr = 
new CacheAddress(indexFileROBuffer.getInt() & UINT32_MASK, cacheFolderName);
 
  383                 if (addr.isInitialized()) {
 
  384                     progressBar.progress(NbBundle.getMessage(
this.getClass(),
 
  385                                             "ChromeCacheExtractor.progressMsg",
 
  386                                             moduleName, i, indexHdr.getTableLen(), cacheFolderName)  );
 
  388                         List<DerivedFile> addedFiles = processCacheEntry(addr, artifactsAdded);
 
  389                         derivedFiles.addAll(addedFiles);
 
  391                     catch (TskCoreException | IngestModuleException ex) {
 
  392                        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())); 
 
  396         } 
catch (java.nio.BufferUnderflowException ex) {
 
  397             logger.log(Level.WARNING, String.format(
"Ran out of data unexpectedly reading file %s (ObjID: %d)", indexFile.getName(), indexFile.getId()));
 
  400         if (context.dataSourceIngestIsCancelled()) {
 
  407         progressBar.progress(String.format(Bundle.ChromeCacheExtract_adding_extracted_files_msg(), derivedFiles.size()));
 
  408         derivedFiles.forEach((derived) -> {
 
  409             services.fireModuleContentEvent(
new ModuleContentEvent(derived));
 
  411         context.addFilesToJob(derivedFiles);
 
  414         progressBar.progress(String.format(Bundle.ChromeCacheExtract_adding_artifacts_msg(), artifactsAdded.size()));
 
  415         Blackboard blackboard = currentCase.getSleuthkitCase().getBlackboard();
 
  417             blackboard.postArtifacts(artifactsAdded, moduleName, context.getJobId());
 
  418         } 
catch (Blackboard.BlackboardException ex) {
 
  419            logger.log(Level.WARNING, String.format(
"Failed to post cacheIndex artifacts "), ex); 
 
  437     private List<DerivedFile> processCacheEntry(CacheAddress cacheAddress, Collection<BlackboardArtifact> artifactsAdded ) 
throws TskCoreException, IngestModuleException {
 
  439         List<DerivedFile> derivedFiles = 
new ArrayList<>();
 
  442         String cacheEntryFileName = cacheAddress.getFilename(); 
 
  443         String cachePath = cacheAddress.getCachePath();
 
  445         Optional<FileWrapper> cacheEntryFileOptional = findDataOrIndexFile(cacheEntryFileName, cachePath);
 
  446         if (!cacheEntryFileOptional.isPresent()) {
 
  447             String msg = String.format(
"Failed to find data file %s", cacheEntryFileName); 
 
  448             throw new IngestModuleException(msg);
 
  452         CacheEntry cacheEntry = 
new CacheEntry(cacheAddress, cacheEntryFileOptional.get() );
 
  453         List<CacheDataSegment> dataSegments = cacheEntry.getDataSegments();
 
  457         if (dataSegments.size() < 2) {
 
  460         CacheDataSegment dataSegment = dataSegments.get(1);
 
  463         String segmentFileName = dataSegment.getCacheAddress().getFilename();
 
  464         Optional<AbstractFile> segmentFileAbstractFile = findAbstractFile(segmentFileName, cachePath);
 
  465         if (!segmentFileAbstractFile.isPresent()) {
 
  466             logger.log(Level.WARNING, 
"Error finding segment file: " + cachePath + 
"/" + segmentFileName); 
 
  470         boolean isBrotliCompressed = 
false;
 
  471         if (dataSegment.getType() != CacheDataTypeEnum.HTTP_HEADER && cacheEntry.isBrotliCompressed() ) {
 
  472             isBrotliCompressed = 
true;
 
  478             AbstractFile cachedItemFile; 
 
  480             if (dataSegment.isInExternalFile() )  {
 
  481                 cachedItemFile = segmentFileAbstractFile.get();
 
  487                 String filename = dataSegment.save();
 
  488                 String relPathname = getRelOutputFolderName() + dataSegment.getCacheAddress().getCachePath() + filename; 
 
  491                 DerivedFile derivedFile = fileManager.addDerivedFile(filename, relPathname,
 
  492                                                     dataSegment.getDataLength(), 
 
  493                                                     cacheEntry.getCreationTime(), cacheEntry.getCreationTime(), cacheEntry.getCreationTime(), cacheEntry.getCreationTime(), 
 
  495                                                     segmentFileAbstractFile.get(), 
 
  500                                                     TskData.EncodingType.NONE);
 
  502                 derivedFiles.add(derivedFile);
 
  503                 cachedItemFile = derivedFile;
 
  506             addArtifacts(cacheEntry, cacheEntryFileOptional.get().getAbstractFile(), cachedItemFile, artifactsAdded);
 
  509             if (isBrotliCompressed) {
 
  510                 cachedItemFile.setMIMEType(BROTLI_MIMETYPE);
 
  511                 cachedItemFile.save();
 
  514         } 
catch (TskException ex) {
 
  515             logger.log(Level.SEVERE, 
"Error while trying to add an artifact", ex); 
 
  530     private void addArtifacts(CacheEntry cacheEntry, AbstractFile cacheEntryFile, AbstractFile cachedItemFile, Collection<BlackboardArtifact> artifactsAdded) 
throws TskCoreException {
 
  533         Collection<BlackboardAttribute> webAttr = 
new ArrayList<>();
 
  534         String url = cacheEntry.getKey() != null ? cacheEntry.getKey() : 
"";
 
  535         webAttr.add(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL,
 
  537         webAttr.add(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN,
 
  538                 moduleName, NetworkUtils.extractDomain(url)));
 
  539         webAttr.add(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED,
 
  540                 moduleName, cacheEntry.getCreationTime()));
 
  541         webAttr.add(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_HEADERS,
 
  542                 moduleName, cacheEntry.getHTTPHeaders()));  
 
  543         webAttr.add(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH,
 
  544                 moduleName, cachedItemFile.getUniquePath()));
 
  545         webAttr.add(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_ID,
 
  546                 moduleName, cachedItemFile.getId()));
 
  548         BlackboardArtifact webCacheArtifact = cacheEntryFile.newDataArtifact(
new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_WEB_CACHE), webAttr);
 
  549         artifactsAdded.add(webCacheArtifact);
 
  552         BlackboardArtifact associatedObjectArtifact = cachedItemFile.newDataArtifact(
 
  553                 new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT), 
 
  554                 Arrays.asList(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT, 
 
  555                         moduleName, webCacheArtifact.getArtifactID())));
 
  557         artifactsAdded.add(associatedObjectArtifact);
 
  568     private void findExternalFiles(String cachePath) 
throws TskCoreException {
 
  570         List<AbstractFile> effFiles = fileManager.findFiles(dataSource, 
"f_%", cachePath); 
 
  571         for (AbstractFile abstractFile : effFiles ) {
 
  572             String cacheKey = cachePath + abstractFile.getName();
 
  573             if (cachePath.equals(abstractFile.getParentPath()) && abstractFile.isFile()) {
 
  575                 if (abstractFile.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC)
 
  576                         || !externalFilesTable.containsKey(cacheKey)) {
 
  577                     this.externalFilesTable.put(cacheKey, abstractFile);
 
  590     private Optional<AbstractFile> findAbstractFile(String cacheFileName, String cacheFolderName) 
throws TskCoreException {
 
  593         String fileTableKey = cacheFolderName + cacheFileName;
 
  595         if (cacheFileName != null) {
 
  596             if (cacheFileName.startsWith(
"f_") && externalFilesTable.containsKey(fileTableKey)) {
 
  597                 return Optional.of(externalFilesTable.get(fileTableKey));
 
  600             return Optional.empty();
 
  603         if (fileCopyCache.containsKey(fileTableKey)) {
 
  604             return Optional.of(fileCopyCache.get(fileTableKey).getAbstractFile());
 
  607         List<AbstractFile> cacheFiles = currentCase.getSleuthkitCase().getFileManager().findFilesExactNameExactPath(dataSource, 
 
  608                 cacheFileName, cacheFolderName);
 
  609         if (!cacheFiles.isEmpty()) {
 
  614             Collections.sort(cacheFiles, 
new Comparator<AbstractFile>() {
 
  616                 public int compare(AbstractFile file1, AbstractFile file2) {
 
  618                         if (file1.getUniquePath().trim().endsWith(DEFAULT_CACHE_PATH_STR)
 
  619                                 && ! file2.getUniquePath().trim().endsWith(DEFAULT_CACHE_PATH_STR)) {
 
  623                         if (file2.getUniquePath().trim().endsWith(DEFAULT_CACHE_PATH_STR)
 
  624                                 && ! file1.getUniquePath().trim().endsWith(DEFAULT_CACHE_PATH_STR)) {
 
  627                     } 
catch (TskCoreException ex) {
 
  628                         logger.log(Level.WARNING, 
"Error getting unique path for file with ID " + file1.getId() + 
" or " + file2.getId(), ex);
 
  631                     if (file1.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC)
 
  632                             && ! file2.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC)) {
 
  635                     if (file2.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC)
 
  636                             && ! file1.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC)) {
 
  640                     return Long.compare(file1.getId(), file2.getId());
 
  645             return Optional.of(cacheFiles.get(0));
 
  648         return Optional.empty(); 
 
  658     private List<AbstractFile> findIndexFiles() throws TskCoreException {
 
  659         return fileManager.findFiles(dataSource, 
"index", DEFAULT_CACHE_PATH_STR); 
 
  675     private Optional<FileWrapper> findDataOrIndexFile(String cacheFileName, String cacheFolderName) 
throws TskCoreException, IngestModuleException  {
 
  678         String fileTableKey = cacheFolderName + cacheFileName;
 
  679         if (fileCopyCache.containsKey(fileTableKey)) {
 
  680             return Optional.of(fileCopyCache.get(fileTableKey));
 
  684         Optional<AbstractFile> abstractFileOptional = findAbstractFile(cacheFileName, cacheFolderName);
 
  685         if (!abstractFileOptional.isPresent()) {
 
  686             return Optional.empty(); 
 
  694         AbstractFile cacheFile = abstractFileOptional.get();
 
  695         RandomAccessFile randomAccessFile = null;
 
  696         String tempFilePathname = RAImageIngestModule.getRATempPath(currentCase, moduleName, context.getJobId()) + cacheFolderName + cacheFile.getName(); 
 
  698             File newFile = 
new File(tempFilePathname);
 
  699             ContentUtils.writeToFile(cacheFile, newFile, context::dataSourceIngestIsCancelled);
 
  701             randomAccessFile = 
new RandomAccessFile(tempFilePathname, 
"r");
 
  702             FileChannel roChannel = randomAccessFile.getChannel();
 
  703             ByteBuffer cacheFileROBuf = roChannel.map(FileChannel.MapMode.READ_ONLY, 0,
 
  704                                                         (
int) roChannel.size());
 
  706             cacheFileROBuf.order(ByteOrder.nativeOrder());
 
  707             FileWrapper cacheFileWrapper = 
new FileWrapper(cacheFile, randomAccessFile, cacheFileROBuf );
 
  709             if (!cacheFileName.startsWith(
"f_")) {
 
  710                 fileCopyCache.put(cacheFolderName + cacheFileName, cacheFileWrapper);
 
  713             return Optional.of(cacheFileWrapper);
 
  715         catch (IOException ex) {
 
  718                 if (randomAccessFile != null) {
 
  719                     randomAccessFile.close();
 
  722             catch (IOException ex2) {
 
  723                 logger.log(Level.SEVERE, 
"Error while trying to close temp file after exception.", ex2); 
 
  725             String msg = String.format(
"Error reading/copying Chrome cache file '%s' (id=%d).", 
 
  726                                             cacheFile.getName(), cacheFile.getId()); 
 
  727             throw new IngestModuleException(msg, ex);
 
  734     final class IndexFileHeader {
 
  736         private final long magic;
 
  737         private final int version;
 
  738         private final int numEntries;
 
  739         private final int numBytes;
 
  740         private final int lastFile;
 
  741         private final int tableLen;
 
  743         IndexFileHeader(ByteBuffer indexFileROBuf) {
 
  745             magic = indexFileROBuf.getInt() & UINT32_MASK; 
 
  747             indexFileROBuf.position(indexFileROBuf.position()+2);
 
  749             version = indexFileROBuf.getShort();
 
  750             numEntries = indexFileROBuf.getInt();
 
  751             numBytes = indexFileROBuf.getInt();
 
  752             lastFile = indexFileROBuf.getInt();
 
  754             indexFileROBuf.position(indexFileROBuf.position()+4); 
 
  755             indexFileROBuf.position(indexFileROBuf.position()+4); 
 
  757             tableLen = indexFileROBuf.getInt();
 
  760         public long getMagic() {
 
  764         public int getVersion() {
 
  768         public int getNumEntries() {
 
  772         public int getNumBytes() {
 
  776         public int getLastFile() {
 
  780         public int getTableLen() {
 
  785         public String toString() {
 
  786             StringBuilder sb = 
new StringBuilder();
 
  788             sb.append(String.format(
"Index Header:"))
 
  789                 .append(String.format(
"\tMagic = %x" , getMagic()) )
 
  790                 .append(String.format(
"\tVersion = %x" , getVersion()) )
 
  791                 .append(String.format(
"\tNumEntries = %x" , getNumEntries()) )
 
  792                 .append(String.format(
"\tNumBytes = %x" , getNumBytes()) )
 
  793                 .append(String.format(
"\tLastFile = %x" , getLastFile()) )
 
  794                 .append(String.format(
"\tTableLen = %x" , getTableLen()) );
 
  796             return sb.toString();
 
  803     enum CacheFileTypeEnum {
 
  838     final class CacheAddress {
 
  840         private static final long ADDR_INITIALIZED_MASK    = 0x80000000l;
 
  841         private static final long FILE_TYPE_MASK     = 0x70000000;
 
  842         private static final long FILE_TYPE_OFFSET   = 28;
 
  843         private static final long NUM_BLOCKS_MASK     = 0x03000000;
 
  844         private static final long NUM_BLOCKS_OFFSET   = 24;
 
  845         private static final long FILE_SELECTOR_MASK   = 0x00ff0000;
 
  846         private static final long FILE_SELECTOR_OFFSET = 16;
 
  847         private static final long START_BLOCK_MASK     = 0x0000FFFF;
 
  848         private static final long EXTERNAL_FILE_NAME_MASK = 0x0FFFFFFF;
 
  850         private final long uint32CacheAddr;
 
  851         private final CacheFileTypeEnum fileType;
 
  852         private final int numBlocks;
 
  853         private final int startBlock;
 
  854         private final String fileName;
 
  855         private final int fileNumber;
 
  857         private final String cachePath;
 
  865         CacheAddress(
long uint32, String cachePath) {
 
  867             uint32CacheAddr = uint32;
 
  868             this.cachePath = cachePath;
 
  872             int fileTypeEnc = (int)(uint32CacheAddr &  FILE_TYPE_MASK) >> FILE_TYPE_OFFSET;
 
  873             fileType = CacheFileTypeEnum.values()[fileTypeEnc];
 
  875             if (isInitialized()) {
 
  876                 if (isInExternalFile()) {
 
  877                     fileNumber = (int)(uint32CacheAddr & EXTERNAL_FILE_NAME_MASK);
 
  878                     fileName =  String.format(
"f_%06x", getFileNumber() );
 
  882                     fileNumber = (int)((uint32CacheAddr & FILE_SELECTOR_MASK) >> FILE_SELECTOR_OFFSET);
 
  883                     fileName = String.format(
"data_%d", getFileNumber() );
 
  884                     numBlocks = (int)(uint32CacheAddr &  NUM_BLOCKS_MASK >> NUM_BLOCKS_OFFSET);
 
  885                     startBlock = (int)(uint32CacheAddr &  START_BLOCK_MASK);
 
  896         boolean isInitialized() {
 
  897             return ((uint32CacheAddr & ADDR_INITIALIZED_MASK) != 0);
 
  900         CacheFileTypeEnum getFileType() {
 
  908         String getFilename() {
 
  912         String getCachePath() {
 
  916         boolean isInExternalFile() {
 
  917             return (fileType == CacheFileTypeEnum.EXTERNAL);
 
  920         int getFileNumber() {
 
  924         int getStartBlock() {
 
  953         public long getUint32CacheAddr() {
 
  954             return uint32CacheAddr;
 
  958         public String toString() {
 
  959             StringBuilder sb = 
new StringBuilder();
 
  960             sb.append(String.format(
"CacheAddr %08x : %s : filename %s", 
 
  962                                     isInitialized() ? 
"Initialized" : 
"UnInitialized",
 
  965             if ((fileType == CacheFileTypeEnum.BLOCK_256) || 
 
  966                 (fileType == CacheFileTypeEnum.BLOCK_1K) || 
 
  967                 (fileType == CacheFileTypeEnum.BLOCK_4K) ) {
 
  968                 sb.append(String.format(
" (%d blocks starting at %08X)", 
 
  974             return sb.toString();
 
  982     enum CacheDataTypeEnum {
 
  996     final class CacheDataSegment {
 
  999         private final CacheAddress cacheAddress;
 
 1000         private CacheDataTypeEnum type;
 
 1002         private boolean isHTTPHeaderHint;
 
 1004         private FileWrapper cacheFileCopy = null;
 
 1005         private byte[] data = null;
 
 1007         private String httpResponse;
 
 1008         private final Map<String, String> httpHeaders = 
new HashMap<>();
 
 1010         CacheDataSegment(CacheAddress cacheAddress, 
int len) {
 
 1011             this(cacheAddress, len, 
false);
 
 1014         CacheDataSegment(CacheAddress cacheAddress, 
int len, 
boolean isHTTPHeader ) {
 
 1015             this.type = CacheDataTypeEnum.UNKNOWN;
 
 1017             this.cacheAddress = cacheAddress;
 
 1018             this.isHTTPHeaderHint = isHTTPHeader;
 
 1021         boolean isInExternalFile() {
 
 1022             return cacheAddress.isInExternalFile();
 
 1025         boolean hasHTTPHeaders() {
 
 1026             return this.type == CacheDataTypeEnum.HTTP_HEADER;
 
 1029         String getHTTPHeader(String key) {
 
 1030             return this.httpHeaders.get(key);
 
 1038         String getHTTPHeaders() {
 
 1039             if (!hasHTTPHeaders()) {
 
 1043             StringBuilder sb = 
new StringBuilder();
 
 1044             httpHeaders.entrySet().forEach((entry) -> {
 
 1045                 if (sb.length() > 0) {
 
 1048                 sb.append(String.format(
"%s : %s",
 
 1049                         entry.getKey(), entry.getValue()));
 
 1052             return sb.toString();
 
 1055         String getHTTPRespone() {
 
 1056             return httpResponse;
 
 1064         void extract() throws TskCoreException, IngestModuleException {
 
 1072             if (!cacheAddress.isInExternalFile()) {
 
 1074                 if (cacheAddress.getFilename() == null) {
 
 1075                     throw new TskCoreException(
"Cache address has no file name");
 
 1078                 cacheFileCopy = findDataOrIndexFile(cacheAddress.getFilename(), cacheAddress.getCachePath()).
get();
 
 1080                 this.data = 
new byte [length];
 
 1081                 ByteBuffer buf = cacheFileCopy.getByteBuffer();
 
 1082                 int dataOffset = DATAFILE_HDR_SIZE + cacheAddress.getStartBlock() * cacheAddress.getBlockSize();
 
 1083                 if (dataOffset > buf.capacity()) {
 
 1086                 buf.position(dataOffset);
 
 1087                 buf.get(data, 0, length);
 
 1090                 if ((isHTTPHeaderHint)) {
 
 1091                     String strData = 
new String(data);
 
 1092                     if (strData.contains(
"HTTP")) {
 
 1103                         type = CacheDataTypeEnum.HTTP_HEADER;
 
 1105                         int startOff = strData.indexOf(
"HTTP");
 
 1106                         Charset charset = Charset.forName(
"UTF-8");
 
 1107                         boolean done = 
false;
 
 1114                             while (i < data.length && data[i] != 0)  {
 
 1119                             if (i == data.length || data[i+1] == 0) {
 
 1123                             int len = (i - start);
 
 1124                             String headerLine = 
new String(data, start, len, charset);
 
 1128                                 httpResponse = headerLine;
 
 1130                                 int nPos = headerLine.indexOf(
':');
 
 1132                                     String key = headerLine.substring(0, nPos);
 
 1133                                     String val= headerLine.substring(nPos+1);
 
 1134                                     httpHeaders.put(key.toLowerCase(), val);
 
 1146         String getDataString() throws TskCoreException, IngestModuleException {
 
 1150             return new String(data);
 
 1153         byte[] getDataBytes() throws TskCoreException, IngestModuleException {
 
 1157             return data.clone();
 
 1160         int getDataLength() {
 
 1164         CacheDataTypeEnum getType() {
 
 1168         CacheAddress getCacheAddress() {
 
 1169             return cacheAddress;
 
 1181         String save() throws TskCoreException, IngestModuleException {
 
 1184             if (cacheAddress.isInExternalFile()) {
 
 1185                 fileName = cacheAddress.getFilename();
 
 1187                 fileName = String.format(
"%s__%08x", cacheAddress.getFilename(), cacheAddress.getUint32CacheAddr());
 
 1190             String filePathName = getAbsOutputFolderName() + cacheAddress.getCachePath() + fileName;
 
 1205         void save(String filePathName) 
throws TskCoreException, IngestModuleException {
 
 1213             if (!this.isInExternalFile()) {
 
 1215                 try (FileOutputStream stream = 
new FileOutputStream(filePathName)) {
 
 1217                 } 
catch (IOException ex) {
 
 1218                     throw new TskCoreException(String.format(
"Failed to write output file %s", filePathName), ex);
 
 1224         public String toString() {
 
 1225             StringBuilder strBuilder = 
new StringBuilder();
 
 1226             strBuilder.append(String.format(
"\t\tData type = : %s, Data Len = %d ", 
 
 1227                                     this.type.toString(), this.length ));
 
 1229             if (hasHTTPHeaders()) {
 
 1230                 String str = getHTTPHeader(
"content-encoding");
 
 1232                     strBuilder.append(String.format(
"\t%s=%s", 
"content-encoding", str ));
 
 1236             return strBuilder.toString(); 
 
 1245     enum EntryStateEnum {
 
 1284     final class CacheEntry {
 
 1287         private static final int MAX_KEY_LEN = 256-24*4; 
 
 1289         private final CacheAddress selfAddress; 
 
 1290         private final FileWrapper cacheFileCopy;
 
 1292         private final long hash;
 
 1293         private final CacheAddress nextAddress;
 
 1294         private final CacheAddress rankingsNodeAddress;
 
 1296         private final int reuseCount;
 
 1297         private final int refetchCount;
 
 1298         private final EntryStateEnum state;
 
 1300         private final long creationTime;
 
 1301         private final int keyLen;
 
 1303         private final CacheAddress longKeyAddresses; 
 
 1305         private final int[] dataSegmentSizes;
 
 1306         private final CacheAddress[] dataSegmentIndexFileEntries;
 
 1307         private List<CacheDataSegment> dataSegments;
 
 1309         private final long flags;
 
 1313         CacheEntry(CacheAddress cacheAdress, FileWrapper cacheFileCopy ) 
throws TskCoreException, IngestModuleException {
 
 1314             this.selfAddress = cacheAdress;
 
 1315             this.cacheFileCopy = cacheFileCopy;
 
 1317             ByteBuffer fileROBuf = cacheFileCopy.getByteBuffer();
 
 1319             int entryOffset = DATAFILE_HDR_SIZE + cacheAdress.getStartBlock() * cacheAdress.getBlockSize();
 
 1322             if (entryOffset < fileROBuf.capacity()) {
 
 1323                 fileROBuf.position(entryOffset);
 
 1325                 throw new IngestModuleException(
"Position seeked in Buffer to big"); 
 
 1328             hash = fileROBuf.getInt() & UINT32_MASK;
 
 1330             long uint32 = fileROBuf.getInt() & UINT32_MASK;
 
 1331             nextAddress = (uint32 != 0) ?  
new CacheAddress(uint32, selfAddress.getCachePath()) : null;  
 
 1333             uint32 = fileROBuf.getInt() & UINT32_MASK;
 
 1334             rankingsNodeAddress = (uint32 != 0) ?  
new CacheAddress(uint32, selfAddress.getCachePath()) : null;  
 
 1336             reuseCount = fileROBuf.getInt();
 
 1337             refetchCount = fileROBuf.getInt();
 
 1339             int stateVal = fileROBuf.getInt();
 
 1340             if ((stateVal >= 0) && (stateVal < EntryStateEnum.values().length)) {
 
 1341                 state = EntryStateEnum.values()[stateVal];
 
 1343                 throw new TskCoreException(
"Invalid EntryStateEnum value"); 
 
 1345             creationTime = (fileROBuf.getLong() / 1000000) - Long.valueOf(
"11644473600");
 
 1347             keyLen = fileROBuf.getInt();
 
 1349             uint32 = fileROBuf.getInt() & UINT32_MASK;
 
 1350             longKeyAddresses = (uint32 != 0) ?  
new CacheAddress(uint32, selfAddress.getCachePath()) : null;  
 
 1352             dataSegments = null;
 
 1353             dataSegmentSizes= 
new int[4];
 
 1354             for (
int i = 0; i < 4; i++)  {
 
 1355                 dataSegmentSizes[i] = fileROBuf.getInt();
 
 1357             dataSegmentIndexFileEntries = 
new CacheAddress[4];
 
 1358             for (
int i = 0; i < 4; i++)  {
 
 1359                 dataSegmentIndexFileEntries[i] =  
new CacheAddress(fileROBuf.getInt() & UINT32_MASK, selfAddress.getCachePath());
 
 1362             flags = fileROBuf.getInt() & UINT32_MASK;
 
 1364             for (
int i = 0; i < 4; i++)  {
 
 1372             if (longKeyAddresses != null) {
 
 1375                     if (longKeyAddresses.getFilename() != null) {
 
 1376                         CacheDataSegment data = 
new CacheDataSegment(longKeyAddresses, this.keyLen, 
true);
 
 1377                         key = data.getDataString();
 
 1379                 } 
catch (TskCoreException | IngestModuleException ex) {
 
 1380                     throw new TskCoreException(String.format(
"Failed to get external key from address %s", longKeyAddresses)); 
 
 1384                 StringBuilder strBuilder = 
new StringBuilder(MAX_KEY_LEN);
 
 1386                 while (fileROBuf.remaining() > 0  && keyLen < MAX_KEY_LEN)  {
 
 1387                     char keyChar = (char)fileROBuf.get();
 
 1388                     if (keyChar == 
'\0') { 
 
 1391                     strBuilder.append(keyChar);
 
 1395                 key = strBuilder.toString();
 
 1399         public CacheAddress getCacheAddress() {
 
 1403         public long getHash() {
 
 1407         public CacheAddress getNextCacheAddress() {
 
 1411         public int getReuseCount() {
 
 1415         public int getRefetchCount() {
 
 1416             return refetchCount;
 
 1419         public EntryStateEnum getState() {
 
 1423         public long getCreationTime() {
 
 1424             return creationTime;
 
 1427         public long getFlags() {
 
 1431         public String getKey() {
 
 1443         public List<CacheDataSegment> getDataSegments() throws TskCoreException, IngestModuleException {
 
 1445             if (dataSegments == null) { 
 
 1446                 dataSegments = 
new ArrayList<>();
 
 1447                  for (
int i = 0; i < 4; i++)  {
 
 1448                      if (dataSegmentSizes[i] > 0) {
 
 1449                          CacheDataSegment cacheData = 
new CacheDataSegment(dataSegmentIndexFileEntries[i], dataSegmentSizes[i], 
true );
 
 1451                          cacheData.extract();
 
 1452                          dataSegments.add(cacheData);
 
 1456             return dataSegments; 
 
 1466         boolean hasHTTPHeaders() {
 
 1467             if ((dataSegments == null) || dataSegments.isEmpty()) {
 
 1470             return dataSegments.get(0).hasHTTPHeaders();
 
 1479         String getHTTPHeader(String key) {
 
 1480             if ((dataSegments == null) || dataSegments.isEmpty()) {
 
 1484             return dataSegments.get(0).getHTTPHeader(key);
 
 1492         String getHTTPHeaders() {
 
 1493             if ((dataSegments == null) || dataSegments.isEmpty()) {
 
 1497             return dataSegments.get(0).getHTTPHeaders();
 
 1508         boolean isBrotliCompressed() {
 
 1510             if (hasHTTPHeaders() ) {
 
 1511                 String encodingHeader = getHTTPHeader(
"content-encoding");
 
 1512                 if (encodingHeader!= null) {
 
 1513                     return encodingHeader.trim().equalsIgnoreCase(
"br");
 
 1521         public String toString() {
 
 1522             StringBuilder sb = 
new StringBuilder();
 
 1523             sb.append(String.format(
"Entry = Hash: %08x,  State: %s, ReuseCount: %d, RefetchCount: %d", 
 
 1524                                     this.hash, 
this.state.toString(), this.reuseCount, this.refetchCount ))
 
 1525                 .append(String.format(
"\n\tKey: %s, Keylen: %d", 
 
 1526                                     this.key, 
this.keyLen, 
this.reuseCount, 
this.refetchCount ))
 
 1527                 .append(String.format(
"\n\tCreationTime: %s", 
 
 1528                                     TimeUtilities.epochToTime(
this.creationTime) ))
 
 1529                 .append(String.format(
"\n\tNext Address: %s", 
 
 1530                                     (nextAddress != null) ? nextAddress.toString() : 
"None"));
 
 1532             for (
int i = 0; i < 4; i++) {
 
 1533                 if (dataSegmentSizes[i] > 0) {
 
 1534                     sb.append(String.format(
"\n\tData %d: cache address = %s, Data = %s", 
 
 1535                                          i, dataSegmentIndexFileEntries[i].toString(), 
 
 1536                                          (dataSegments != null)
 
 1537                                                  ? dataSegments.get(i).toString() 
 
 1538                                                  : 
"Data not retrived yet."));
 
 1542             return sb.toString();