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