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;
 
   62 final class ExtractEdge 
extends Extract {
 
   64     private static final Logger LOG = Logger.getLogger(ExtractEdge.class.getName());
 
   65     private Content dataSource;
 
   66     private final IngestJobContext context;
 
   67     private HashMap<String, ArrayList<String>> containersTable;
 
   69     private static final String EDGE = 
"Edge"; 
 
   71     private static final String EDGE_KEYWORD_VISIT = 
"Visited:"; 
 
   72     private static final String IGNORE_COMMA_IN_QUOTES_REGEX = 
",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)"; 
 
   74     private static final String EDGE_TABLE_TYPE_DOWNLOAD = 
"iedownload"; 
 
   75     private static final String EDGE_TABLE_TYPE_HISTORY = 
"History"; 
 
   76     private static final String EDGE_TABLE_TYPE_COOKIE = 
"cookie"; 
 
   78     private static final String EDGE_HEAD_URL = 
"url"; 
 
   79     private static final String EDGE_HEAD_ACCESSTIME = 
"accessedtime"; 
 
   80     private static final String EDGE_HEAD_NAME = 
"name"; 
 
   81     private static final String EDGE_HEAD_CONTAINER_ID = 
"containerid"; 
 
   82     private static final String EDGE_HEAD_RESPONSEHEAD = 
"responseheaders"; 
 
   83     private static final String EDGE_HEAD_TITLE = 
"title"; 
 
   84     private static final String EDGE_HEAD_RDOMAIN = 
"rdomain"; 
 
   85     private static final String EDGE_HEAD_VALUE = 
"value"; 
 
   86     private static final String EDGE_HEAD_LASTMOD = 
"lastmodified"; 
 
   88     private static final String EDGE_WEBCACHE_PREFIX = 
"WebCacheV01"; 
 
   89     private static final String EDGE_CONTAINER_FILE_PREFIX = 
"Container_"; 
 
   90     private static final String EDGE_CONTAINER_FILE_EXT = 
".csv"; 
 
   91     private static final String EDGE_WEBCACHE_EXT = 
".dat"; 
 
   93     private static final String ESE_TOOL_NAME = 
"ESEDatabaseView.exe"; 
 
   94     private static final String EDGE_WEBCACHE_NAME = 
"WebCacheV01.dat"; 
 
   95     private static final String EDGE_SPARTAN_NAME = 
"Spartan.edb"; 
 
   96     private static final String EDGE_CONTAINTERS_FILE_NAME = 
"Containers.csv"; 
 
   97     private static final String EDGE_FAVORITE_FILE_NAME = 
"Favorites.csv"; 
 
   98     private static final String EDGE_OUTPUT_FILE_NAME = 
"Output.txt"; 
 
   99     private static final String EDGE_ERROR_FILE_NAME = 
"File.txt"; 
 
  100     private static final String EDGE_WEBCACHE_FOLDER_NAME = 
"WebCache"; 
 
  101     private static final String EDGE_SPARTAN_FOLDER_NAME = 
"MicrosoftEdge"; 
 
  103     private static final String ESE_TOOL_FOLDER = 
"ESEDatabaseView"; 
 
  104     private static final String EDGE_RESULT_FOLDER_NAME = 
"results"; 
 
  108     private SimpleDateFormat previouslyValidDateFormat = null;
 
  111         "ExtractEdge_process_errMsg_unableFindESEViewer=Unable to find ESEDatabaseViewer",
 
  112         "ExtractEdge_process_errMsg_errGettingWebCacheFiles=Error trying to retrieving Edge WebCacheV01 file",
 
  113         "ExtractEdge_process_errMsg_webcacheFail=Failure processing Microsoft Edge WebCacheV01.dat file",
 
  114         "ExtractEdge_process_errMsg_spartanFail=Failure processing Microsoft Edge spartan.edb file",
 
  115         "ExtractEdge_Module_Name=Microsoft Edge Analyzer",
 
  116         "ExtractEdge_getHistory_containerFileNotFound=Error while trying to analyze Edge history",
 
  117         "Progress_Message_Edge_History=Microsoft Edge History",
 
  118         "Progress_Message_Edge_Bookmarks=Microsoft Edge Bookmarks",
 
  119         "Progress_Message_Edge_Cookies=Microsoft Edge Cookies",})
 
  124     ExtractEdge(IngestJobContext context) {
 
  125         super(Bundle.ExtractEdge_Module_Name(), context);
 
  126         this.context = context;
 
  130     protected String getDisplayName() {
 
  131         return Bundle.ExtractEdge_Module_Name();
 
  135     void process(Content dataSource, DataSourceIngestModuleProgress progressBar) {
 
  136         String moduleTempDir = RAImageIngestModule.getRATempPath(getCurrentCase(), EDGE, context.getJobId());
 
  137         String moduleTempResultDir = Paths.get(moduleTempDir, EDGE_RESULT_FOLDER_NAME).toString();
 
  139         this.dataSource = dataSource;
 
  140         this.setFoundData(
false);
 
  142         List<AbstractFile> webCacheFiles = null;
 
  143         List<AbstractFile> spartanFiles = null;
 
  146             webCacheFiles = fetchWebCacheDBFiles();
 
  147         } 
catch (TskCoreException ex) {
 
  148             this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_errGettingWebCacheFiles());
 
  149             LOG.log(Level.SEVERE, 
"Error fetching 'WebCacheV01.dat' files for Microsoft Edge", ex); 
 
  152         if (context.dataSourceIngestIsCancelled()) {
 
  157             spartanFiles = fetchSpartanDBFiles(); 
 
  158         } 
catch (TskCoreException ex) {
 
  159             this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_spartanFail());
 
  160             LOG.log(Level.SEVERE, 
"Error fetching 'spartan.edb' files for Microsoft Edge", ex); 
 
  164         if (webCacheFiles == null && spartanFiles == null) {
 
  168         this.setFoundData(
true);
 
  170         if (!PlatformUtil.isWindowsOS()) {
 
  171             LOG.log(Level.WARNING, 
"Microsoft Edge files found, unable to parse on Non-Windows system"); 
 
  175         if (context.dataSourceIngestIsCancelled()) {
 
  179         final String esedumper = getPathForESEDumper();
 
  180         if (esedumper == null) {
 
  181             LOG.log(Level.SEVERE, 
"Error finding ESEDatabaseViewer program"); 
 
  182             this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_unableFindESEViewer());
 
  187             this.processWebCacheDbFile(esedumper, webCacheFiles, progressBar, moduleTempDir, moduleTempResultDir);
 
  188         } 
catch (IOException | TskCoreException ex) {
 
  189             LOG.log(Level.SEVERE, 
"Error processing 'WebCacheV01.dat' files for Microsoft Edge", ex); 
 
  190             this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_webcacheFail());
 
  193         progressBar.progress(Bundle.Progress_Message_Edge_Bookmarks());
 
  195             this.processSpartanDbFile(esedumper, spartanFiles, moduleTempDir, moduleTempResultDir);
 
  196         } 
catch (IOException | TskCoreException ex) {
 
  197             LOG.log(Level.SEVERE, 
"Error processing 'spartan.edb' files for Microsoft Edge", ex); 
 
  198             this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_spartanFail());
 
  214     void processWebCacheDbFile(String eseDumperPath, List<AbstractFile> webCacheFiles, DataSourceIngestModuleProgress progressBar,
 
  215             String moduleTempDir, String moduleTempResultDir) 
throws IOException, TskCoreException {
 
  216         for (AbstractFile webCacheFile : webCacheFiles) {
 
  218             if (context.dataSourceIngestIsCancelled()) {
 
  222             clearContainerTable();
 
  225             String tempWebCacheFileName = EDGE_WEBCACHE_PREFIX
 
  226                     + Integer.toString((
int) webCacheFile.getId()) + EDGE_WEBCACHE_EXT; 
 
  227             File tempWebCacheFile = 
new File(moduleTempDir, tempWebCacheFileName);
 
  230                 ContentUtils.writeToFile(webCacheFile, tempWebCacheFile,
 
  231                         context::dataSourceIngestIsCancelled);
 
  232             } 
catch (IOException ex) {
 
  233                 throw new IOException(
"Error writingToFile: " + webCacheFile, ex); 
 
  236             File resultsDir = 
new File(moduleTempDir, Integer.toString((
int) webCacheFile.getId()));
 
  239                 executeDumper(eseDumperPath, tempWebCacheFile.getAbsolutePath(),
 
  240                         resultsDir.getAbsolutePath());
 
  242                 if (context.dataSourceIngestIsCancelled()) {
 
  246                 progressBar.progress(Bundle.Progress_Message_Edge_History());
 
  248                 this.getHistory(webCacheFile, resultsDir);
 
  250                 if (context.dataSourceIngestIsCancelled()) {
 
  254                 progressBar.progress(Bundle.Progress_Message_Edge_Cookies());
 
  256                 this.getCookies(webCacheFile, resultsDir);
 
  259                 tempWebCacheFile.delete();
 
  260                 FileUtil.deleteFileDir(resultsDir);
 
  277     void processSpartanDbFile(String eseDumperPath, List<AbstractFile> spartanFiles, String moduleTempDir, String moduleTempResultDir) 
throws IOException, TskCoreException {
 
  278         for (AbstractFile spartanFile : spartanFiles) {
 
  280             if (context.dataSourceIngestIsCancelled()) {
 
  285             String tempSpartanFileName = EDGE_WEBCACHE_PREFIX
 
  286                     + Integer.toString((
int) spartanFile.getId()) + EDGE_WEBCACHE_EXT;
 
  287             File tempSpartanFile = 
new File(moduleTempDir, tempSpartanFileName);
 
  290                 ContentUtils.writeToFile(spartanFile, tempSpartanFile,
 
  291                         context::dataSourceIngestIsCancelled);
 
  292             } 
catch (IOException ex) {
 
  293                 throw new IOException(
"Error writingToFile: " + spartanFile, ex); 
 
  296             File resultsDir = 
new File(moduleTempResultDir, Integer.toString((
int) spartanFile.getId()));
 
  299                 executeDumper(eseDumperPath, tempSpartanFile.getAbsolutePath(),
 
  300                         resultsDir.getAbsolutePath());
 
  302                 if (context.dataSourceIngestIsCancelled()) {
 
  306                 this.getBookmarks(spartanFile, resultsDir);
 
  309                 tempSpartanFile.delete();
 
  310                 FileUtil.deleteFileDir(resultsDir);
 
  326     private void getHistory(AbstractFile origFile, File resultDir) 
throws TskCoreException, FileNotFoundException {
 
  327         ArrayList<File> historyFiles = getHistoryFiles(resultDir);
 
  328         if (historyFiles == null) {
 
  332         for (File file : historyFiles) {
 
  333             if (context.dataSourceIngestIsCancelled()) {
 
  339                 fileScanner = 
new Scanner(
new FileInputStream(file.toString()));
 
  340             } 
catch (FileNotFoundException ex) {
 
  341                 LOG.log(Level.WARNING, 
"Unable to find the ESEDatabaseView file at " + file.getPath(), ex); 
 
  345             Collection<BlackboardArtifact> bbartifacts = 
new ArrayList<>();
 
  348                 List<String> headers = null;
 
  349                 while (fileScanner.hasNext()) {
 
  350                     if (context.dataSourceIngestIsCancelled()) {
 
  354                     String line = fileScanner.nextLine();
 
  355                     if (headers == null) {
 
  356                         headers = Arrays.asList(line.toLowerCase().split(
","));
 
  360                     if (line.contains(EDGE_KEYWORD_VISIT)) {
 
  361                         BlackboardArtifact ba = getHistoryArtifact(origFile, headers, line);
 
  371             if (!bbartifacts.isEmpty() && !context.dataSourceIngestIsCancelled()) {
 
  372                 postArtifacts(bbartifacts);
 
  386     private void getBookmarks(AbstractFile origFile, File resultDir) 
throws TskCoreException {
 
  388         File favoriteFile = 
new File(resultDir, EDGE_FAVORITE_FILE_NAME);
 
  391             fileScanner = 
new Scanner(
new FileInputStream(favoriteFile));
 
  392         } 
catch (FileNotFoundException ex) {
 
  398         Collection<BlackboardArtifact> bbartifacts = 
new ArrayList<>();
 
  401             List<String> headers = null;
 
  402             while (fileScanner.hasNext()) {
 
  403                 String line = fileScanner.nextLine();
 
  404                 if (headers == null) {
 
  405                     headers = Arrays.asList(line.toLowerCase().split(
","));
 
  409                 BlackboardArtifact ba = getBookmarkArtifact(origFile, headers, line);
 
  418         if (!bbartifacts.isEmpty() && !context.dataSourceIngestIsCancelled()) {
 
  419             postArtifacts(bbartifacts);
 
  431     private void getCookies(AbstractFile origFile, File resultDir) 
throws TskCoreException {
 
  432         File containerFiles[] = resultDir.listFiles((dir, name) -> name.toLowerCase().contains(EDGE_TABLE_TYPE_COOKIE));
 
  434         if (containerFiles == null) {
 
  438         for (File file : containerFiles) {
 
  439             if (context.dataSourceIngestIsCancelled()) {
 
  445                 fileScanner = 
new Scanner(
new FileInputStream(file.toString()));
 
  446             } 
catch (FileNotFoundException ex) {
 
  447                 LOG.log(Level.WARNING, 
"Unable to find the ESEDatabaseView file at " + file.getPath(), ex); 
 
  451             Collection<BlackboardArtifact> bbartifacts = 
new ArrayList<>();
 
  454                 List<String> headers = null;
 
  455                 while (fileScanner.hasNext()) {
 
  456                     if (context.dataSourceIngestIsCancelled()) {
 
  460                     String line = fileScanner.nextLine();
 
  461                     if (headers == null) {
 
  462                         headers = Arrays.asList(line.toLowerCase().split(
","));
 
  466                     BlackboardArtifact ba = getCookieArtifact(origFile, headers, line);
 
  475             if (!bbartifacts.isEmpty() && !context.dataSourceIngestIsCancelled()) {
 
  476                 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);
 
  550     private String getPathForESEDumper() {
 
  551         Path path = Paths.get(ESE_TOOL_FOLDER, ESE_TOOL_NAME);
 
  552         File eseToolFile = InstalledFileLocator.getDefault().locate(path.toString(),
 
  553                 ExtractEdge.class.getPackage().getName(), 
false);
 
  554         if (eseToolFile != null) {
 
  555             return eseToolFile.getAbsolutePath();
 
  568     private List<AbstractFile> fetchWebCacheDBFiles() throws TskCoreException {
 
  570                 = currentCase.getServices().getFileManager();
 
  571         return fileManager.
findFiles(dataSource, EDGE_WEBCACHE_NAME, EDGE_WEBCACHE_FOLDER_NAME);
 
  581     private List<AbstractFile> fetchSpartanDBFiles() throws TskCoreException {
 
  583                 = currentCase.getServices().getFileManager();
 
  584         return fileManager.
findFiles(dataSource, EDGE_SPARTAN_NAME, EDGE_SPARTAN_FOLDER_NAME);
 
  599     private void executeDumper(String dumperPath, String inputFilePath,
 
  600             String outputDir) 
throws IOException {
 
  602         final Path outputFilePath = Paths.get(outputDir, EDGE_OUTPUT_FILE_NAME);
 
  603         final Path errFilePath = Paths.get(outputDir, EDGE_ERROR_FILE_NAME);
 
  604         LOG.log(Level.INFO, 
"Writing ESEDatabaseViewer results to: {0}", outputDir); 
 
  606         List<String> commandLine = 
new ArrayList<>();
 
  607         commandLine.add(dumperPath);
 
  608         commandLine.add(
"/table");  
 
  609         commandLine.add(inputFilePath);
 
  610         commandLine.add(
"*");  
 
  611         commandLine.add(
"/scomma");  
 
  612         commandLine.add(outputDir + 
"\\" + 
"*.csv");  
 
  614         ProcessBuilder processBuilder = 
new ProcessBuilder(commandLine);
 
  615         processBuilder.redirectOutput(outputFilePath.toFile());
 
  616         processBuilder.redirectError(errFilePath.toFile());
 
  618         ExecUtil.execute(processBuilder, 
new DataSourceIngestModuleProcessTerminator(context, 
true));
 
  633     private BlackboardArtifact getHistoryArtifact(AbstractFile origFile, List<String> headers, String line) 
throws TskCoreException {
 
  634         String[] rowSplit = line.split(
",");
 
  636         int index = headers.indexOf(EDGE_HEAD_URL);
 
  637         String urlUserStr = rowSplit[index];
 
  639         String[] str = urlUserStr.split(
"@");
 
  640         String user = (str[0].replace(EDGE_KEYWORD_VISIT, 
"")).trim();
 
  643         index = headers.indexOf(EDGE_HEAD_ACCESSTIME);
 
  644         String accessTime = rowSplit[index].trim();
 
  645         Long ftime = parseTimestamp(accessTime);
 
  647         return createArtifactWithAttributes(BlackboardArtifact.Type.TSK_WEB_HISTORY, origFile, createHistoryAttributes(url, ftime,
 
  649                 this.getDisplayName(),
 
  650                 NetworkUtils.extractDomain(url), user));
 
  664     private BlackboardArtifact getCookieArtifact(AbstractFile origFile, List<String> headers, String line) 
throws TskCoreException {
 
  665         String[] lineSplit = line.split(
","); 
 
  667         String accessTime = lineSplit[headers.indexOf(EDGE_HEAD_LASTMOD)].trim();
 
  668         Long ftime = parseTimestamp(accessTime);
 
  670         String domain = lineSplit[headers.indexOf(EDGE_HEAD_RDOMAIN)].trim();
 
  671         String name = hexToChar(lineSplit[headers.indexOf(EDGE_HEAD_NAME)].trim());
 
  672         String value = hexToChar(lineSplit[headers.indexOf(EDGE_HEAD_VALUE)].trim());
 
  673         String url = flipDomain(domain);
 
  675         return createArtifactWithAttributes(BlackboardArtifact.Type.TSK_WEB_COOKIE, origFile, createCookieAttributes(url, null, ftime, null, name, value, this.getDisplayName(), NetworkUtils.extractDomain(url)));
 
  693     private BlackboardArtifact getDownloadArtifact(AbstractFile origFile, List<String> headers, String line) 
throws TskCoreException {
 
  694         BlackboardArtifact bbart = null;
 
  696         String[] lineSplit = line.split(
","); 
 
  697         String rheader = lineSplit[headers.indexOf(EDGE_HEAD_RESPONSEHEAD)];
 
  717     private BlackboardArtifact getBookmarkArtifact(AbstractFile origFile, List<String> headers, String line) 
throws TskCoreException {
 
  719         String[] lineSplit = line.split(IGNORE_COMMA_IN_QUOTES_REGEX, -1);
 
  721         String url = lineSplit[headers.indexOf(EDGE_HEAD_URL)];
 
  722         String title = lineSplit[headers.indexOf(EDGE_HEAD_TITLE)].replace(
"\"", 
""); 
 
  728         return createArtifactWithAttributes(BlackboardArtifact.Type.TSK_WEB_BOOKMARK, origFile, createBookmarkAttributes(url, title, null,
 
  729                 this.getDisplayName(), NetworkUtils.extractDomain(url)));
 
  746     private Long parseTimestamp(String timeStr) {
 
  749         if (previouslyValidDateFormat != null) {
 
  751                 return previouslyValidDateFormat.parse(timeStr).getTime() / 1000;
 
  752             } 
catch (ParseException ex) {
 
  759             SimpleDateFormat usDateFormat = 
new SimpleDateFormat(
"MM/dd/yyyy hh:mm:ss a"); 
 
  760             usDateFormat.setLenient(
false); 
 
  761             Long epochTime = usDateFormat.parse(timeStr).getTime();
 
  762             previouslyValidDateFormat = usDateFormat;
 
  763             return epochTime / 1000;
 
  764         } 
catch (ParseException ex) {
 
  770         boolean monthFirstFromLocale = 
true;
 
  771         String localeDatePattern = ((SimpleDateFormat) DateFormat.getDateInstance(
 
  772                 DateFormat.SHORT, Locale.getDefault())).toPattern();
 
  773         if (localeDatePattern.startsWith(
"d")) {
 
  774             monthFirstFromLocale = 
false;
 
  779         boolean monthFirst = monthFirstFromLocale;
 
  780         Pattern pattern = Pattern.compile(
"^([0-9]{1,2})[^0-9]([0-9]{1,2})");
 
  781         Matcher matcher = pattern.matcher(timeStr);
 
  782         if (matcher.find()) {
 
  783             int firstVal = Integer.parseInt(matcher.group(1));
 
  784             int secondVal = Integer.parseInt(matcher.group(2));
 
  788             } 
else if (secondVal > 12) {
 
  795         boolean hasAmPm = 
false;
 
  796         if (timeStr.endsWith(
"M") || timeStr.endsWith(
"m")) {
 
  801         boolean hasSlashes = 
false;
 
  802         if (timeStr.contains(
"/")) {
 
  807         String dateFormatPattern;
 
  810                 dateFormatPattern = 
"MM/dd/yyyy ";
 
  812                 dateFormatPattern = 
"MM.dd.yyyy ";
 
  816                 dateFormatPattern = 
"dd/MM/yyyy ";
 
  818                 dateFormatPattern = 
"dd.MM.yyyy ";
 
  823             dateFormatPattern += 
"hh:mm:ss a";
 
  825             dateFormatPattern += 
"HH:mm:ss";
 
  829             SimpleDateFormat dateFormat = 
new SimpleDateFormat(dateFormatPattern); 
 
  830             dateFormat.setLenient(
false); 
 
  831             Long epochTime = dateFormat.parse(timeStr).getTime();
 
  832             previouslyValidDateFormat = dateFormat;
 
  833             return epochTime / 1000;
 
  834         } 
catch (ParseException ex) {
 
  835             LOG.log(Level.WARNING, 
"Timestamp could not be parsed ({0})", timeStr); 
 
  847     private String hexToChar(String hexString) {
 
  848         String[] hexValues = hexString.split(
" "); 
 
  849         StringBuilder output = 
new StringBuilder();
 
  851         for (String str : hexValues) {
 
  853                 int value = Integer.parseInt(str, 16);
 
  855                     output.append((
char) value);
 
  857             } 
catch (NumberFormatException ex) {
 
  862         return output.toString();
 
  877     private String flipDomain(String domain) {
 
  878         if (domain == null || domain.isEmpty()) {
 
  882         String[] tokens = domain.split(
"\\."); 
 
  884         if (tokens.length < 2 || tokens.length > 3) {
 
  888         StringBuilder buf = 
new StringBuilder();
 
  889         if (tokens.length > 2) {
 
  890             buf.append(tokens[2]);
 
  893         buf.append(tokens[1]);
 
  895         buf.append(tokens[0]);
 
  897         return buf.toString();
 
  908     private ArrayList<File> getDownloadFiles(File resultDir) 
throws FileNotFoundException {
 
  909         return getContainerFiles(resultDir, EDGE_TABLE_TYPE_DOWNLOAD);
 
  921     private ArrayList<File> getHistoryFiles(File resultDir) 
throws FileNotFoundException {
 
  922         return getContainerFiles(resultDir, EDGE_TABLE_TYPE_HISTORY);
 
  936     private ArrayList<File> getContainerFiles(File resultDir, String type) 
throws FileNotFoundException {
 
  937         HashMap<String, ArrayList<String>> idTable = getContainerIDTable(resultDir);
 
  939         ArrayList<String> idList = idTable.get(type);
 
  940         if (idList == null) {
 
  944         ArrayList<File> fileList = 
new ArrayList<>();
 
  945         for (String str : idList) {
 
  946             String fileName = EDGE_CONTAINER_FILE_PREFIX + str + EDGE_CONTAINER_FILE_EXT;
 
  947             fileList.add(
new File(resultDir, fileName));
 
  965     private HashMap<String, ArrayList<String>> getContainerIDTable(File resultDir) 
throws FileNotFoundException {
 
  967         if (containersTable == null) {
 
  968             File containerFile = 
new File(resultDir, EDGE_CONTAINTERS_FILE_NAME);
 
  970             try (Scanner fileScanner = 
new Scanner(
new FileInputStream(containerFile))) {
 
  971                 List<String> headers = null;
 
  972                 containersTable = 
new HashMap<>();
 
  975                 while (fileScanner.hasNext()) {
 
  976                     String line = fileScanner.nextLine();
 
  977                     if (headers == null) {
 
  978                         headers = Arrays.asList(line.toLowerCase().split(
","));
 
  979                         nameIdx = headers.indexOf(EDGE_HEAD_NAME);
 
  980                         idIdx = headers.indexOf(EDGE_HEAD_CONTAINER_ID);
 
  982                         String[] row = line.split(
","); 
 
  983                         String name = row[nameIdx];
 
  984                         String 
id = row[idIdx];
 
  986                         ArrayList<String> idList = containersTable.get(name);
 
  987                         if (idList == null) {
 
  988                             idList = 
new ArrayList<>();
 
  989                             containersTable.put(name, idList);
 
  998         return containersTable;
 
 1004     private void clearContainerTable() {
 
 1005         containersTable = null;
 
List< AbstractFile > findFiles(String fileName)