19 package org.sleuthkit.autopsy.recentactivity;
 
   22 import java.io.FileInputStream;
 
   23 import java.io.FileNotFoundException;
 
   24 import java.io.IOException;
 
   25 import java.nio.file.Path;
 
   26 import java.nio.file.Paths;
 
   27 import java.text.ParseException;
 
   28 import java.text.SimpleDateFormat;
 
   29 import java.text.DateFormat;
 
   30 import java.util.ArrayList;
 
   31 import java.util.Arrays;
 
   32 import java.util.Collection;
 
   33 import java.util.HashMap;
 
   34 import java.util.List;
 
   35 import java.util.Locale;
 
   36 import java.util.Scanner;
 
   37 import java.util.logging.Level;
 
   38 import java.util.regex.Matcher;
 
   39 import java.util.regex.Pattern;
 
   40 import org.openide.modules.InstalledFileLocator;
 
   41 import org.openide.util.NbBundle.Messages;
 
   55 import static org.
sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_BOOKMARK;
 
   56 import static org.
sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_COOKIE;
 
   57 import static org.
sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY;
 
   64 final class ExtractEdge 
extends Extract {
 
   66     private static final Logger LOG = Logger.getLogger(ExtractEdge.class.getName());
 
   67     private Content dataSource;
 
   68     private IngestJobContext context;
 
   69     private HashMap<String, ArrayList<String>> containersTable;
 
   71     private static final String EDGE = 
"Edge"; 
 
   73     private static final String EDGE_KEYWORD_VISIT = 
"Visited:"; 
 
   74     private static final String IGNORE_COMMA_IN_QUOTES_REGEX = 
",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)"; 
 
   76     private static final String EDGE_TABLE_TYPE_DOWNLOAD = 
"iedownload"; 
 
   77     private static final String EDGE_TABLE_TYPE_HISTORY = 
"History"; 
 
   78     private static final String EDGE_TABLE_TYPE_COOKIE = 
"cookie"; 
 
   80     private static final String EDGE_HEAD_URL = 
"url"; 
 
   81     private static final String EDGE_HEAD_ACCESSTIME = 
"accessedtime"; 
 
   82     private static final String EDGE_HEAD_NAME = 
"name"; 
 
   83     private static final String EDGE_HEAD_CONTAINER_ID = 
"containerid"; 
 
   84     private static final String EDGE_HEAD_RESPONSEHEAD = 
"responseheaders"; 
 
   85     private static final String EDGE_HEAD_TITLE = 
"title"; 
 
   86     private static final String EDGE_HEAD_RDOMAIN = 
"rdomain"; 
 
   87     private static final String EDGE_HEAD_VALUE = 
"value"; 
 
   88     private static final String EDGE_HEAD_LASTMOD = 
"lastmodified"; 
 
   90     private static final String EDGE_WEBCACHE_PREFIX = 
"WebCacheV01"; 
 
   91     private static final String EDGE_CONTAINER_FILE_PREFIX = 
"Container_"; 
 
   92     private static final String EDGE_CONTAINER_FILE_EXT = 
".csv"; 
 
   93     private static final String EDGE_WEBCACHE_EXT = 
".dat"; 
 
   95     private static final String ESE_TOOL_NAME = 
"ESEDatabaseView.exe"; 
 
   96     private static final String EDGE_WEBCACHE_NAME = 
"WebCacheV01.dat"; 
 
   97     private static final String EDGE_SPARTAN_NAME = 
"Spartan.edb"; 
 
   98     private static final String EDGE_CONTAINTERS_FILE_NAME = 
"Containers.csv"; 
 
   99     private static final String EDGE_FAVORITE_FILE_NAME = 
"Favorites.csv"; 
 
  100     private static final String EDGE_OUTPUT_FILE_NAME = 
"Output.txt"; 
 
  101     private static final String EDGE_ERROR_FILE_NAME = 
"File.txt"; 
 
  102     private static final String EDGE_WEBCACHE_FOLDER_NAME = 
"WebCache"; 
 
  103     private static final String EDGE_SPARTAN_FOLDER_NAME = 
"MicrosoftEdge"; 
 
  105     private static final String ESE_TOOL_FOLDER = 
"ESEDatabaseView"; 
 
  106     private static final String EDGE_RESULT_FOLDER_NAME = 
"results"; 
 
  110     private SimpleDateFormat previouslyValidDateFormat = null;
 
  113         "ExtractEdge_process_errMsg_unableFindESEViewer=Unable to find ESEDatabaseViewer",
 
  114         "ExtractEdge_process_errMsg_errGettingWebCacheFiles=Error trying to retrieving Edge WebCacheV01 file",
 
  115         "ExtractEdge_process_errMsg_webcacheFail=Failure processing Microsoft Edge WebCacheV01.dat file",
 
  116         "ExtractEdge_process_errMsg_spartanFail=Failure processing Microsoft Edge spartan.edb file",
 
  117         "ExtractEdge_Module_Name=Microsoft Edge",
 
  118         "ExtractEdge_getHistory_containerFileNotFound=Error while trying to analyze Edge history",
 
  119         "Progress_Message_Edge_History=Microsoft Edge History",
 
  120         "Progress_Message_Edge_Bookmarks=Microsoft Edge Bookmarks",
 
  121         "Progress_Message_Edge_Cookies=Microsoft Edge Cookies",
 
  128         super(Bundle.ExtractEdge_Module_Name());
 
  132     protected String getName() {
 
  133         return Bundle.ExtractEdge_Module_Name();
 
  137     void process(Content dataSource, IngestJobContext context, DataSourceIngestModuleProgress progressBar) {
 
  138         String moduleTempDir = RAImageIngestModule.getRATempPath(getCurrentCase(), EDGE, context.getJobId());
 
  139         String moduleTempResultDir = Paths.get(moduleTempDir, EDGE_RESULT_FOLDER_NAME).toString();
 
  141         this.dataSource = dataSource;
 
  142         this.context = context;
 
  143         this.setFoundData(
false);
 
  145         List<AbstractFile> webCacheFiles = null;
 
  146         List<AbstractFile> spartanFiles = null;
 
  149             webCacheFiles = fetchWebCacheDBFiles();
 
  150         } 
catch (TskCoreException ex) {
 
  151             this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_errGettingWebCacheFiles());
 
  152             LOG.log(Level.SEVERE, 
"Error fetching 'WebCacheV01.dat' files for Microsoft Edge", ex); 
 
  155         if (context.dataSourceIngestIsCancelled()) {
 
  160             spartanFiles = fetchSpartanDBFiles(); 
 
  161         } 
catch (TskCoreException ex) {
 
  162             this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_spartanFail());
 
  163             LOG.log(Level.SEVERE, 
"Error fetching 'spartan.edb' files for Microsoft Edge", ex); 
 
  167         if (webCacheFiles == null && spartanFiles == null) {
 
  171         this.setFoundData(
true);
 
  173         if (!PlatformUtil.isWindowsOS()) {
 
  174             LOG.log(Level.WARNING, 
"Microsoft Edge files found, unable to parse on Non-Windows system"); 
 
  178         if (context.dataSourceIngestIsCancelled()) {
 
  182         final String esedumper = getPathForESEDumper();
 
  183         if (esedumper == null) {
 
  184             LOG.log(Level.SEVERE, 
"Error finding ESEDatabaseViewer program"); 
 
  185             this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_unableFindESEViewer());
 
  190             this.processWebCacheDbFile(esedumper, webCacheFiles, progressBar, moduleTempDir, moduleTempResultDir);
 
  191         } 
catch (IOException | TskCoreException ex) {
 
  192             LOG.log(Level.SEVERE, 
"Error processing 'WebCacheV01.dat' files for Microsoft Edge", ex); 
 
  193             this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_webcacheFail());
 
  196         progressBar.progress(Bundle.Progress_Message_Edge_Bookmarks());
 
  198             this.processSpartanDbFile(esedumper, spartanFiles, moduleTempDir, moduleTempResultDir);
 
  199         } 
catch (IOException | TskCoreException ex) {
 
  200             LOG.log(Level.SEVERE, 
"Error processing 'spartan.edb' files for Microsoft Edge", ex); 
 
  201             this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_spartanFail());
 
  216     void processWebCacheDbFile(String eseDumperPath, List<AbstractFile> webCacheFiles, DataSourceIngestModuleProgress progressBar, 
 
  217             String moduleTempDir, String moduleTempResultDir) 
throws IOException, TskCoreException {
 
  219         for (AbstractFile webCacheFile : webCacheFiles) {
 
  221             if (context.dataSourceIngestIsCancelled()) {
 
  225             clearContainerTable();
 
  228             String tempWebCacheFileName = EDGE_WEBCACHE_PREFIX
 
  229                     + Integer.toString((
int) webCacheFile.getId()) + EDGE_WEBCACHE_EXT; 
 
  230             File tempWebCacheFile = 
new File(moduleTempDir, tempWebCacheFileName);
 
  233                 ContentUtils.writeToFile(webCacheFile, tempWebCacheFile,
 
  234                         context::dataSourceIngestIsCancelled);
 
  235             } 
catch (IOException ex) {
 
  236                 throw new IOException(
"Error writingToFile: " + webCacheFile, ex); 
 
  239             File resultsDir = 
new File(moduleTempDir, Integer.toString((
int) webCacheFile.getId()));
 
  242                 executeDumper(eseDumperPath, tempWebCacheFile.getAbsolutePath(),
 
  243                         resultsDir.getAbsolutePath());
 
  245                 if (context.dataSourceIngestIsCancelled()) {
 
  249                 progressBar.progress(Bundle.Progress_Message_Edge_History());
 
  251                 this.getHistory(webCacheFile, resultsDir);
 
  253                 if (context.dataSourceIngestIsCancelled()) {
 
  257                 progressBar.progress(Bundle.Progress_Message_Edge_Cookies());
 
  259                 this.getCookies(webCacheFile, resultsDir);
 
  262                 tempWebCacheFile.delete();
 
  263                 FileUtil.deleteFileDir(resultsDir);
 
  279     void processSpartanDbFile(String eseDumperPath, List<AbstractFile> spartanFiles, 
 
  280             String moduleTempDir, String moduleTempResultDir) 
throws IOException, TskCoreException {
 
  282         for (AbstractFile spartanFile : spartanFiles) {
 
  284             if (context.dataSourceIngestIsCancelled()) {
 
  289             String tempSpartanFileName = EDGE_WEBCACHE_PREFIX
 
  290                     + Integer.toString((
int) spartanFile.getId()) + EDGE_WEBCACHE_EXT; 
 
  291             File tempSpartanFile = 
new File(moduleTempDir, tempSpartanFileName);
 
  294                 ContentUtils.writeToFile(spartanFile, tempSpartanFile,
 
  295                         context::dataSourceIngestIsCancelled);
 
  296             } 
catch (IOException ex) {
 
  297                 throw new IOException(
"Error writingToFile: " + spartanFile, ex); 
 
  300             File resultsDir = 
new File(moduleTempResultDir, Integer.toString((
int) spartanFile.getId()));
 
  303                 executeDumper(eseDumperPath, tempSpartanFile.getAbsolutePath(),
 
  304                         resultsDir.getAbsolutePath());
 
  306                 if (context.dataSourceIngestIsCancelled()) {
 
  310                 this.getBookmarks(spartanFile, resultsDir);
 
  313                 tempSpartanFile.delete();
 
  314                 FileUtil.deleteFileDir(resultsDir);
 
  328     private void getHistory(AbstractFile origFile, File resultDir) 
throws TskCoreException, FileNotFoundException {
 
  329         ArrayList<File> historyFiles = getHistoryFiles(resultDir);
 
  331         if (historyFiles == null) {
 
  335         for (File file : historyFiles) {
 
  336             if (context.dataSourceIngestIsCancelled()) {
 
  342                 fileScanner = 
new Scanner(
new FileInputStream(file.toString()));
 
  343             } 
catch (FileNotFoundException ex) {
 
  344                 LOG.log(Level.WARNING, 
"Unable to find the ESEDatabaseView file at " + file.getPath(), ex); 
 
  348             Collection<BlackboardArtifact> bbartifacts = 
new ArrayList<>();
 
  351                 List<String> headers = null;
 
  352                 while (fileScanner.hasNext()) {
 
  353                     if (context.dataSourceIngestIsCancelled()) {
 
  357                     String line = fileScanner.nextLine();
 
  358                     if (headers == null) {
 
  359                         headers = Arrays.asList(line.toLowerCase().split(
","));
 
  363                     if (line.contains(EDGE_KEYWORD_VISIT)) {
 
  364                         BlackboardArtifact ba = getHistoryArtifact(origFile, headers, line);
 
  374             if (!bbartifacts.isEmpty() && !context.dataSourceIngestIsCancelled()) {
 
  375                 postArtifacts(bbartifacts);
 
  388     private void getBookmarks(AbstractFile origFile, File resultDir) 
throws TskCoreException {
 
  390         File favoriteFile = 
new File(resultDir, EDGE_FAVORITE_FILE_NAME);
 
  393             fileScanner = 
new Scanner(
new FileInputStream(favoriteFile));
 
  394         } 
catch (FileNotFoundException ex) {
 
  400         Collection<BlackboardArtifact> bbartifacts = 
new ArrayList<>();
 
  403             List<String> headers = null;
 
  404             while (fileScanner.hasNext()) {
 
  405                 String line = fileScanner.nextLine();
 
  406                 if (headers == null) {
 
  407                     headers = Arrays.asList(line.toLowerCase().split(
","));
 
  411                 BlackboardArtifact ba = getBookmarkArtifact(origFile, headers, line);
 
  420         if (!bbartifacts.isEmpty() && !context.dataSourceIngestIsCancelled()) {
 
  421             postArtifacts(bbartifacts);
 
  432     private void getCookies(AbstractFile origFile, File resultDir) 
throws TskCoreException {
 
  433         File containerFiles[] = resultDir.listFiles((dir, name) -> name.toLowerCase().contains(EDGE_TABLE_TYPE_COOKIE));
 
  435         if (containerFiles == null) {
 
  439         for (File file : containerFiles) {
 
  440             if (context.dataSourceIngestIsCancelled()) {
 
  446                 fileScanner = 
new Scanner(
new FileInputStream(file.toString()));
 
  447             } 
catch (FileNotFoundException ex) {
 
  448                 LOG.log(Level.WARNING, 
"Unable to find the ESEDatabaseView file at " + file.getPath(), ex); 
 
  452             Collection<BlackboardArtifact> bbartifacts = 
new ArrayList<>();
 
  455                 List<String> headers = null;
 
  456                 while (fileScanner.hasNext()) {
 
  457                     if (context.dataSourceIngestIsCancelled()) {
 
  461                     String line = fileScanner.nextLine();
 
  462                     if (headers == null) {
 
  463                         headers = Arrays.asList(line.toLowerCase().split(
","));
 
  467                     BlackboardArtifact ba = getCookieArtifact(origFile, headers, line);
 
  476             if (!bbartifacts.isEmpty() && !context.dataSourceIngestIsCancelled()) {
 
  477                 postArtifacts(bbartifacts);
 
  492     private void getDownloads(AbstractFile origFile, File resultDir) 
throws TskCoreException, FileNotFoundException {
 
  493         ArrayList<File> downloadFiles = getDownloadFiles(resultDir);
 
  495         if (downloadFiles == null) {
 
  499         for (File file : downloadFiles) {
 
  500             if (context.dataSourceIngestIsCancelled()) {
 
  506                 fileScanner = 
new Scanner(
new FileInputStream(file.toString()));
 
  507             } 
catch (FileNotFoundException ex) {
 
  508                 LOG.log(Level.WARNING, 
"Unable to find the ESEDatabaseView file at " + file.getPath(), ex); 
 
  511             Collection<BlackboardArtifact> bbartifacts = 
new ArrayList<>();
 
  514                 List<String> headers = null;
 
  515                 while (fileScanner.hasNext()) {
 
  516                     if (context.dataSourceIngestIsCancelled()) {
 
  520                     String line = fileScanner.nextLine();
 
  521                     if (headers == null) {
 
  522                         headers = Arrays.asList(line.toLowerCase().split(
","));
 
  526                     if (line.contains(EDGE_TABLE_TYPE_DOWNLOAD)) {
 
  528                         BlackboardArtifact ba = getDownloadArtifact(origFile, headers, line);
 
  538             if(!context.dataSourceIngestIsCancelled()) {
 
  539                 postArtifacts(bbartifacts);
 
  549     private String getPathForESEDumper() {
 
  550         Path path = Paths.get(ESE_TOOL_FOLDER, ESE_TOOL_NAME);
 
  551         File eseToolFile = InstalledFileLocator.getDefault().locate(path.toString(),
 
  552                 ExtractEdge.class.getPackage().getName(), 
false);
 
  553         if (eseToolFile != null) {
 
  554             return eseToolFile.getAbsolutePath();
 
  566     private List<AbstractFile> fetchWebCacheDBFiles() throws TskCoreException {
 
  568                 = currentCase.getServices().getFileManager();
 
  569         return fileManager.
findFiles(dataSource, EDGE_WEBCACHE_NAME, EDGE_WEBCACHE_FOLDER_NAME);
 
  578     private List<AbstractFile> fetchSpartanDBFiles() throws TskCoreException {
 
  580                 = currentCase.getServices().getFileManager();
 
  581         return fileManager.
findFiles(dataSource, EDGE_SPARTAN_NAME, EDGE_SPARTAN_FOLDER_NAME);
 
  595     private void executeDumper(String dumperPath, String inputFilePath,
 
  596             String outputDir) 
throws IOException {
 
  598         final Path outputFilePath = Paths.get(outputDir, EDGE_OUTPUT_FILE_NAME);
 
  599         final Path errFilePath = Paths.get(outputDir, EDGE_ERROR_FILE_NAME);
 
  600         LOG.log(Level.INFO, 
"Writing ESEDatabaseViewer results to: {0}", outputDir); 
 
  602         List<String> commandLine = 
new ArrayList<>();
 
  603         commandLine.add(dumperPath);
 
  604         commandLine.add(
"/table");  
 
  605         commandLine.add(inputFilePath);
 
  606         commandLine.add(
"*");  
 
  607         commandLine.add(
"/scomma");  
 
  608         commandLine.add(outputDir + 
"\\" + 
"*.csv");  
 
  610         ProcessBuilder processBuilder = 
new ProcessBuilder(commandLine);
 
  611         processBuilder.redirectOutput(outputFilePath.toFile());
 
  612         processBuilder.redirectError(errFilePath.toFile());
 
  614         ExecUtil.execute(processBuilder, 
new DataSourceIngestModuleProcessTerminator(context, 
true));
 
  627     private BlackboardArtifact getHistoryArtifact(AbstractFile origFile, List<String> headers, String line) 
throws TskCoreException {
 
  628         String[] rowSplit = line.split(
",");
 
  630         int index = headers.indexOf(EDGE_HEAD_URL);
 
  631         String urlUserStr = rowSplit[index];
 
  633         String[] str = urlUserStr.split(
"@");
 
  634         String user = (str[0].replace(EDGE_KEYWORD_VISIT, 
"")).trim();
 
  637         index = headers.indexOf(EDGE_HEAD_ACCESSTIME);
 
  638         String accessTime = rowSplit[index].trim();
 
  639         Long ftime = parseTimestamp(accessTime);
 
  641         return createArtifactWithAttributes(TSK_WEB_HISTORY, origFile, createHistoryAttribute(url, ftime,
 
  644                 NetworkUtils.extractDomain(url), user));
 
  656     private BlackboardArtifact getCookieArtifact(AbstractFile origFile, List<String> headers, String line) 
throws TskCoreException {
 
  657         String[] lineSplit = line.split(
","); 
 
  659         String accessTime = lineSplit[headers.indexOf(EDGE_HEAD_LASTMOD)].trim();
 
  660         Long ftime = parseTimestamp(accessTime);
 
  662         String domain = lineSplit[headers.indexOf(EDGE_HEAD_RDOMAIN)].trim();
 
  663         String name = hexToChar(lineSplit[headers.indexOf(EDGE_HEAD_NAME)].trim());
 
  664         String value = hexToChar(lineSplit[headers.indexOf(EDGE_HEAD_VALUE)].trim());
 
  665         String url = flipDomain(domain);
 
  667         return createArtifactWithAttributes(TSK_WEB_COOKIE, origFile, createCookieAttributes(url, null, ftime, null, name, value, this.getName(), NetworkUtils.extractDomain(url)));
 
  683     private BlackboardArtifact getDownloadArtifact(AbstractFile origFile, List<String> headers, String line) 
throws TskCoreException {
 
  684         BlackboardArtifact bbart = null;
 
  686         String[] lineSplit = line.split(
","); 
 
  687         String rheader = lineSplit[headers.indexOf(EDGE_HEAD_RESPONSEHEAD)];
 
  704     private BlackboardArtifact getBookmarkArtifact(AbstractFile origFile, List<String> headers, String line) 
throws TskCoreException {
 
  706         String[] lineSplit = line.split(IGNORE_COMMA_IN_QUOTES_REGEX, -1);
 
  708         String url = lineSplit[headers.indexOf(EDGE_HEAD_URL)];
 
  709         String title = lineSplit[headers.indexOf(EDGE_HEAD_TITLE)].replace(
"\"", 
""); 
 
  715         return createArtifactWithAttributes(TSK_WEB_BOOKMARK, origFile, createBookmarkAttributes(url, title, null,
 
  716                 this.getName(), NetworkUtils.extractDomain(url)));
 
  734     private Long parseTimestamp(String timeStr) {
 
  737         if (previouslyValidDateFormat != null) {
 
  739                 return previouslyValidDateFormat.parse(timeStr).getTime() / 1000;
 
  740             } 
catch (ParseException ex) {
 
  747             SimpleDateFormat usDateFormat = 
new SimpleDateFormat(
"MM/dd/yyyy hh:mm:ss a"); 
 
  748             usDateFormat.setLenient(
false); 
 
  749             Long epochTime = usDateFormat.parse(timeStr).getTime();
 
  750             previouslyValidDateFormat = usDateFormat;
 
  751             return epochTime / 1000;
 
  752         } 
catch (ParseException ex) {
 
  758         boolean monthFirstFromLocale = 
true;
 
  759         String localeDatePattern = ((SimpleDateFormat) DateFormat.getDateInstance(
 
  760                         DateFormat.SHORT, Locale.getDefault())).toPattern();
 
  761         if (localeDatePattern.startsWith(
"d")) {
 
  762             monthFirstFromLocale = 
false;
 
  767         boolean monthFirst = monthFirstFromLocale;
 
  768         Pattern pattern = Pattern.compile(
"^([0-9]{1,2})[^0-9]([0-9]{1,2})");
 
  769         Matcher matcher = pattern.matcher(timeStr);
 
  770         if (matcher.find()) {
 
  771             int firstVal = Integer.parseInt(matcher.group(1));
 
  772             int secondVal = Integer.parseInt(matcher.group(2));
 
  776             } 
else if (secondVal > 12) {
 
  783         boolean hasAmPm = 
false;
 
  784         if (timeStr.endsWith(
"M") || timeStr.endsWith(
"m")) {
 
  789         boolean hasSlashes = 
false;
 
  790         if (timeStr.contains(
"/")) {
 
  795         String dateFormatPattern;
 
  798                 dateFormatPattern = 
"MM/dd/yyyy ";
 
  800                 dateFormatPattern = 
"MM.dd.yyyy ";
 
  804                 dateFormatPattern = 
"dd/MM/yyyy ";
 
  806                 dateFormatPattern = 
"dd.MM.yyyy ";
 
  811             dateFormatPattern += 
"hh:mm:ss a";
 
  813             dateFormatPattern += 
"HH:mm:ss";
 
  817             SimpleDateFormat dateFormat = 
new SimpleDateFormat(dateFormatPattern); 
 
  818             dateFormat.setLenient(
false); 
 
  819             Long epochTime = dateFormat.parse(timeStr).getTime();
 
  820             previouslyValidDateFormat = dateFormat;
 
  821             return epochTime / 1000;
 
  822         } 
catch (ParseException ex) {
 
  823             LOG.log(Level.WARNING, 
"Timestamp could not be parsed ({0})", timeStr); 
 
  834     private String hexToChar(String hexString) {
 
  835         String[] hexValues = hexString.split(
" "); 
 
  836         StringBuilder output = 
new StringBuilder();
 
  838         for (String str : hexValues) {
 
  840                 int value = Integer.parseInt(str, 16);
 
  842                     output.append((
char) value);
 
  844             } 
catch (NumberFormatException ex) {
 
  849         return output.toString();
 
  863     private String flipDomain(String domain) {
 
  864         if (domain == null || domain.isEmpty()) {
 
  868         String[] tokens = domain.split(
"\\."); 
 
  870         if (tokens.length < 2 || tokens.length > 3) {
 
  874         StringBuilder buf = 
new StringBuilder();
 
  875         if (tokens.length > 2) {
 
  876             buf.append(tokens[2]);
 
  879         buf.append(tokens[1]);
 
  881         buf.append(tokens[0]);
 
  883         return buf.toString();
 
  893     private ArrayList<File> getDownloadFiles(File resultDir) 
throws FileNotFoundException {
 
  894         return getContainerFiles(resultDir, EDGE_TABLE_TYPE_DOWNLOAD);
 
  904     private ArrayList<File> getHistoryFiles(File resultDir) 
throws FileNotFoundException {
 
  905         return getContainerFiles(resultDir, EDGE_TABLE_TYPE_HISTORY);
 
  916     private ArrayList<File> getContainerFiles(File resultDir, String type) 
throws FileNotFoundException {
 
  917         HashMap<String, ArrayList<String>> idTable = getContainerIDTable(resultDir);
 
  919         ArrayList<String> idList = idTable.get(type);
 
  920         if (idList == null) {
 
  924         ArrayList<File> fileList = 
new ArrayList<>();
 
  925         for (String str : idList) {
 
  926             String fileName = EDGE_CONTAINER_FILE_PREFIX + str + EDGE_CONTAINER_FILE_EXT;
 
  927             fileList.add(
new File(resultDir, fileName));
 
  943     private HashMap<String, ArrayList<String>> getContainerIDTable(File resultDir) 
throws FileNotFoundException {
 
  945         if (containersTable == null) {
 
  946             File containerFile = 
new File(resultDir, EDGE_CONTAINTERS_FILE_NAME);
 
  948             try (Scanner fileScanner = 
new Scanner(
new FileInputStream(containerFile))) {
 
  949                 List<String> headers = null;
 
  950                 containersTable = 
new HashMap<>();
 
  953                 while (fileScanner.hasNext()) {
 
  954                     String line = fileScanner.nextLine();
 
  955                     if (headers == null) {
 
  956                         headers = Arrays.asList(line.toLowerCase().split(
","));
 
  957                         nameIdx = headers.indexOf(EDGE_HEAD_NAME);
 
  958                         idIdx = headers.indexOf(EDGE_HEAD_CONTAINER_ID);
 
  960                         String[] row = line.split(
","); 
 
  961                         String name = row[nameIdx];
 
  962                         String 
id = row[idIdx];
 
  964                         ArrayList<String> idList = containersTable.get(name);
 
  965                         if (idList == null) {
 
  966                             idList = 
new ArrayList<>();
 
  967                             containersTable.put(name, idList);
 
  976         return containersTable;
 
  982     private void clearContainerTable(){
 
  983         containersTable = null;
 
List< AbstractFile > findFiles(String fileName)