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.util.Date;
 
   28 import java.text.ParseException;
 
   29 import java.text.SimpleDateFormat;
 
   30 import java.text.DateFormat;
 
   31 import java.util.ArrayList;
 
   32 import java.util.Arrays;
 
   33 import java.util.Collection;
 
   34 import java.util.HashMap;
 
   35 import java.util.List;
 
   36 import java.util.Locale;
 
   37 import java.util.Scanner;
 
   38 import java.util.logging.Level;
 
   39 import java.util.regex.Matcher;
 
   40 import java.util.regex.Pattern;
 
   41 import org.openide.modules.InstalledFileLocator;
 
   42 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 final Path moduleTempResultPath;
 
   66     private Content dataSource;
 
   67     private IngestJobContext context;
 
   68     private HashMap<String, ArrayList<String>> containersTable;
 
   70     private static final String EDGE = 
"Edge"; 
 
   72     private static final String EDGE_KEYWORD_VISIT = 
"Visited:"; 
 
   73     private static final String IGNORE_COMMA_IN_QUOTES_REGEX = 
",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)"; 
 
   75     private static final String EDGE_TABLE_TYPE_DOWNLOAD = 
"iedownload"; 
 
   76     private static final String EDGE_TABLE_TYPE_HISTORY = 
"History"; 
 
   77     private static final String EDGE_TABLE_TYPE_COOKIE = 
"cookie"; 
 
   79     private static final String EDGE_HEAD_URL = 
"url"; 
 
   80     private static final String EDGE_HEAD_ACCESSTIME = 
"accessedtime"; 
 
   81     private static final String EDGE_HEAD_NAME = 
"name"; 
 
   82     private static final String EDGE_HEAD_CONTAINER_ID = 
"containerid"; 
 
   83     private static final String EDGE_HEAD_RESPONSEHEAD = 
"responseheaders"; 
 
   84     private static final String EDGE_HEAD_TITLE = 
"title"; 
 
   85     private static final String EDGE_HEAD_RDOMAIN = 
"rdomain"; 
 
   86     private static final String EDGE_HEAD_VALUE = 
"value"; 
 
   87     private static final String EDGE_HEAD_LASTMOD = 
"lastmodified"; 
 
   89     private static final String EDGE_WEBCACHE_PREFIX = 
"WebCacheV01"; 
 
   90     private static final String EDGE_CONTAINER_FILE_PREFIX = 
"Container_"; 
 
   91     private static final String EDGE_CONTAINER_FILE_EXT = 
".csv"; 
 
   92     private static final String EDGE_WEBCACHE_EXT = 
".dat"; 
 
   94     private static final String ESE_TOOL_NAME = 
"ESEDatabaseView.exe"; 
 
   95     private static final String EDGE_WEBCACHE_NAME = 
"WebCacheV01.dat"; 
 
   96     private static final String EDGE_SPARTAN_NAME = 
"Spartan.edb"; 
 
   97     private static final String EDGE_CONTAINTERS_FILE_NAME = 
"Containers.csv"; 
 
   98     private static final String EDGE_FAVORITE_FILE_NAME = 
"Favorites.csv"; 
 
   99     private static final String EDGE_OUTPUT_FILE_NAME = 
"Output.txt"; 
 
  100     private static final String EDGE_ERROR_FILE_NAME = 
"File.txt"; 
 
  101     private static final String EDGE_WEBCACHE_FOLDER_NAME = 
"WebCache"; 
 
  102     private static final String EDGE_SPARTAN_FOLDER_NAME = 
"MicrosoftEdge"; 
 
  104     private static final String ESE_TOOL_FOLDER = 
"ESEDatabaseView"; 
 
  105     private static final String EDGE_RESULT_FOLDER_NAME = 
