Autopsy  4.10.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
ChromeCacheExtractor.java
Go to the documentation of this file.
1 /*
2  *
3  * Autopsy Forensic Browser
4  *
5  * Copyright 2019 Basis Technology Corp.
6  *
7  * Project Contact/Architect: carrier <at> sleuthkit <dot> org
8  *
9  * Licensed under the Apache License, Version 2.0 (the "License");
10  * you may not use this file except in compliance with the License.
11  * You may obtain a copy of the License at
12  *
13  * http://www.apache.org/licenses/LICENSE-2.0
14  *
15  * Unless required by applicable law or agreed to in writing, software
16  * distributed under the License is distributed on an "AS IS" BASIS,
17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18  * See the License for the specific language governing permissions and
19  * limitations under the License.
20  */
21 package org.sleuthkit.autopsy.recentactivity;
22 
23 import java.io.File;
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;
37 import java.util.Map;
38 import java.util.Map.Entry;
39 import java.util.Optional;
40 import java.util.logging.Level;
41 import org.openide.util.NbBundle;
53 import org.sleuthkit.datamodel.AbstractFile;
54 import org.sleuthkit.datamodel.BlackboardArtifact;
55 import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
56 import org.sleuthkit.datamodel.BlackboardAttribute;
57 import org.sleuthkit.datamodel.Content;
58 import org.sleuthkit.datamodel.DerivedFile;
59 import org.sleuthkit.datamodel.TimeUtilities;
60 import org.sleuthkit.datamodel.TskCoreException;
61 import org.sleuthkit.datamodel.TskData;
62 import org.sleuthkit.datamodel.TskException;
63 
76 final class ChromeCacheExtractor {
77 
78  private final static String DEFAULT_CACHE_STR = "default/cache"; //NON-NLS
79  private final static String BROTLI_MIMETYPE ="application/x-brotli"; //NON-NLS
80 
81  private final static long UINT32_MASK = 0xFFFFFFFFl;
82 
83  private final static int INDEXFILE_HDR_SIZE = 92*4;
84  private final static int DATAFILE_HDR_SIZE = 8192;
85 
86  private final static Logger logger = Logger.getLogger(ChromeCacheExtractor.class.getName());
87 
88  private static final String VERSION_NUMBER = "1.0.0"; //NON-NLS
89  private final String moduleName;
90 
91  private String absOutputFolderName;
92  private String relOutputFolderName;
93 
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;
100 
101  // A file table to cache copies of index and data_n files.
102  private final Map<String, CacheFileCopy> filesTable = new HashMap<>();
103 
104  // A file table to cache the f_* files.
105  private final Map<String, AbstractFile> externalFilesTable = new HashMap<>();
106 
111  final class CacheFileCopy {
112 
113  private final AbstractFile abstractFile;
114  private final RandomAccessFile fileCopy;
115  private final ByteBuffer byteBuffer;
116 
117  CacheFileCopy (AbstractFile abstractFile, RandomAccessFile fileCopy, ByteBuffer buffer ) {
118  this.abstractFile = abstractFile;
119  this.fileCopy = fileCopy;
120  this.byteBuffer = buffer;
121  }
122 
123  public RandomAccessFile getFileCopy() {
124  return fileCopy;
125  }
126  public ByteBuffer getByteBuffer() {
127  return byteBuffer;
128  }
129  AbstractFile getAbstractFile() {
130  return abstractFile;
131  }
132  }
133 
134  @NbBundle.Messages({
135  "ChromeCacheExtractor.moduleName=ChromeCacheExtractor",
136  "ChromeCacheExtractor.progressMsg={0}: Extracting cache entry {1} of {2} entries from {3}"
137  })
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;
143  }
144 
145 
151  void moduleInit() throws IngestModuleException {
152 
153  try {
154  currentCase = Case.getCurrentCaseThrows();
155  fileManager = currentCase.getServices().getFileManager();
156 
157  // Create an output folder to save any derived files
158  absOutputFolderName = RAImageIngestModule.getRAOutputPath(currentCase, moduleName);
159  relOutputFolderName = Paths.get( RAImageIngestModule.getRelModuleOutputPath(), moduleName).normalize().toString();
160 
161  File dir = new File(absOutputFolderName);
162  if (dir.exists() == false) {
163  dir.mkdirs();
164  }
165  } catch (NoCurrentCaseException ex) {
166  String msg = "Failed to get current case."; //NON-NLS
167  throw new IngestModuleException(msg, ex);
168  }
169  }
170 
178  void subInit(String cachePath) throws IngestModuleException {
179 
180  filesTable.clear();
181  externalFilesTable.clear();
182 
183  String cacheAbsOutputFolderName = this.getAbsOutputFolderName() + cachePath;
184  File outDir = new File(cacheAbsOutputFolderName);
185  if (outDir.exists() == false) {
186  outDir.mkdirs();
187  }
188 
189  String cacheTempPath = RAImageIngestModule.getRATempPath(currentCase, moduleName) + cachePath;
190  File tempDir = new File(cacheTempPath);
191  if (tempDir.exists() == false) {
192  tempDir.mkdirs();
193  }
194  }
195 
202  void cleanup () {
203 
204  for (Entry<String, CacheFileCopy> entry : this.filesTable.entrySet()) {
205  Path tempFilePath = Paths.get(RAImageIngestModule.getRATempPath(currentCase, moduleName), entry.getKey() );
206  try {
207  entry.getValue().getFileCopy().getChannel().close();
208  entry.getValue().getFileCopy().close();
209 
210  File tmpFile = tempFilePath.toFile();
211  if (!tmpFile.delete()) {
212  tmpFile.deleteOnExit();
213  }
214  } catch (IOException ex) {
215  logger.log(Level.WARNING, String.format("Failed to delete cache file copy %s", tempFilePath.toString()), ex); //NON-NLS
216  }
217  }
218  }
219 
225  private String getAbsOutputFolderName() {
226  return absOutputFolderName;
227  }
228 
234  private String getRelOutputFolderName() {
235  return relOutputFolderName;
236  }
237 
244  void getCaches() {
245 
246  try {
247  moduleInit();
248  } catch (IngestModuleException ex) {
249  String msg = "Failed to initialize ChromeCacheExtractor."; //NON-NLS
250  logger.log(Level.SEVERE, msg, ex);
251  return;
252  }
253 
254  // Find all possible caches
255  List<AbstractFile> indexFiles;
256  try {
257  indexFiles = findCacheFiles("index"); //NON-NLS
258 
259  // Get each of the caches
260  for (AbstractFile indexFile: indexFiles) {
261  getCache(indexFile);
262  }
263 
264  } catch (TskCoreException ex) {
265  String msg = "Failed to find cache index files"; //NON-NLS
266  logger.log(Level.SEVERE, msg, ex);
267  }
268  }
269 
275  void getCache(AbstractFile indexAbstractFile) {
276 
277  String cachePath = indexAbstractFile.getParentPath();
278  Optional<CacheFileCopy> indexFile;
279  try {
280  subInit(cachePath);
281 
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);
286  return;
287  }
288 
289  for (int i = 0; i < 4; i ++) {
290  Optional<CacheFileCopy> dataFile = findAndCopyCacheFile(String.format("data_%1d",i), cachePath );
291  if (!dataFile.isPresent()) {
292  return;
293  }
294  }
295 
296  // find all f_* files in a single query.
297  findExternalFiles(cachePath);
298 
299  } catch (TskCoreException | IngestModuleException ex) {
300  String msg = "Failed to find cache files in path " + cachePath; //NON-NLS
301  logger.log(Level.SEVERE, msg, ex);
302  return;
303  }
304 
305  logger.log(Level.INFO, "{0}- Now reading Cache index file from path {1}", new Object[]{moduleName, cachePath }); //NON-NLS
306 
307  List<AbstractFile> derivedFiles = new ArrayList<>();
308  Collection<BlackboardArtifact> sourceArtifacts = new ArrayList<>();
309  Collection<BlackboardArtifact> webCacheArtifacts = new ArrayList<>();
310 
311  ByteBuffer indexFileROBuffer = indexFile.get().getByteBuffer();
312  IndexFileHeader indexHdr = new IndexFileHeader(indexFileROBuffer);
313 
314  // seek past the header
315  indexFileROBuffer.position(INDEXFILE_HDR_SIZE);
316 
317  // Process each address in the table
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) );
324  try {
325  List<DerivedFile> addedFiles = this.getCacheEntry(addr, sourceArtifacts, webCacheArtifacts);
326  derivedFiles.addAll(addedFiles);
327  }
328  catch (TskCoreException | IngestModuleException ex) {
329  logger.log(Level.SEVERE, String.format("Failed to get cache entry at address %s", addr), ex); //NON-NLS
330  }
331  }
332  }
333 
334  derivedFiles.forEach((derived) -> {
335  services.fireModuleContentEvent(new ModuleContentEvent(derived));
336  });
337 
338  context.addFilesToJob(derivedFiles);
339 
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));
342 
343  cleanup();
344  }
345 
357  List<DerivedFile> getCacheEntry(CacheAddress cacheEntryAddress, Collection<BlackboardArtifact> sourceArtifacts, Collection<BlackboardArtifact> webCacheArtifacts ) throws TskCoreException, IngestModuleException {
358 
359  List<DerivedFile> derivedFiles = new ArrayList<>();
360 
361  String cacheEntryFileName = cacheEntryAddress.getFilename();
362  String cachePath = cacheEntryAddress.getCachePath();
363 
364 
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); //NON-NLS
368  throw new IngestModuleException(msg);
369  }
370 
371 
372  // Get the cache entry and its data segments
373  CacheEntry cacheEntry = new CacheEntry(cacheEntryAddress, cacheEntryFile.get() );
374  List<CacheData> dataEntries = cacheEntry.getData();
375 
376  BlackboardAttribute urlAttr = new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL,
377  moduleName,
378  ((cacheEntry.getKey() != null) ? cacheEntry.getKey() : ""));
379 
380  BlackboardAttribute createTimeAttr = new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED,
381  moduleName,
382  cacheEntry.getCreationTime());
383 
384  BlackboardAttribute hhtpHeaderAttr = new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_HEADERS,
385  moduleName,
386  cacheEntry.getHTTPHeaders());
387 
388 
389  // Only process the first payload data segment in each entry
390  // first data segement has the HTTP headers, 2nd is the payload
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);
395 
396  boolean isBrotliCompressed = false;
397  if (data.getType() != CacheDataTypeEnum.HTTP_HEADER && cacheEntry.isBrotliCompressed() ) {
398  isBrotliCompressed = true;
399  }
400 
401  Collection<BlackboardAttribute> sourceArtifactAttributes = new ArrayList<>();
402  sourceArtifactAttributes.add(urlAttr);
403  sourceArtifactAttributes.add(createTimeAttr);
404 
405  Collection<BlackboardAttribute> webCacheAttributes = new ArrayList<>();
406  webCacheAttributes.add(urlAttr);
407  webCacheAttributes.add(createTimeAttr);
408  webCacheAttributes.add(hhtpHeaderAttr);
409 
410  if (dataFile.isPresent()) {
411  if (data.isInExternalFile() ) {
412  try {
413  BlackboardArtifact sourceArtifact = dataFile.get().newArtifact(ARTIFACT_TYPE.TSK_DOWNLOAD_SOURCE);
414  if (sourceArtifact != null) {
415  sourceArtifact.addAttributes(sourceArtifactAttributes);
416  sourceArtifacts.add(sourceArtifact);
417  }
418 
419  BlackboardArtifact webCacheArtifact = cacheEntryFile.get().getAbstractFile().newArtifact(ARTIFACT_TYPE.TSK_WEB_CACHE);
420  if (webCacheArtifact != null) {
421  webCacheArtifact.addAttributes(webCacheAttributes);
422 
423  // Add path of f_* file as attribute
424  webCacheArtifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH,
425  moduleName,
426  dataFile.get().getUniquePath()));
427 
428  webCacheArtifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_ID,
429  moduleName, dataFile.get().getId()));
430 
431  webCacheArtifacts.add(webCacheArtifact);
432  }
433 
434  if (isBrotliCompressed) {
435  dataFile.get().setMIMEType(BROTLI_MIMETYPE);
436  dataFile.get().save();
437  }
438  } catch (TskException ex) {
439  logger.log(Level.SEVERE, "Error while trying to add an artifact", ex); //NON-NLS
440  }
441  } else {
442 
443  // Data segments in "data_x" files are saved in individual files and added as derived files
444  String filename = data.save();
445  String relPathname = getRelOutputFolderName() + data.getAddress().getCachePath() + filename;
446  try {
447  DerivedFile derivedFile = fileManager.addDerivedFile(filename, relPathname,
448  data.getDataLength(),
449  cacheEntry.getCreationTime(), cacheEntry.getCreationTime(), cacheEntry.getCreationTime(), cacheEntry.getCreationTime(), // TBD
450  true,
451  dataFile.get(),
452  "",
453  moduleName,
454  VERSION_NUMBER,
455  "",
456  TskData.EncodingType.NONE);
457 
458  BlackboardArtifact sourceArtifact = derivedFile.newArtifact(ARTIFACT_TYPE.TSK_DOWNLOAD_SOURCE);
459  if (sourceArtifact != null) {
460  sourceArtifact.addAttributes(sourceArtifactAttributes);
461  sourceArtifacts.add(sourceArtifact);
462  }
463 
464  BlackboardArtifact webCacheArtifact = cacheEntryFile.get().getAbstractFile().newArtifact(ARTIFACT_TYPE.TSK_WEB_CACHE);
465  if (webCacheArtifact != null) {
466  webCacheArtifact.addAttributes(webCacheAttributes);
467 
468  // Add path of derived file as attribute
469  webCacheArtifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH,
470  moduleName,
471  derivedFile.getUniquePath()));
472 
473  webCacheArtifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_ID,
474  moduleName, derivedFile.getId()));
475 
476  webCacheArtifacts.add(webCacheArtifact);
477  }
478 
479  if (isBrotliCompressed) {
480  derivedFile.setMIMEType(BROTLI_MIMETYPE);
481  derivedFile.save();
482  }
483 
484  derivedFiles.add(derivedFile);
485  } catch (TskException ex) {
486  logger.log(Level.SEVERE, "Error while trying to add an artifact", ex); //NON-NLS
487  }
488  }
489  }
490  }
491 
492  return derivedFiles;
493  }
494 
503  private void findExternalFiles(String cachePath) throws TskCoreException {
504 
505  List<AbstractFile> effFiles = fileManager.findFiles(dataSource, "f_%", cachePath); //NON-NLS
506  for (AbstractFile abstractFile : effFiles ) {
507  this.externalFilesTable.put(cachePath + abstractFile.getName(), abstractFile);
508  }
509  }
518  Optional<AbstractFile> findCacheFile(String cacheFileName, String cachePath) throws TskCoreException {
519 
520  String fileTableKey = cachePath + cacheFileName;
521  if (cacheFileName.startsWith("f_") && externalFilesTable.containsKey(fileTableKey)) {
522  return Optional.of(externalFilesTable.get(fileTableKey));
523  }
524  if (filesTable.containsKey(fileTableKey)) {
525  return Optional.of(filesTable.get(fileTableKey).getAbstractFile());
526  }
527 
528  List<AbstractFile> cacheFiles = fileManager.findFiles(dataSource, cacheFileName, cachePath); //NON-NLS
529  if (!cacheFiles.isEmpty()) {
530  for (AbstractFile abstractFile: cacheFiles ) {
531  if (abstractFile.getUniquePath().trim().endsWith(DEFAULT_CACHE_STR)) {
532  return Optional.of(abstractFile);
533  }
534  }
535  return Optional.of(cacheFiles.get(0));
536  }
537 
538  return Optional.empty();
539  }
540 
548  List<AbstractFile> findCacheFiles(String cacheFileName) throws TskCoreException {
549  return fileManager.findFiles(dataSource, cacheFileName, DEFAULT_CACHE_STR); //NON-NLS
550  }
551 
552 
561  Optional<CacheFileCopy> getCacheFileCopy(String cacheFileName, String cachePath) throws TskCoreException, IngestModuleException {
562 
563  // Check if the file is already in the table
564  String fileTableKey = cachePath + cacheFileName;
565  if (filesTable.containsKey(fileTableKey)) {
566  return Optional.of(filesTable.get(fileTableKey));
567  }
568 
569  return findAndCopyCacheFile(cacheFileName, cachePath);
570  }
571 
579  Optional<CacheFileCopy> findAndCopyCacheFile(String cacheFileName, String cachePath) throws TskCoreException, IngestModuleException {
580 
581  Optional<AbstractFile> cacheFileOptional = findCacheFile(cacheFileName, cachePath);
582  if (!cacheFileOptional.isPresent()) {
583  return Optional.empty();
584  }
585 
586  AbstractFile cacheFile = cacheFileOptional.get();
587  RandomAccessFile randomAccessFile = null;
588  String tempFilePathname = RAImageIngestModule.getRATempPath(currentCase, moduleName) + cachePath + cacheFile.getName(); //NON-NLS
589  try {
590  File newFile = new File(tempFilePathname);
591  ContentUtils.writeToFile(cacheFile, newFile, context::dataSourceIngestIsCancelled);
592 
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());
597 
598  cacheFileROBuf.order(ByteOrder.nativeOrder());
599  CacheFileCopy cacheFileCopy = new CacheFileCopy(cacheFile, randomAccessFile, cacheFileROBuf );
600 
601  if (!cacheFileName.startsWith("f_")) {
602  filesTable.put(cachePath + cacheFileName, cacheFileCopy);
603  }
604 
605  return Optional.of(cacheFileCopy);
606  }
607  catch (IOException ex) {
608 
609  try {
610  if (randomAccessFile != null) {
611  randomAccessFile.close();
612  }
613  }
614  catch (IOException ex2) {
615  logger.log(Level.SEVERE, "Error while trying to close temp file after exception.", ex2); //NON-NLS
616  }
617  String msg = String.format("Error reading/copying Chrome cache file '%s' (id=%d).", //NON-NLS
618  cacheFile.getName(), cacheFile.getId());
619  throw new IngestModuleException(msg, ex);
620  }
621  }
622 
626  final class IndexFileHeader {
627 
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;
634 
635  IndexFileHeader(ByteBuffer indexFileROBuf) {
636 
637  magic = indexFileROBuf.getInt() & UINT32_MASK;
638 
639  indexFileROBuf.position(indexFileROBuf.position()+2);
640 
641  version = indexFileROBuf.getShort();
642  numEntries = indexFileROBuf.getInt();
643  numBytes = indexFileROBuf.getInt();
644  lastFile = indexFileROBuf.getInt();
645 
646  indexFileROBuf.position(indexFileROBuf.position()+4); // this_id
647  indexFileROBuf.position(indexFileROBuf.position()+4); // stats cache address
648 
649  tableLen = indexFileROBuf.getInt();
650  }
651 
652  public long getMagic() {
653  return magic;
654  }
655 
656  public int getVersion() {
657  return version;
658  }
659 
660  public int getNumEntries() {
661  return numEntries;
662  }
663 
664  public int getNumBytes() {
665  return numBytes;
666  }
667 
668  public int getLastFile() {
669  return lastFile;
670  }
671 
672  public int getTableLen() {
673  return tableLen;
674  }
675 
676  @Override
677  public String toString() {
678  StringBuilder sb = new StringBuilder();
679 
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()) );
687 
688  return sb.toString();
689  }
690  }
691 
695  enum CacheFileTypeEnum {
696  EXTERNAL,
697  RANKINGS,
698  BLOCK_256,
699  BLOCK_1K,
700  BLOCK_4K,
701  BLOCK_FILES,
702  BLOCK_ENTRIES,
703  BLOCK_EVICTED
704  }
705 
706 
707 
727  final class CacheAddress {
728  // sundry constants to parse the bit fields in address
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;
738 
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;
745 
746  private final String cachePath;
747 
748 
749  CacheAddress(long uint32, String cachePath) {
750 
751  uint32CacheAddr = uint32;
752  this.cachePath = cachePath;
753 
754  int fileTypeEnc = (int)(uint32CacheAddr & FILE_TYPE_MASK) >> FILE_TYPE_OFFSET;
755  fileType = CacheFileTypeEnum.values()[fileTypeEnc];
756 
757  if (isInitialized()) {
758  if (isInExternalFile()) {
759  fileNumber = (int)(uint32CacheAddr & EXTERNAL_FILE_NAME_MASK);
760  fileName = String.format("f_%06x", getFileNumber() );
761  numBlocks = 0;
762  startBlock = 0;
763  } else {
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);
768  }
769  }
770  else {
771  fileName = null;
772  fileNumber = 0;
773  numBlocks = 0;
774  startBlock = 0;
775  }
776  }
777 
778  boolean isInitialized() {
779  return ((uint32CacheAddr & ADDR_INITIALIZED_MASK) != 0);
780  }
781 
782  CacheFileTypeEnum getFileType() {
783  return fileType;
784  }
785 
786  String getFilename() {
787  return fileName;
788  }
789 
790  String getCachePath() {
791  return cachePath;
792  }
793 
794  boolean isInExternalFile() {
795  return (fileType == CacheFileTypeEnum.EXTERNAL);
796  }
797 
798  int getFileNumber() {
799  return fileNumber;
800  }
801 
802  int getStartBlock() {
803  return startBlock;
804  }
805 
806  int getNumBlocks() {
807  return numBlocks;
808  }
809 
810  int getBlockSize() {
811  switch (fileType) {
812  case RANKINGS:
813  return 36;
814  case BLOCK_256:
815  return 256;
816  case BLOCK_1K:
817  return 1024;
818  case BLOCK_4K:
819  return 4096;
820  case BLOCK_FILES:
821  return 8;
822  case BLOCK_ENTRIES:
823  return 104;
824  case BLOCK_EVICTED:
825  return 48;
826  default:
827  return 0;
828  }
829  }
830 
831  public long getUint32CacheAddr() {
832  return uint32CacheAddr;
833  }
834 
835  @Override
836  public String toString() {
837  StringBuilder sb = new StringBuilder();
838  sb.append(String.format("CacheAddr %08x : %s : filename %s",
839  uint32CacheAddr,
840  isInitialized() ? "Initialized" : "UnInitialized",
841  getFilename()));
842 
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)",
847  this.getNumBlocks(),
848  this.getStartBlock()
849  ));
850  }
851 
852  return sb.toString();
853  }
854 
855  }
856 
860  enum CacheDataTypeEnum {
861  HTTP_HEADER,
862  UNKNOWN,
863  };
864 
874  final class CacheData {
875 
876  private int length;
877  private final CacheAddress address;
878  private CacheDataTypeEnum type;
879 
880  private boolean isHTTPHeaderHint;
881 
882  private CacheFileCopy cacheFileCopy = null;
883  private byte[] data = null;
884 
885  private String httpResponse;
886  private final Map<String, String> httpHeaders = new HashMap<>();
887 
888  CacheData(CacheAddress cacheAdress, int len) {
889  this(cacheAdress, len, false);
890  }
891 
892  CacheData(CacheAddress cacheAdress, int len, boolean isHTTPHeader ) {
893  this.type = CacheDataTypeEnum.UNKNOWN;
894  this.length = len;
895  this.address = cacheAdress;
896  this.isHTTPHeaderHint = isHTTPHeader;
897  }
898 
899  boolean isInExternalFile() {
900  return address.isInExternalFile();
901  }
902 
903  boolean hasHTTPHeaders() {
904  return this.type == CacheDataTypeEnum.HTTP_HEADER;
905  }
906 
907  String getHTTPHeader(String key) {
908  return this.httpHeaders.get(key);
909  }
910 
916  String getHTTPHeaders() {
917  if (!hasHTTPHeaders()) {
918  return "";
919  }
920 
921  StringBuilder sb = new StringBuilder();
922  httpHeaders.entrySet().forEach((entry) -> {
923  if (sb.length() > 0) {
924  sb.append(" \n");
925  }
926  sb.append(String.format("%s : %s",
927  entry.getKey(), entry.getValue()));
928  });
929 
930  return sb.toString();
931  }
932 
933  String getHTTPRespone() {
934  return httpResponse;
935  }
936 
942  void extract() throws TskCoreException, IngestModuleException {
943 
944  // do nothing if already extracted,
945  if (data != null) {
946  return;
947  }
948 
949  // Don't extract data from external files.
950  if (!address.isInExternalFile() ) {
951 
952  cacheFileCopy = getCacheFileCopy(address.getFilename(), address.getCachePath()).get();
953 
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);
959 
960  // if this might be a HTPP header, lets try to parse it as such
961  if ((isHTTPHeaderHint)) {
962  String strData = new String(data);
963  if (strData.contains("HTTP")) {
964 
965  // Http headers if present, are usually in frst data segment in an entry
966  // General Parsing algo:
967  // - Find start of HTTP header by searching for string "HTTP"
968  // - Skip to the first 0x00 to get to the end of the HTTP response line, this makrs start of headers section
969  // - Find the end of the header by searching for 0x00 0x00 bytes
970  // - Extract the headers section
971  // - Parse the headers section - each null terminated string is a header
972  // - Each header is of the format "name: value" e.g.
973 
974  type = CacheDataTypeEnum.HTTP_HEADER;
975 
976  int startOff = strData.indexOf("HTTP");
977  Charset charset = Charset.forName("UTF-8");
978  boolean done = false;
979  int i = startOff;
980  int hdrNum = 1;
981 
982  while (!done) {
983  // each header is null terminated
984  int start = i;
985  while (i < data.length && data[i] != 0) {
986  i++;
987  }
988 
989  // http headers are terminated by 0x00 0x00
990  if (i == data.length || data[i+1] == 0) {
991  done = true;
992  }
993 
994  int len = (i - start);
995  String headerLine = new String(data, start, len, charset);
996 
997  // first line is the http response
998  if (hdrNum == 1) {
999  httpResponse = headerLine;
1000  } else {
1001  int nPos = headerLine.indexOf(':');
1002  if (nPos > 0 ) {
1003  String key = headerLine.substring(0, nPos);
1004  String val= headerLine.substring(nPos+1);
1005  httpHeaders.put(key.toLowerCase(), val);
1006  }
1007  }
1008 
1009  i++;
1010  hdrNum++;
1011  }
1012  }
1013  }
1014  }
1015  }
1016 
1017  String getDataString() throws TskCoreException, IngestModuleException {
1018  if (data == null) {
1019  extract();
1020  }
1021  return new String(data);
1022  }
1023 
1024  byte[] getDataBytes() throws TskCoreException, IngestModuleException {
1025  if (data == null) {
1026  extract();
1027  }
1028  return data.clone();
1029  }
1030 
1031  int getDataLength() {
1032  return this.length;
1033  }
1034 
1035  CacheDataTypeEnum getType() {
1036  return type;
1037  }
1038 
1039  CacheAddress getAddress() {
1040  return address;
1041  }
1042 
1043 
1052  String save() throws TskCoreException, IngestModuleException {
1053  String fileName;
1054 
1055  if (address.isInExternalFile()) {
1056  fileName = address.getFilename();
1057  } else {
1058  fileName = String.format("%s__%08x", address.getFilename(), address.getUint32CacheAddr());
1059  }
1060 
1061  String filePathName = getAbsOutputFolderName() + address.getCachePath() + fileName;
1062  save(filePathName);
1063 
1064  return fileName;
1065  }
1066 
1076  void save(String filePathName) throws TskCoreException, IngestModuleException {
1077 
1078  // Save the data to specified file
1079  if (data == null) {
1080  extract();
1081  }
1082 
1083  // Data in external files is not saved in local files
1084  if (!this.isInExternalFile()) {
1085  // write the
1086  try (FileOutputStream stream = new FileOutputStream(filePathName)) {
1087  stream.write(data);
1088  } catch (IOException ex) {
1089  throw new TskCoreException(String.format("Failed to write output file %s", filePathName), ex);
1090  }
1091  }
1092  }
1093 
1094  @Override
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 ));
1099 
1100  if (hasHTTPHeaders()) {
1101  String str = getHTTPHeader("content-encoding");
1102  if (str != null) {
1103  strBuilder.append(String.format("\t%s=%s", "content-encoding", str ));
1104  }
1105  }
1106 
1107  return strBuilder.toString();
1108  }
1109 
1110  }
1111 
1112 
1116  enum EntryStateEnum {
1117  ENTRY_NORMAL,
1118  ENTRY_EVICTED,
1119  ENTRY_DOOMED
1120  };
1121 
1122 
1123 // Main structure for an entry on the backing storage.
1124 //
1125 // Each entry has a key, identifying the URL the cache entry pertains to.
1126 // If the key is longer than
1127 // what can be stored on this structure, it will be extended on consecutive
1128 // blocks (adding 256 bytes each time), up to 4 blocks (1024 - 32 - 1 chars).
1129 // After that point, the whole key will be stored as a data block or external
1130 // file.
1131 //
1132 // Each entry can have upto 4 data segments
1133 //
1134 // struct EntryStore {
1135 // uint32 hash; // Full hash of the key.
1136 // CacheAddr next; // Next entry with the same hash or bucket.
1137 // CacheAddr rankings_node; // Rankings node for this entry.
1138 // int32 reuse_count; // How often is this entry used.
1139 // int32 refetch_count; // How often is this fetched from the net.
1140 // int32 state; // Current state.
1141 // uint64 creation_time;
1142 // int32 key_len;
1143 // CacheAddr long_key; // Optional address of a long key.
1144 // int32 data_size[4]; // We can store up to 4 data streams for each
1145 // CacheAddr data_addr[4]; // entry.
1146 // uint32 flags; // Any combination of EntryFlags.
1147 // int32 pad[4];
1148 // uint32 self_hash; // The hash of EntryStore up to this point.
1149 // char key[256 - 24 * 4]; // null terminated
1150 // };
1151 
1155  final class CacheEntry {
1156 
1157  // each entry is 256 bytes. The last section of the entry, after all the other fields is a null terminated key
1158  private static final int MAX_KEY_LEN = 256-24*4;
1159 
1160  private final CacheAddress selfAddress;
1161  private final CacheFileCopy cacheFileCopy;
1162 
1163  private final long hash;
1164  private final CacheAddress nextAddress;
1165  private final CacheAddress rankingsNodeAddress;
1166 
1167  private final int reuseCount;
1168  private final int refetchCount;
1169  private final EntryStateEnum state;
1170 
1171  private final long creationTime;
1172  private final int keyLen;
1173 
1174  private final CacheAddress longKeyAddresses; // address of the key, if the key is external to the entry
1175 
1176  private final int dataSizes[];
1177  private final CacheAddress dataAddresses[];
1178  private List<CacheData> dataList;
1179 
1180  private final long flags;
1181 
1182  private String key; // Key may be found within the entry or may be external
1183 
1184  CacheEntry(CacheAddress cacheAdress, CacheFileCopy cacheFileCopy ) {
1185  this.selfAddress = cacheAdress;
1186  this.cacheFileCopy = cacheFileCopy;
1187 
1188  ByteBuffer fileROBuf = cacheFileCopy.getByteBuffer();
1189 
1190  int entryOffset = DATAFILE_HDR_SIZE + cacheAdress.getStartBlock() * cacheAdress.getBlockSize();
1191 
1192  // reposition the buffer to the the correct offset
1193  fileROBuf.position(entryOffset);
1194 
1195  hash = fileROBuf.getInt() & UINT32_MASK;
1196 
1197  long uint32 = fileROBuf.getInt() & UINT32_MASK;
1198  nextAddress = (uint32 != 0) ? new CacheAddress(uint32, selfAddress.getCachePath()) : null;
1199 
1200  uint32 = fileROBuf.getInt() & UINT32_MASK;
1201  rankingsNodeAddress = (uint32 != 0) ? new CacheAddress(uint32, selfAddress.getCachePath()) : null;
1202 
1203  reuseCount = fileROBuf.getInt();
1204  refetchCount = fileROBuf.getInt();
1205 
1206  state = EntryStateEnum.values()[fileROBuf.getInt()];
1207  creationTime = (fileROBuf.getLong() / 1000000) - Long.valueOf("11644473600");
1208 
1209  keyLen = fileROBuf.getInt();
1210 
1211  uint32 = fileROBuf.getInt() & UINT32_MASK;
1212  longKeyAddresses = (uint32 != 0) ? new CacheAddress(uint32, selfAddress.getCachePath()) : null;
1213 
1214  dataList = null;
1215  dataSizes= new int[4];
1216  for (int i = 0; i < 4; i++) {
1217  dataSizes[i] = fileROBuf.getInt();
1218  }
1219  dataAddresses = new CacheAddress[4];
1220  for (int i = 0; i < 4; i++) {
1221  dataAddresses[i] = new CacheAddress(fileROBuf.getInt() & UINT32_MASK, selfAddress.getCachePath());
1222  }
1223 
1224  flags = fileROBuf.getInt() & UINT32_MASK;
1225  // skip over pad
1226  for (int i = 0; i < 4; i++) {
1227  fileROBuf.getInt();
1228  }
1229 
1230  // skip over self hash
1231  fileROBuf.getInt();
1232 
1233  // get the key
1234  if (longKeyAddresses != null) {
1235  // Key is stored outside of the entry
1236  try {
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)); //NON-NLS
1241  }
1242  }
1243  else { // key stored within entry
1244  StringBuilder strBuilder = new StringBuilder(MAX_KEY_LEN);
1245  int keyLen = 0;
1246  while (fileROBuf.remaining() > 0 && keyLen < MAX_KEY_LEN) {
1247  char keyChar = (char)fileROBuf.get();
1248  if (keyChar == '\0') {
1249  break;
1250  }
1251  strBuilder.append(keyChar);
1252  keyLen++;
1253  }
1254 
1255  key = strBuilder.toString();
1256  }
1257  }
1258 
1259  public CacheAddress getAddress() {
1260  return selfAddress;
1261  }
1262 
1263  public long getHash() {
1264  return hash;
1265  }
1266 
1267  public CacheAddress getNextAddress() {
1268  return nextAddress;
1269  }
1270 
1271  public int getReuseCount() {
1272  return reuseCount;
1273  }
1274 
1275  public int getRefetchCount() {
1276  return refetchCount;
1277  }
1278 
1279  public EntryStateEnum getState() {
1280  return state;
1281  }
1282 
1283  public long getCreationTime() {
1284  return creationTime;
1285  }
1286 
1287  public long getFlags() {
1288  return flags;
1289  }
1290 
1291  public String getKey() {
1292  return key;
1293  }
1294 
1303  public List<CacheData> getData() throws TskCoreException, IngestModuleException {
1304 
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 );
1310 
1311  cacheData.extract();
1312  dataList.add(cacheData);
1313  }
1314  }
1315  }
1316  return dataList;
1317  }
1318 
1326  boolean hasHTTPHeaders() {
1327  if ((dataList == null) || dataList.isEmpty()) {
1328  return false;
1329  }
1330  return dataList.get(0).hasHTTPHeaders();
1331  }
1332 
1339  String getHTTPHeader(String key) {
1340  if ((dataList == null) || dataList.isEmpty()) {
1341  return null;
1342  }
1343  // First data segment has the HTTP headers, if any
1344  return dataList.get(0).getHTTPHeader(key);
1345  }
1346 
1352  String getHTTPHeaders() {
1353  if ((dataList == null) || dataList.isEmpty()) {
1354  return null;
1355  }
1356  // First data segment has the HTTP headers, if any
1357  return dataList.get(0).getHTTPHeaders();
1358  }
1359 
1368  boolean isBrotliCompressed() {
1369 
1370  if (hasHTTPHeaders() ) {
1371  String encodingHeader = getHTTPHeader("content-encoding");
1372  if (encodingHeader!= null) {
1373  return encodingHeader.trim().equalsIgnoreCase("br");
1374  }
1375  }
1376 
1377  return false;
1378  }
1379 
1380  @Override
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"));
1391 
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(),
1396  (dataList != null)
1397  ? dataList.get(i).toString()
1398  : "Data not retrived yet."));
1399  }
1400  }
1401 
1402  return sb.toString();
1403  }
1404  }
1405 }

Copyright © 2012-2018 Basis Technology. Generated on: Fri Mar 22 2019
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.