"results"; 
 
  109     private SimpleDateFormat previouslyValidDateFormat = null;
 
  112         "ExtractEdge_process_errMsg_unableFindESEViewer=Unable to find ESEDatabaseViewer",
 
  113         "ExtractEdge_process_errMsg_errGettingWebCacheFiles=Error trying to retrieving Edge WebCacheV01 file",
 
  114         "ExtractEdge_process_errMsg_webcacheFail=Failure processing Microsoft Edge WebCacheV01.dat file",
 
  115         "ExtractEdge_process_errMsg_spartanFail=Failure processing Microsoft Edge spartan.edb file",
 
  116         "ExtractEdge_Module_Name=Microsoft Edge",
 
  117         "ExtractEdge_getHistory_containerFileNotFound=Error while trying to analyze Edge history",
 
  118         "Progress_Message_Edge_History=Microsoft Edge History",
 
  119         "Progress_Message_Edge_Bookmarks=Microsoft Edge Bookmarks",
 
  120         "Progress_Message_Edge_Cookies=Microsoft Edge Cookies",
 
  126     ExtractEdge() throws NoCurrentCaseException {
 
  127         moduleTempResultPath = Paths.get(RAImageIngestModule.getRATempPath(Case.getCurrentCaseThrows(), EDGE), EDGE_RESULT_FOLDER_NAME);
 
  131     protected String getName() {
 
  132         return Bundle.ExtractEdge_Module_Name();
 
  136     void process(Content dataSource, IngestJobContext context, DataSourceIngestModuleProgress progressBar) {
 
  137         this.dataSource = dataSource;
 
  138         this.context = context;
 
  139         this.setFoundData(
false);
 
  141         List<AbstractFile> webCacheFiles = null;
 
  142         List<AbstractFile> spartanFiles = null;
 
  145             webCacheFiles = fetchWebCacheDBFiles();
 
  146         } 
catch (TskCoreException ex) {
 
  147             this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_errGettingWebCacheFiles());
 
  148             LOG.log(Level.SEVERE, 
"Error fetching 'WebCacheV01.dat' files for Microsoft Edge", ex); 
 
  152             spartanFiles = fetchSpartanDBFiles(); 
 
  153         } 
catch (TskCoreException ex) {
 
  154             this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_spartanFail());
 
  155             LOG.log(Level.SEVERE, 
"Error fetching 'spartan.edb' files for Microsoft Edge", ex); 
 
  159         if (webCacheFiles == null && spartanFiles == null) {
 
  163         this.setFoundData(
true);
 
  165         if (!PlatformUtil.isWindowsOS()) {
 
  166             LOG.log(Level.WARNING, 
"Microsoft Edge files found, unable to parse on Non-Windows system"); 
 
  170         final String esedumper = getPathForESEDumper();
 
  171         if (esedumper == null) {
 
  172             LOG.log(Level.SEVERE, 
"Error finding ESEDatabaseViewer program"); 
 
  173             this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_unableFindESEViewer());
 
  178             this.processWebCacheDbFile(esedumper, webCacheFiles, progressBar);
 
  179         } 
catch (IOException | TskCoreException ex) {
 
  180             LOG.log(Level.SEVERE, 
"Error processing 'WebCacheV01.dat' files for Microsoft Edge", ex); 
 
  181             this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_webcacheFail());
 
  184         progressBar.progress(Bundle.Progress_Message_Edge_Bookmarks());
 
  186             this.processSpartanDbFile(esedumper, spartanFiles);
 
  187         } 
catch (IOException | TskCoreException ex) {
 
  188             LOG.log(Level.SEVERE, 
"Error processing 'spartan.edb' files for Microsoft Edge", ex); 
 
  189             this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_spartanFail());
 
  202     void processWebCacheDbFile(String eseDumperPath, List<AbstractFile> webCacheFiles, DataSourceIngestModuleProgress progressBar) 
throws IOException, TskCoreException {
 
  204         for (AbstractFile webCacheFile : webCacheFiles) {
 
  206             if (context.dataSourceIngestIsCancelled()) {
 
  210             clearContainerTable();
 
  213             String tempWebCacheFileName = EDGE_WEBCACHE_PREFIX
 
  214                     + Integer.toString((
int) webCacheFile.getId()) + EDGE_WEBCACHE_EXT; 
 
  215             File tempWebCacheFile = 
new File(RAImageIngestModule.getRATempPath(currentCase, EDGE), tempWebCacheFileName);
 
  218                 ContentUtils.writeToFile(webCacheFile, tempWebCacheFile,
 
  219                         context::dataSourceIngestIsCancelled);
 
  220             } 
catch (IOException ex) {
 
  221                 throw new IOException(
"Error writingToFile: " + webCacheFile, ex); 
 
  224             File resultsDir = 
new File(moduleTempResultPath.toAbsolutePath() + Integer.toString((
int) webCacheFile.getId()));
 
  227                 executeDumper(eseDumperPath, tempWebCacheFile.getAbsolutePath(),
 
  228                         resultsDir.getAbsolutePath());
 
  230                 if (context.dataSourceIngestIsCancelled()) {
 
  234                 progressBar.progress(Bundle.Progress_Message_Edge_History());
 
  236                 this.getHistory(webCacheFile, resultsDir);
 
  238                 if (context.dataSourceIngestIsCancelled()) {
 
  242                 progressBar.progress(Bundle.Progress_Message_Edge_Cookies());
 
  244                 this.getCookies(webCacheFile, resultsDir);
 
  247                 tempWebCacheFile.delete();
 
  248                 FileUtil.deleteFileDir(resultsDir);
 
  262     void processSpartanDbFile(String eseDumperPath, List<AbstractFile> spartanFiles) 
throws IOException, TskCoreException {
 
  264         for (AbstractFile spartanFile : spartanFiles) {
 
  266             if (context.dataSourceIngestIsCancelled()) {
 
  271             String tempSpartanFileName = EDGE_WEBCACHE_PREFIX
 
  272                     + Integer.toString((
int) spartanFile.getId()) + EDGE_WEBCACHE_EXT; 
 
  273             File tempSpartanFile = 
new File(RAImageIngestModule.getRATempPath(currentCase, EDGE), tempSpartanFileName);
 
  276                 ContentUtils.writeToFile(spartanFile, tempSpartanFile,
 
  277                         context::dataSourceIngestIsCancelled);
 
  278             } 
catch (IOException ex) {
 
  279                 throw new IOException(
"Error writingToFile: " + spartanFile, ex); 
 
  282             File resultsDir = 
new File(moduleTempResultPath.toAbsolutePath() + Integer.toString((
int) spartanFile.getId()));
 
  285                 executeDumper(eseDumperPath, tempSpartanFile.getAbsolutePath(),
 
  286                         resultsDir.getAbsolutePath());
 
  288                 if (context.dataSourceIngestIsCancelled()) {
 
  292                 this.getBookmarks(spartanFile, resultsDir);
 
  295                 tempSpartanFile.delete();
 
  296                 FileUtil.deleteFileDir(resultsDir);
 
  310     private void getHistory(AbstractFile origFile, File resultDir) 
throws TskCoreException, FileNotFoundException {
 
  311         ArrayList<File> historyFiles = getHistoryFiles(resultDir);
 
  313         if (historyFiles == null) {
 
  317         for (File file : historyFiles) {
 
  318             if (context.dataSourceIngestIsCancelled()) {
 
  324                 fileScanner = 
new Scanner(
new FileInputStream(file.toString()));
 
  325             } 
catch (FileNotFoundException ex) {
 
  326                 LOG.log(Level.WARNING, 
"Unable to find the ESEDatabaseView file at " + file.getPath(), ex); 
 
  330             Collection<BlackboardArtifact> bbartifacts = 
new ArrayList<>();
 
  333                 List<String> headers = null;
 
  334                 while (fileScanner.hasNext()) {
 
  335                     if (context.dataSourceIngestIsCancelled()) {
 
  339                     String line = fileScanner.nextLine();
 
  340                     if (headers == null) {
 
  341                         headers = Arrays.asList(line.toLowerCase().split(
","));
 
  345                     if (line.contains(EDGE_KEYWORD_VISIT)) {
 
  346                         BlackboardArtifact ba = getHistoryArtifact(origFile, headers, line);
 
  356             if (!bbartifacts.isEmpty()) {
 
  357                 postArtifacts(bbartifacts);
 
  370     private void getBookmarks(AbstractFile origFile, File resultDir) 
throws TskCoreException {
 
  372         File favoriteFile = 
new File(resultDir, EDGE_FAVORITE_FILE_NAME);
 
  375             fileScanner = 
new Scanner(
new FileInputStream(favoriteFile));
 
  376         } 
catch (FileNotFoundException ex) {
 
  382         Collection<BlackboardArtifact> bbartifacts = 
new ArrayList<>();
 
  385             List<String> headers = null;
 
  386             while (fileScanner.hasNext()) {
 
  387                 String line = fileScanner.nextLine();
 
  388                 if (headers == null) {
 
  389                     headers = Arrays.asList(line.toLowerCase().split(
","));
 
  393                 BlackboardArtifact ba = getBookmarkArtifact(origFile, headers, line);
 
  402         if (!bbartifacts.isEmpty()) {
 
  403             postArtifacts(bbartifacts);
 
  414     private void getCookies(AbstractFile origFile, File resultDir) 
throws TskCoreException {
 
  415         File containerFiles[] = resultDir.listFiles((dir, name) -> name.toLowerCase().contains(EDGE_TABLE_TYPE_COOKIE));
 
  417         if (containerFiles == null) {
 
  421         for (File file : containerFiles) {
 
  422             if (context.dataSourceIngestIsCancelled()) {
 
  428                 fileScanner = 
new Scanner(
new FileInputStream(file.toString()));
 
  429             } 
catch (FileNotFoundException ex) {
 
  430                 LOG.log(Level.WARNING, 
"Unable to find the ESEDatabaseView file at " + file.getPath(), ex); 
 
  434             Collection<BlackboardArtifact> bbartifacts = 
new ArrayList<>();
 
  437                 List<String> headers = null;
 
  438                 while (fileScanner.hasNext()) {
 
  439                     if (context.dataSourceIngestIsCancelled()) {
 
  443                     String line = fileScanner.nextLine();
 
  444                     if (headers == null) {
 
  445                         headers = Arrays.asList(line.toLowerCase().split(
","));
 
  449                     BlackboardArtifact ba = getCookieArtifact(origFile, headers, line);
 
  458             if (!bbartifacts.isEmpty()) {
 
  459                 postArtifacts(bbartifacts);
 
  474     private void getDownloads(AbstractFile origFile, File resultDir) 
throws TskCoreException, FileNotFoundException {
 
  475         ArrayList<File> downloadFiles = getDownloadFiles(resultDir);
 
  477         if (downloadFiles == null) {
 
  481         for (File file : downloadFiles) {
 
  482             if (context.dataSourceIngestIsCancelled()) {
 
  488                 fileScanner = 
new Scanner(
new FileInputStream(file.toString()));
 
  489             } 
catch (FileNotFoundException ex) {
 
  490                 LOG.log(Level.WARNING, 
"Unable to find the ESEDatabaseView file at " + file.getPath(), ex); 
 
  493             Collection<BlackboardArtifact> bbartifacts = 
new ArrayList<>();
 
  496                 List<String> headers = null;
 
  497                 while (fileScanner.hasNext()) {
 
  498                     if (context.dataSourceIngestIsCancelled()) {
 
  502                     String line = fileScanner.nextLine();
 
  503                     if (headers == null) {
 
  504                         headers = Arrays.asList(line.toLowerCase().split(
","));
 
  508                     if (line.contains(EDGE_TABLE_TYPE_DOWNLOAD)) {
 
  510                         BlackboardArtifact ba = getDownloadArtifact(origFile, headers, line);
 
  520             postArtifacts(bbartifacts);
 
  529     private String getPathForESEDumper() {
 
  530         Path path = Paths.get(ESE_TOOL_FOLDER, ESE_TOOL_NAME);
 
  531         File eseToolFile = InstalledFileLocator.getDefault().locate(path.toString(),
 
  532                 ExtractEdge.class.getPackage().getName(), 
false);
 
  533         if (eseToolFile != null) {
 
  534             return eseToolFile.getAbsolutePath();
 
  546     private List<AbstractFile> fetchWebCacheDBFiles() throws TskCoreException {
 
  548                 = currentCase.getServices().getFileManager();
 
  549         return fileManager.
findFiles(dataSource, EDGE_WEBCACHE_NAME, EDGE_WEBCACHE_FOLDER_NAME);
 
  558     private List<AbstractFile> fetchSpartanDBFiles() throws TskCoreException {
 
  560                 = currentCase.getServices().getFileManager();
 
  561         return fileManager.
findFiles(dataSource, EDGE_SPARTAN_NAME, EDGE_SPARTAN_FOLDER_NAME);
 
  575     private void executeDumper(String dumperPath, String inputFilePath,
 
  576             String outputDir) 
throws IOException {
 
  578         final Path outputFilePath = Paths.get(outputDir, EDGE_OUTPUT_FILE_NAME);
 
  579         final Path errFilePath = Paths.get(outputDir, EDGE_ERROR_FILE_NAME);
 
  580         LOG.log(Level.INFO, 
"Writing ESEDatabaseViewer results to: {0}", outputDir); 
 
  582         List<String> commandLine = 
new ArrayList<>();
 
  583         commandLine.add(dumperPath);
 
  584         commandLine.add(
"/table");  
 
  585         commandLine.add(inputFilePath);
 
  586         commandLine.add(
"*");  
 
  587         commandLine.add(
"/scomma");  
 
  588         commandLine.add(outputDir + 
"\\" + 
"*.csv");  
 
  590         ProcessBuilder processBuilder = 
new ProcessBuilder(commandLine);
 
  591         processBuilder.redirectOutput(outputFilePath.toFile());
 
  592         processBuilder.redirectError(errFilePath.toFile());
 
  594         ExecUtil.execute(processBuilder, 
new DataSourceIngestModuleProcessTerminator(context, 
true));
 
  607     private BlackboardArtifact getHistoryArtifact(AbstractFile origFile, List<String> headers, String line) 
throws TskCoreException {
 
  608         String[] rowSplit = line.split(
",");
 
  610         int index = headers.indexOf(EDGE_HEAD_URL);
 
  611         String urlUserStr = rowSplit[index];
 
  613         String[] str = urlUserStr.split(
"@");
 
  614         String user = (str[0].replace(EDGE_KEYWORD_VISIT, 
"")).trim();
 
  617         index = headers.indexOf(EDGE_HEAD_ACCESSTIME);
 
  618         String accessTime = rowSplit[index].trim();
 
  619         Long ftime = parseTimestamp(accessTime);
 
  621         BlackboardArtifact bbart = origFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY);
 
  623         bbart.addAttributes(createHistoryAttribute(url, ftime,
 
  626                 NetworkUtils.extractDomain(url), user));
 
  640     private BlackboardArtifact getCookieArtifact(AbstractFile origFile, List<String> headers, String line) 
throws TskCoreException {
 
  641         String[] lineSplit = line.split(
","); 
 
  643         String accessTime = lineSplit[headers.indexOf(EDGE_HEAD_LASTMOD)].trim();
 
  644         Long ftime = parseTimestamp(accessTime);
 
  646         String domain = lineSplit[headers.indexOf(EDGE_HEAD_RDOMAIN)].trim();
 
  647         String name = hexToChar(lineSplit[headers.indexOf(EDGE_HEAD_NAME)].trim());
 
  648         String value = hexToChar(lineSplit[headers.indexOf(EDGE_HEAD_VALUE)].trim());
 
  649         String url = flipDomain(domain);
 
  651         BlackboardArtifact bbart = origFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_COOKIE);
 
  652         bbart.addAttributes(createCookieAttributes(url, ftime, name, value, this.getName(), NetworkUtils.extractDomain(url)));
 
  669     private BlackboardArtifact getDownloadArtifact(AbstractFile origFile, List<String> headers, String line) 
throws TskCoreException {
 
  670         BlackboardArtifact bbart = null;
 
  672         String[] lineSplit = line.split(
","); 
 
  673         String rheader = lineSplit[headers.indexOf(EDGE_HEAD_RESPONSEHEAD)];
 
  690     private BlackboardArtifact getBookmarkArtifact(AbstractFile origFile, List<String> headers, String line) 
throws TskCoreException {
 
  692         String[] lineSplit = line.split(IGNORE_COMMA_IN_QUOTES_REGEX, -1);
 
  694         String url = lineSplit[headers.indexOf(EDGE_HEAD_URL)];
 
  695         String title = lineSplit[headers.indexOf(EDGE_HEAD_TITLE)].replace(
"\"", 
""); 
 
  701         BlackboardArtifact bbart = origFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_BOOKMARK);
 
  702         bbart.addAttributes(createBookmarkAttributes(url, title, null,
 
  703                 this.getName(), NetworkUtils.extractDomain(url)));
 
  722     private Long parseTimestamp(String timeStr) {
 
  725         if (previouslyValidDateFormat != null) {
 
  727                 return previouslyValidDateFormat.parse(timeStr).getTime() / 1000;
 
  728             } 
catch (ParseException ex) {
 
  735             SimpleDateFormat usDateFormat = 
new SimpleDateFormat(
"MM/dd/yyyy hh:mm:ss a"); 
 
  736             usDateFormat.setLenient(
false); 
 
  737             Long epochTime = usDateFormat.parse(timeStr).getTime();
 
  738             previouslyValidDateFormat = usDateFormat;
 
  739             return epochTime / 1000;
 
  740         } 
catch (ParseException ex) {
 
  746         boolean monthFirstFromLocale = 
true;
 
  747         String localeDatePattern = ((SimpleDateFormat) DateFormat.getDateInstance(
 
  748                         DateFormat.SHORT, Locale.getDefault())).toPattern();
 
  749         if (localeDatePattern.startsWith(
"d")) {
 
  750             monthFirstFromLocale = 
false;
 
  755         boolean monthFirst = monthFirstFromLocale;
 
  756         Pattern pattern = Pattern.compile(
"^([0-9]{1,2})[^0-9]([0-9]{1,2})");
 
  757         Matcher matcher = pattern.matcher(timeStr);
 
  758         if (matcher.find()) {
 
  759             int firstVal = Integer.parseInt(matcher.group(1));
 
  760             int secondVal = Integer.parseInt(matcher.group(2));
 
  764             } 
else if (secondVal > 12) {
 
  771         boolean hasAmPm = 
false;
 
  772         if (timeStr.endsWith(
"M") || timeStr.endsWith(
"m")) {
 
  777         boolean hasSlashes = 
false;
 
  778         if (timeStr.contains(
"/")) {
 
  783         String dateFormatPattern;
 
  786                 dateFormatPattern = 
"MM/dd/yyyy ";
 
  788                 dateFormatPattern = 
"MM.dd.yyyy ";
 
  792                 dateFormatPattern = 
"dd/MM/yyyy ";
 
  794                 dateFormatPattern = 
"dd.MM.yyyy ";
 
  799             dateFormatPattern += 
"hh:mm:ss a";
 
  801             dateFormatPattern += 
"HH:mm:ss";
 
  805             SimpleDateFormat dateFormat = 
new SimpleDateFormat(dateFormatPattern); 
 
  806             dateFormat.setLenient(
false); 
 
  807             Long epochTime = dateFormat.parse(timeStr).getTime();
 
  808             previouslyValidDateFormat = dateFormat;
 
  809             return epochTime / 1000;
 
  810         } 
catch (ParseException ex) {
 
  811             LOG.log(Level.WARNING, 
"Timestamp could not be parsed ({0})", timeStr); 
 
  822     private String hexToChar(String hexString) {
 
  823         String[] hexValues = hexString.split(
" "); 
 
  824         StringBuilder output = 
new StringBuilder();
 
  826         for (String str : hexValues) {
 
  828                 int value = Integer.parseInt(str, 16);
 
  830                     output.append((
char) value);
 
  832             } 
catch (NumberFormatException ex) {
 
  837         return output.toString();
 
  851     private String flipDomain(String domain) {
 
  852         if (domain == null || domain.isEmpty()) {
 
  856         String[] tokens = domain.split(
"\\."); 
 
  858         if (tokens.length < 2 || tokens.length > 3) {
 
  862         StringBuilder buf = 
new StringBuilder();
 
  863         if (tokens.length > 2) {
 
  864             buf.append(tokens[2]);
 
  867         buf.append(tokens[1]);
 
  869         buf.append(tokens[0]);
 
  871         return buf.toString();
 
  881     private ArrayList<File> getDownloadFiles(File resultDir) 
throws FileNotFoundException {
 
  882         return getContainerFiles(resultDir, EDGE_TABLE_TYPE_DOWNLOAD);
 
  892     private ArrayList<File> getHistoryFiles(File resultDir) 
throws FileNotFoundException {
 
  893         return getContainerFiles(resultDir, EDGE_TABLE_TYPE_HISTORY);
 
  904     private ArrayList<File> getContainerFiles(File resultDir, String type) 
throws FileNotFoundException {
 
  905         HashMap<String, ArrayList<String>> idTable = getContainerIDTable(resultDir);
 
  907         ArrayList<String> idList = idTable.get(type);
 
  908         if (idList == null) {
 
  912         ArrayList<File> fileList = 
new ArrayList<>();
 
  913         for (String str : idList) {
 
  914             String fileName = EDGE_CONTAINER_FILE_PREFIX + str + EDGE_CONTAINER_FILE_EXT;
 
  915             fileList.add(
new File(resultDir, fileName));
 
  931     private HashMap<String, ArrayList<String>> getContainerIDTable(File resultDir) 
throws FileNotFoundException {
 
  933         if (containersTable == null) {
 
  934             File containerFile = 
new File(resultDir, EDGE_CONTAINTERS_FILE_NAME);
 
  936             try (Scanner fileScanner = 
new Scanner(
new FileInputStream(containerFile))) {
 
  937                 List<String> headers = null;
 
  938                 containersTable = 
new HashMap<>();
 
  941                 while (fileScanner.hasNext()) {
 
  942                     String line = fileScanner.nextLine();
 
  943                     if (headers == null) {
 
  944                         headers = Arrays.asList(line.toLowerCase().split(
","));
 
  945                         nameIdx = headers.indexOf(EDGE_HEAD_NAME);
 
  946                         idIdx = headers.indexOf(EDGE_HEAD_CONTAINER_ID);
 
  948                         String[] row = line.split(
","); 
 
  949                         String name = row[nameIdx];
 
  950                         String 
id = row[idIdx];
 
  952                         ArrayList<String> idList = containersTable.get(name);
 
  953                         if (idList == null) {
 
  954                             idList = 
new ArrayList<>();
 
  955                             containersTable.put(name, idList);
 
  964         return containersTable;
 
  970     private void clearContainerTable(){
 
  971         containersTable = null;
 
synchronized List< AbstractFile > findFiles(String fileName)