diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java index 22b2c1b98b..a41743784c 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java @@ -97,7 +97,7 @@ public class TagsManager implements Closeable { tagNames.clear(); tskCase.getTagNamesInUse(tagNames); } - + /** * Checks whether a tag name with a given display name exists. * @param [in] tagDisplayName The display name for which to check. @@ -227,6 +227,20 @@ public class TagsManager implements Closeable { tskCase.deleteContentTag(tag); } + /** + * Gets all content tags for the current case. + * @param [out] tags A list, possibly empty, of content tags. + * @throws TskCoreException + */ + public void getAllContentTags(List tags) throws TskCoreException { + // @@@ This is a work around to be removed when database access on the EDT is correctly synchronized. + if (!tagNamesInitialized) { + getExistingTagNames(); + } + + tskCase.getAllContentTags(tags); + } + /** * Gets content tags count by tag name. * @param [in] tagName The tag name of interest. @@ -297,6 +311,20 @@ public class TagsManager implements Closeable { tskCase.deleteBlackboardArtifactTag(tag); } + /** + * Gets all blackboard artifact tags for the current case. + * @param [out] tags A list, possibly empty, of blackboard artifact tags. + * @throws TskCoreException + */ + public void getAllBlackboardArtifactTags(List tags) throws TskCoreException { + // @@@ This is a work around to be removed when database access on the EDT is correctly synchronized. + if (!tagNamesInitialized) { + getExistingTagNames(); + } + + tskCase.getAllBlackboardArtifactTags(tags); + } + /** * Gets blackboard artifact tags count by tag name. * @param [in] tagName The tag name of interest. diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java b/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java index fbf1ba19f6..5a221e6ef6 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java @@ -121,40 +121,6 @@ public class ReportExcel implements TableReportModule { } } - - /** - * Start a new worksheet for the given data type. - * @param name data type name - */ - @Override - public void startDataType(String name) { - // Create a worksheet for the data type (assumed to be an artifact type). - name = escapeForExcel(name); - sheet = wb.createSheet(name); - sheet.setAutobreaks(true); - rowIndex = 0; - artifactsCount = 0; - - // Add a title row to the worksheet. - Row row = sheet.createRow(rowIndex); - row.setRowStyle(setStyle); - row.createCell(0).setCellValue(name); - ++rowIndex; - - // Add an artifacts count row. The actual count will be filled in later. - row = sheet.createRow(rowIndex); - row.setRowStyle(setStyle); - row.createCell(0).setCellValue("Number of artifacts:"); - ++rowIndex; - - // Add an empty row as a separator. - sheet.createRow(rowIndex); - ++rowIndex; - - // There will be at least two columns, one each for the artifacts count and its label. - sheetColCount = 2; - } - /** * Start a new worksheet for the given data type. * Note: This method is a temporary workaround to avoid modifying the TableReportModule interface. @@ -162,7 +128,8 @@ public class ReportExcel implements TableReportModule { * @param name Name of the data type * @param comment Comment on the data type, may be the empty string */ - public void startDataType(String name, String comment) { + @Override + public void startDataType(String name, String description) { // Create a worksheet for the data type (assumed to be an artifact type). name = escapeForExcel(name); sheet = wb.createSheet(name); @@ -183,10 +150,10 @@ public class ReportExcel implements TableReportModule { ++rowIndex; // Add a comment row, if a comment was supplied. - if (!comment.isEmpty()) { + if (!description.isEmpty()) { row = sheet.createRow(rowIndex); row.setRowStyle(setStyle); - row.createCell(0).setCellValue(comment); + row.createCell(0).setCellValue(description); ++rowIndex; } diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java b/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java index 45a551759d..792bfa169b 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java @@ -50,7 +50,6 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.EscapeUtil; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; -import org.sleuthkit.autopsy.datamodel.Tags; import org.sleuthkit.autopsy.report.ReportProgressPanel.ReportStatus; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; @@ -58,16 +57,15 @@ import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; import org.sleuthkit.datamodel.BlackboardArtifactTag; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE; +import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; /** - * Generates all TableReportModules and GeneralReportModules, given whether each module for both - * types is enabled or disabled, and the base report path to save them at. - * - * After creating an instance of ReportGenerator, one must tell it which reports to run, - * TableReportModules on Tags or Artifacts, and the GeneralReportModules. - * Then, one calls displayProgressPanels() to display the progress to the user. + * Instances of this class use GeneralReportModules, TableReportModules and + * FileReportModules to generate a report. If desired, displayProgressPanels() + * can be called to show report generation progress using ReportProgressPanel + * objects displayed using a dialog box. */ public class ReportGenerator { private static final Logger logger = Logger.getLogger(ReportGenerator.class.getName()); @@ -106,10 +104,11 @@ public class ReportGenerator { } /** - * For every ReportModule which the user enabled, create a ReportProgressPanel for that report. + * Create a ReportProgressPanel for each report generation module selected by the user. * - * @param tableModuleStates the enabled/disabled state of each TableReportModule - * @param generalModuleStates the enabled/disabled state of each GeneralReportModule + * @param tableModuleStates The enabled/disabled state of each TableReportModule + * @param generalModuleStates The enabled/disabled state of each GeneralReportModule + * @param fileListModuleStates The enabled/disabled state of each FileReportModule */ private void setupProgressPanels(Map tableModuleStates, Map generalModuleStates, Map fileListModuleStates) { if (null != tableModuleStates) { @@ -174,28 +173,28 @@ public class ReportGenerator { } /** - * Generate the GeneralReportModule reports in a new SwingWorker. + * Run the GeneralReportModules using a SwingWorker. */ public void generateGeneralReports() { - GeneralWorker worker = new GeneralWorker(); + GeneralReportsWorker worker = new GeneralReportsWorker(); worker.execute(); } /** - * Generate the TableReportModule reports on Blackboard Artifacts in a new SwingWorker. + * Run the TableReportModules using a SwingWorker. * * @param artifactTypeSelections the enabled/disabled state of the artifact types to be included in the report - * @param tagSelections the enabled/disabled state of the tags to be included in the report + * @param tagSelections the enabled/disabled state of the tag names to be included in the report */ - public void generateArtifactTableReports(Map artifactTypeSelections, Map tagSelections) { + public void generateBlackboardArtifactsReports(Map artifactTypeSelections, Map tagNameSelections) { if (!tableProgress.isEmpty() && null != artifactTypeSelections) { - ArtifactsReportsWorker worker = new ArtifactsReportsWorker(artifactTypeSelections, tagSelections); + TableReportsWorker worker = new TableReportsWorker(artifactTypeSelections, tagNameSelections); worker.execute(); } } /** - * Generate the FileReportModule reports in a new SwingWorker. + * Run the FileReportModules using a SwingWorker. * * @param enabledInfo the Information that should be included about each file * in the report. @@ -214,9 +213,9 @@ public class ReportGenerator { } /** - * SwingWorker to generate a report on all GeneralReportModules. + * SwingWorker to run GeneralReportModules. */ - private class GeneralWorker extends SwingWorker { + private class GeneralReportsWorker extends SwingWorker { @Override protected Integer doInBackground() throws Exception { @@ -232,7 +231,7 @@ public class ReportGenerator { } /** - * SwingWorker to generate a FileReport. + * SwingWorker to run FileReportModules. */ private class FileReportsWorker extends SwingWorker { private List enabledInfo = Arrays.asList(FileReportDataTypes.values()); @@ -317,15 +316,15 @@ public class ReportGenerator { } /** - * SwingWorker to generate reports on blackboard artifacts. + * SwingWorker to run TableReportModules to report on blackboard artifacts, + * content tags, and blackboard artifact tags. */ - private class ArtifactsReportsWorker extends SwingWorker { + private class TableReportsWorker extends SwingWorker { private List tableModules = new ArrayList<>(); private List artifactTypes = new ArrayList<>(); private HashSet tagNamesFilter = new HashSet<>(); - // Create an ArtifactWorker with the enabled/disabled state of all Artifacts - ArtifactsReportsWorker(Map artifactTypeSelections, Map tagSelections) { + TableReportsWorker(Map artifactTypeSelections, Map tagNameSelections) { // Get the report modules selected by the user. for (Entry entry : tableProgress.entrySet()) { tableModules.add(entry.getKey()); @@ -338,9 +337,9 @@ public class ReportGenerator { } } - // Get the tags selected by the user. - if (null != tagSelections) { - for (Entry entry : tagSelections.entrySet()) { + // Get the tag names selected by the user and make a tag names filter. + if (null != tagNameSelections) { + for (Entry entry : tagNameSelections.entrySet()) { if (entry.getValue() == true) { tagNamesFilter.add(entry.getKey()); } @@ -350,39 +349,46 @@ public class ReportGenerator { @Override protected Integer doInBackground() throws Exception { - // Start the report + // Start the progress indicators for each active TableReportModule. for (TableReportModule module : tableModules) { ReportProgressPanel progress = tableProgress.get(module); if (progress.getStatus() != ReportStatus.CANCELED) { module.startReport(reportPath); progress.start(); progress.setIndeterminate(false); - progress.setMaximumProgress(ARTIFACT_TYPE.values().length); + progress.setMaximumProgress(ARTIFACT_TYPE.values().length + 2); // +2 for content and blackboard artifact tags } } + + makeBlackboardArtifactTables(); + makeContentTagsTables(); + makeBlackboardArtifactTagsTables(); - // Make a comment on the tags filter. + for (TableReportModule module : tableModules) { + tableProgress.get(module).complete(); + module.endReport(); + } + + return 0; + } + + private void makeBlackboardArtifactTables() { + // Make a comment string describing the tag names filter in effect. StringBuilder comment = new StringBuilder(); if (!tagNamesFilter.isEmpty()) { - comment.append("This report only includes files and artifacts tagged with: "); + comment.append("This report only includes results tagged with: "); comment.append(makeCommaSeparatedList(tagNamesFilter)); } - - // For every enabled artifact type + + // Add a table to the report for every enabled blackboard artifact type. for (ARTIFACT_TYPE type : artifactTypes) { - // Check to see if all the TableReportModules have been canceled + // Check for cancellaton. + removeCancelledTableReportModules(); if (tableModules.isEmpty()) { - break; + return; } - Iterator iter = tableModules.iterator(); - while (iter.hasNext()) { - TableReportModule module = iter.next(); - if (tableProgress.get(module).getStatus() == ReportStatus.CANCELED) { - iter.remove(); - } - } - - // If the type is keyword hit or hashset hit, use the helper + + // Keyword hits and hashset hit artifacts get sepcial handling. if (type.equals(ARTIFACT_TYPE.TSK_KEYWORD_HIT)) { writeKeywordHits(tableModules, comment.toString(), tagNamesFilter); continue; @@ -392,20 +398,19 @@ public class ReportGenerator { } List unsortedArtifacts = getFilteredArtifacts(type, tagNamesFilter); - if (unsortedArtifacts.isEmpty()) { - // Don't report on this artifact type if there are no results continue; } + // The most efficient way to sort all the Artifacts is to add them to a List, and then // sort that List based off a Comparator. Adding to a TreeMap/Set/List sorts the list // each time an element is added, which adds unnecessary overhead if we only need it sorted once. Collections.sort(unsortedArtifacts); // Get the column headers appropriate for the artifact type. - /* @@@ BC: Seems like a better design here woudl be to have a method that - * takes in teh artifact as an argument andreturns the attributes. We then use that - * to make the headers and to make each row afterwards so that we don't ahve artifact-specific + /* @@@ BC: Seems like a better design here would be to have a method that + * takes in the artifact as an argument and returns the attributes. We then use that + * to make the headers and to make each row afterwards so that we don't have artifact-specific * logic in both getArtifactTableCoumnHeaders and getArtifactRow() */ List columnHeaders = getArtifactTableColumnHeaders(type.getTypeID()); @@ -414,37 +419,17 @@ public class ReportGenerator { MessageNotifyUtil.Notify.show("Skipping artifact type " + type + " in reports", "Unknown columns to report on", MessageNotifyUtil.MessageType.ERROR); continue; } - + for (TableReportModule module : tableModules) { tableProgress.get(module).updateStatusLabel("Now processing " + type.getDisplayName() + "..."); - - // This is a temporary workaround to avoid modifying the TableReportModule interface. - if (module instanceof ReportHTML) { - ReportHTML htmlReportModule = (ReportHTML)module; - htmlReportModule.startDataType(type.getDisplayName(), comment.toString()); - htmlReportModule.startTable(columnHeaders, type); - } - else if (module instanceof ReportExcel) { - ReportExcel excelReportModule = (ReportExcel)module; - excelReportModule.startDataType(type.getDisplayName(), comment.toString()); - excelReportModule.startTable(columnHeaders); - } - else { - module.startDataType(type.getDisplayName()); - module.startTable(columnHeaders); - } + module.startDataType(type.getDisplayName(), comment.toString()); + module.startTable(columnHeaders); } - + boolean msgSent = false; - for(ArtifactData artifactData : unsortedArtifacts) { -// HashSet tags = artifactData.getTags(); -// -// String tagsList = makeCommaSeparatedList(tags); - + for(ArtifactData artifactData : unsortedArtifacts) { // Add the row data to all of the reports. - for (TableReportModule module : tableModules) { - - // Get the row data for this type of artifact. + for (TableReportModule module : tableModules) { List rowData; rowData = getArtifactRow(artifactData, module); if (rowData.isEmpty()) { @@ -455,44 +440,160 @@ public class ReportGenerator { continue; } -// // This is a temporary workaround to avoid modifying the TableReportModule interface. -// if (module instanceof ReportHTML) { -// ReportHTML htmlReportModule = (ReportHTML)module; -// htmlReportModule.addRow(rowData, artifactData.getArtifact()); -// } -// else { - module.addRow(rowData); -// } + module.addRow(rowData); } } - + // Finish up this data type for (TableReportModule module : tableModules) { tableProgress.get(module).increment(); module.endTable(); module.endDataType(); } - } - - // End the report - for (TableReportModule module : tableModules) { - tableProgress.get(module).complete(); - module.endReport(); - } - - return 0; + } } + + private void makeContentTagsTables() { + // Check for cancellaton. + removeCancelledTableReportModules(); + if (tableModules.isEmpty()) { + return; + } + + // Get the content tags. + ArrayList tags = new ArrayList<>(); + try { + Case.getCurrentCase().getServices().getTagsManager().getAllContentTags(tags); + } + catch (TskCoreException ex) { + logger.log(Level.SEVERE, "failed to get content tags", ex); + return; + } + + // Tell the modules reporting on content tags is beginning. + for (TableReportModule module : tableModules) { + // @@@ This casting is a tricky little workaround to allow the HTML report module to slip in a content hyperlink. + // @@@ Alos Using the obsolete ARTIFACT_TYPE.TSK_TAG_FILE is also an expedient hack. + tableProgress.get(module).updateStatusLabel("Now processing " + ARTIFACT_TYPE.TSK_TAG_FILE.getDisplayName() + "..."); + ArrayList columnHeaders = new ArrayList<>(Arrays.asList("File", "Tag", "Comment")); + StringBuilder comment = new StringBuilder(); + if (!tagNamesFilter.isEmpty()) { + comment.append("This report only includes file tagged with: "); + comment.append(makeCommaSeparatedList(tagNamesFilter)); + } + if (module instanceof ReportHTML) { + ReportHTML htmlReportModule = (ReportHTML)module; + htmlReportModule.startDataType(ARTIFACT_TYPE.TSK_TAG_FILE.getDisplayName(), comment.toString()); + htmlReportModule.startContentTagsTable(columnHeaders); + } + else { + module.startDataType(ARTIFACT_TYPE.TSK_TAG_FILE.getDisplayName(), comment.toString()); + module.startTable(columnHeaders); + } + } + + // Give the modules the rows for the content tags. + for (ContentTag tag : tags) { + // Apply the tag names filter. + if (!tagNamesFilter.isEmpty()) { + if (tagNamesFilter.contains(tag.getName().getDisplayName())) { + continue; + } + } + + ArrayList rowData = new ArrayList<>(Arrays.asList(tag.getContent().getName(), tag.getName().getDisplayName(), tag.getComment())); + for (TableReportModule module : tableModules) { + // @@@ This casting is a tricky little workaround to allow the HTML report module to slip in a content hyperlink. + if (module instanceof ReportHTML) { + ReportHTML htmlReportModule = (ReportHTML)module; + htmlReportModule.addRowWithTaggedContentHyperlink(rowData, tag); + } + else { + module.addRow(rowData); + } + } + } + + // The the modules content tags reporting is ended. + for (TableReportModule module : tableModules) { + tableProgress.get(module).increment(); + module.endTable(); + module.endDataType(); + } + } + + private void makeBlackboardArtifactTagsTables() { + // Check for cancellaton. + removeCancelledTableReportModules(); + if (tableModules.isEmpty()) { + return; + } + + ArrayList tags = new ArrayList<>(); + try { + Case.getCurrentCase().getServices().getTagsManager().getAllBlackboardArtifactTags(tags); + } + catch (TskCoreException ex) { + logger.log(Level.SEVERE, "failed to get blackboard artifact tags", ex); + return; + } + + // Tell the modules reporting on blackboard artifact tags data type is beginning. + // @@@ Using the obsolete ARTIFACT_TYPE.TSK_TAG_ARTIFACT is an expedient hack. + for (TableReportModule module : tableModules) { + tableProgress.get(module).updateStatusLabel("Now processing " + ARTIFACT_TYPE.TSK_TAG_ARTIFACT.getDisplayName() + "..."); + StringBuilder comment = new StringBuilder(); + if (!tagNamesFilter.isEmpty()) { + comment.append("This report only includes results tagged with: "); + comment.append(makeCommaSeparatedList(tagNamesFilter)); + } + module.startDataType(ARTIFACT_TYPE.TSK_TAG_ARTIFACT.getDisplayName(), comment.toString()); + module.startTable(new ArrayList<>(Arrays.asList("Result Type", "Tag", "Comment", "Source File"))); + } + + // Give the modules the rows for the content tags. + for (BlackboardArtifactTag tag : tags) { + // Apply the tag names filter. + if (!tagNamesFilter.isEmpty()) { + if (tagNamesFilter.contains(tag.getName().getDisplayName())) { + continue; + } + } + + for (TableReportModule module : tableModules) { + module.addRow(new ArrayList<>(Arrays.asList(tag.getArtifact().getArtifactTypeName(), tag.getName().getDisplayName(), tag.getComment(), tag.getContent().getName()))); + } + } + + // The the modules blackboard artifact tags reporting is ended. + for (TableReportModule module : tableModules) { + tableProgress.get(module).increment(); + module.endTable(); + module.endDataType(); + } + } + + void removeCancelledTableReportModules() { + Iterator iter = tableModules.iterator(); + while (iter.hasNext()) { + TableReportModule module = iter.next(); + if (tableProgress.get(module).getStatus() == ReportStatus.CANCELED) { + iter.remove(); + } + } + } } - - private Boolean failsTagFilter(HashSet tags, HashSet tagsFilter) + + /// @@@ Should move the methods specific to TableReportsWorker into that scope. + private Boolean failsTagFilter(HashSet tagNames, HashSet tagsNamesFilter) { - if (null == tagsFilter || tagsFilter.isEmpty()) { + if (null == tagsNamesFilter || tagsNamesFilter.isEmpty()) { return false; } - HashSet filteredTags = new HashSet<>(tags); - filteredTags.retainAll(tagsFilter); - return filteredTags.isEmpty(); + HashSet filteredTagNames = new HashSet<>(tagNames); + filteredTagNames.retainAll(tagsNamesFilter); + return filteredTagNames.isEmpty(); } /** @@ -554,18 +655,7 @@ public class ReportGenerator { // Make keyword data type and give them set index for (TableReportModule module : tableModules) { - // This is a temporary workaround to avoid modifying the TableReportModule interface. - if (module instanceof ReportHTML) { - ReportHTML htmlReportModule = (ReportHTML)module; - htmlReportModule.startDataType(ARTIFACT_TYPE.TSK_KEYWORD_HIT.getDisplayName(), comment); - } - else if (module instanceof ReportExcel) { - ReportExcel excelReportModule = (ReportExcel)module; - excelReportModule.startDataType(ARTIFACT_TYPE.TSK_KEYWORD_HIT.getDisplayName(), comment); - } - else { - module.startDataType(ARTIFACT_TYPE.TSK_KEYWORD_HIT.getDisplayName()); - } + module.startDataType(ARTIFACT_TYPE.TSK_KEYWORD_HIT.getDisplayName(), comment); module.addSetIndex(lists); tableProgress.get(module).updateStatusLabel("Now processing " + ARTIFACT_TYPE.TSK_KEYWORD_HIT.getDisplayName() + "..."); @@ -708,18 +798,7 @@ public class ReportGenerator { } for (TableReportModule module : tableModules) { - // This is a temporary workaround to avoid modifying the TableReportModule interface. - if (module instanceof ReportHTML) { - ReportHTML htmlReportModule = (ReportHTML)module; - htmlReportModule.startDataType(ARTIFACT_TYPE.TSK_HASHSET_HIT.getDisplayName(), comment); - } - else if (module instanceof ReportExcel) { - ReportExcel excelReportModule = (ReportExcel)module; - excelReportModule.startDataType(ARTIFACT_TYPE.TSK_HASHSET_HIT.getDisplayName(), comment); - } - else { - module.startDataType(ARTIFACT_TYPE.TSK_HASHSET_HIT.getDisplayName()); - } + module.startDataType(ARTIFACT_TYPE.TSK_HASHSET_HIT.getDisplayName(), comment); module.addSetIndex(lists); tableProgress.get(module).updateStatusLabel("Now processing " + ARTIFACT_TYPE.TSK_HASHSET_HIT.getDisplayName() + "..."); @@ -981,7 +1060,7 @@ public class ReportGenerator { * @return List row values * @throws TskCoreException */ - private List getArtifactRow(ArtifactData artifactData, TableReportModule module) throws TskCoreException { + private List getArtifactRow(ArtifactData artifactData, TableReportModule module) { Map attributes = getMappedAttributes(artifactData.getAttributes(), module); List rowData = new ArrayList<>(); @@ -1152,7 +1231,7 @@ public class ReportGenerator { } rowData.add(makeCommaSeparatedList(artifactData.getTags())); - return rowData; // RJCTODO: Is anyone checking for null here? + return rowData; } /** diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java b/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java index c3d0466119..14f47a798b 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java @@ -28,7 +28,6 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.FileInputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; @@ -45,17 +44,17 @@ import org.openide.filesystems.FileUtil; import org.openide.util.Exceptions; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.datamodel.Tags; -import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.autopsy.datamodel.ContentUtils.ExtractFscContentVisitor; +import org.sleuthkit.autopsy.datamodel.Tags; import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Image; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.TskData; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM; public class ReportHTML implements TableReportModule { @@ -91,7 +90,7 @@ public class ReportHTML implements TableReportModule { currentCase = Case.getCurrentCase(); skCase = currentCase.getSleuthkitCase(); - dataTypes = new TreeMap(); + dataTypes = new TreeMap<>(); path = ""; currentDataType = ""; @@ -129,10 +128,10 @@ public class ReportHTML implements TableReportModule { { String iconFilePath; String iconFileName; - InputStream in = null; + InputStream in; OutputStream output = null; - logger.log(Level.INFO, "useDataTypeIcon: dataType = " + dataType); + logger.log(Level.INFO, "useDataTypeIcon: dataType = {0}", dataType); // find the artifact with matching display name BlackboardArtifact.ARTIFACT_TYPE artifactType = null; @@ -296,39 +295,6 @@ public class ReportHTML implements TableReportModule { } } } - - /** - * Start a new HTML page for the given data type. Update the output stream to this page, - * and setup the web page header. - * @param title title of the data type - */ - @Override - public void startDataType(String title) { - String fTitle = dataTypeToFileName(title); - // Make a new out for this page - try { - //escape out slashes tha that appear in title - - out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(path + fTitle + getExtension()), "UTF-8")); - } catch (FileNotFoundException ex) { - logger.log(Level.SEVERE, "File not found: {0}", ex); - } catch (UnsupportedEncodingException ex) { - logger.log(Level.SEVERE, "Unrecognized encoding"); - } - - // Write the beginnings of a page - // Like , header, title, any content divs - try { - StringBuilder page = new StringBuilder(); - page.append("\n\n\t").append(title).append("\n\t\n\n\n\n"); - page.append("
").append(title).append("
\n
\n"); - out.write(page.toString()); - currentDataType = title; - rowCount = 0; - } catch (IOException ex) { - logger.log(Level.SEVERE, "Failed to write page head: {0}", ex); - } - } /** * Start a new HTML page for the given data type. Update the output stream to this page, @@ -338,7 +304,8 @@ public class ReportHTML implements TableReportModule { * @param name Name of the data type * @param comment Comment on the data type, may be the empty string */ - public void startDataType(String name, String comment) { + @Override + public void startDataType(String name, String description) { String title = dataTypeToFileName(name); try { out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(path + title + getExtension()), "UTF-8")); @@ -352,9 +319,9 @@ public class ReportHTML implements TableReportModule { StringBuilder page = new StringBuilder(); page.append("\n\n\t").append(name).append("\n\t\n\n\n\n"); page.append("
").append(name).append("
\n
\n"); - if (!comment.isEmpty()) { + if (!description.isEmpty()) { page.append("

"); - page.append(comment); + page.append(description); page.append("

\n"); } out.write(page.toString()); @@ -477,17 +444,17 @@ public class ReportHTML implements TableReportModule { * @param columnHeaders column headers * @param sourceArtifact source blackboard artifact for the table data */ - public void startTable(List columnHeaders, ARTIFACT_TYPE artifactType) { + public void startContentTagsTable(List columnHeaders) { StringBuilder htmlOutput = new StringBuilder(); htmlOutput.append("\n\n\t\n"); + + // Add the specified columns. for(String columnHeader : columnHeaders) { htmlOutput.append("\t\t\n"); } - // For file tag artifacts, add a column for a hyperlink to a local copy of the tagged file. - if (artifactType.equals(ARTIFACT_TYPE.TSK_TAG_FILE)) { - htmlOutput.append("\t\t\n"); - } + // Add a column for a hyperlink to a local copy of the tagged content. + htmlOutput.append("\t\t\n"); htmlOutput.append("\t\n\n"); @@ -527,20 +494,75 @@ public class ReportHTML implements TableReportModule { try { out.write(builder.toString()); } catch (IOException ex) { - logger.log(Level.SEVERE, "Failed to write row to out."); + logger.log(Level.SEVERE, "Failed to write row to out.", ex); } catch (NullPointerException ex) { - logger.log(Level.SEVERE, "Output writer is null. Page was not initialized before writing."); + logger.log(Level.SEVERE, "Output writer is null. Page was not initialized before writing.", ex); } } /** - * Add a row to the current table. + * Saves a local copy of a tagged file and adds a row with a hyper link to + * the file. * - * @param row values for each cell in the row - * @param sourceArtifact source blackboard artifact for the table data + * @param row Values for each data cell in the row. + * @param contentTag A content tag to use to make the hyper link. */ - public void addRow(List row, BlackboardArtifact sourceArtifact) { - addRowDataForSourceArtifact(row, sourceArtifact); + public void addRowWithTaggedContentHyperlink(List row, ContentTag contentTag) { + // Only handling AbstractFiles at present. + AbstractFile file; + if (contentTag.getContent() instanceof AbstractFile) { + file = (AbstractFile)contentTag.getContent(); + } + else { + return; + } + + // Don't make a local copy of the file if it is a directory or unallocated space. + if (file.isDir() || + file.getType() == TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS || + file.getType() == TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS) { + row.add(""); + return; + } + + // Make a folder for the local file with the same name as the tag. + StringBuilder localFilePath = new StringBuilder(); + localFilePath.append(path); + localFilePath.append(contentTag.getName().getDisplayName()); + File localFileFolder = new File(localFilePath.toString()); + if (!localFileFolder.exists()) { + localFileFolder.mkdirs(); + } + + // Construct a file name for the local file that incorporates the file id to ensure uniqueness. + String fileName = file.getName(); + String objectIdSuffix = "_" + file.getId(); + int lastDotIndex = fileName.lastIndexOf("."); + if (lastDotIndex != -1 && lastDotIndex != 0) { + // The file name has a conventional extension. Insert the object id before the '.' of the extension. + fileName = fileName.substring(0, lastDotIndex) + objectIdSuffix + fileName.substring(lastDotIndex, fileName.length()); + } + else { + // The file has no extension or the only '.' in the file is an initial '.', as in a hidden file. + // Add the object id to the end of the file name. + fileName += objectIdSuffix; + } + localFilePath.append(File.separator); + localFilePath.append(fileName); + + // If the local file doesn't already exist, create it now. + // The existence check is necessary because it is possible to apply multiple tags with the same name to a file. + File localFile = new File(localFilePath.toString()); + if (!localFile.exists()) { + ExtractFscContentVisitor.extract(file, localFile, null, null); + } + + // Add the hyperlink to the row. A column header for it was created in startTable(). + StringBuilder localFileLink = new StringBuilder(); + localFileLink.append("View File"); + row.add(localFileLink.toString()); StringBuilder builder = new StringBuilder(); builder.append("\t\n"); @@ -561,90 +583,6 @@ public class ReportHTML implements TableReportModule { } } - /** - * Add cells particular to a type of artifact associated with the row. Assumes that the overload of startTable() that takes an artifact type was called. - * - * @param row The row. - * @param sourceArtifact The artifact associated with the row. - */ - private void addRowDataForSourceArtifact(List row, BlackboardArtifact sourceArtifact) { -// int artifactTypeID = sourceArtifact.getArtifactTypeID(); -// BlackboardArtifact.ARTIFACT_TYPE type = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifactTypeID); -// switch (type) { -// case TSK_TAG_FILE: -// addRowDataForFileTagArtifact(row, sourceArtifact); -// break; -// default: -// break; -// } - } - - /** - * Saves a local copy of a tagged file and adds a hyper link to the file to the row. - * - * @param row The row. - * @param sourceArtifact The artifact associated with the row. - */ - private void addRowDataForFileTagArtifact(List row, BlackboardArtifact sourceArtifact) { -// try { -// AbstractFile file = Case.getCurrentCase().getSleuthkitCase().getAbstractFileById(sourceArtifact.getObjectID()); -// -// // Don't make a local copy of the file if it is a directory or unallocated space. -// if (file.isDir() || -// file.getType() == TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS || -// file.getType() == TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS) { -// row.add(""); -// return; -// } -// -// // Make a folder for the local file with the same name as the tag. -// StringBuilder localFilePath = new StringBuilder(); -// localFilePath.append(path); -// HashSet tagNames = Tags.getUniqueTagNamesForArtifact(sourceArtifact); -// if (!tagNames.isEmpty()) { -// localFilePath.append(tagNames.iterator().next()); -// } -// File localFileFolder = new File(localFilePath.toString()); -// if (!localFileFolder.exists()) { -// localFileFolder.mkdirs(); -// } -// -// // Construct a file name for the local file that incorporates the corresponding object id to ensure uniqueness. -// String fileName = file.getName(); -// String objectIdSuffix = "_" + sourceArtifact.getObjectID(); -// int lastDotIndex = fileName.lastIndexOf("."); -// if (lastDotIndex != -1 && lastDotIndex != 0) { -// // The file name has a conventional extension. Insert the object id before the '.' of the extension. -// fileName = fileName.substring(0, lastDotIndex) + objectIdSuffix + fileName.substring(lastDotIndex, fileName.length()); -// } -// else { -// // The file has no extension or the only '.' in the file is an initial '.', as in a hidden file. -// // Add the object id to the end of the file name. -// fileName += objectIdSuffix; -// } -// localFilePath.append(File.separator); -// localFilePath.append(fileName); -// -// // If the local file doesn't already exist, create it now. -// // The existence check is necessary because it is possible to apply multiple tags with the same name to a file. -// File localFile = new File(localFilePath.toString()); -// if (!localFile.exists()) { -// ExtractFscContentVisitor.extract(file, localFile, null, null); -// } -// -// // Add the hyperlink to the row. A column header for it was created in startTable(). -// StringBuilder localFileLink = new StringBuilder(); -// localFileLink.append("View File"); -// row.add(localFileLink.toString()); -// } -// catch (TskCoreException ex) { -// logger.log(Level.WARNING, "Failed to get AbstractFile by ID.", ex); -// row.add(""); -// } - } - /** * Return a String date for the long date given. * @param date date as a long @@ -704,11 +642,11 @@ public class ReportHTML implements TableReportModule { "table tr:nth-child(even) td {background: #f3f3f3;}"; cssOut.write(css); } catch (FileNotFoundException ex) { - logger.log(Level.SEVERE, "Could not find index.css file to write to."); + logger.log(Level.SEVERE, "Could not find index.css file to write to.", ex); } catch (UnsupportedEncodingException ex) { - logger.log(Level.SEVERE, "Did not recognize encoding when writing index.css."); + logger.log(Level.SEVERE, "Did not recognize encoding when writing index.css.", ex); } catch (IOException ex) { - logger.log(Level.SEVERE, "Error creating Writer for index.css."); + logger.log(Level.SEVERE, "Error creating Writer for index.css.", ex); } finally { try { if(cssOut != null) { diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java b/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java index 67a91e6521..23d9020de1 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java @@ -71,7 +71,7 @@ public final class ReportWizardAction extends CallableSystemAction implements P ReportGenerator generator = new ReportGenerator((Map)wiz.getProperty("tableModuleStates"), (Map)wiz.getProperty("generalModuleStates"), (Map)wiz.getProperty("fileModuleStates")); - generator.generateArtifactTableReports((Map)wiz.getProperty("artifactStates"), (Map)wiz.getProperty("tagStates")); + generator.generateBlackboardArtifactsReports((Map)wiz.getProperty("artifactStates"), (Map)wiz.getProperty("tagStates")); generator.generateFileListReports((Map)wiz.getProperty("fileReportOptions")); generator.generateGeneralReports(); generator.displayProgressPanels(); diff --git a/Core/src/org/sleuthkit/autopsy/report/TableReportModule.java b/Core/src/org/sleuthkit/autopsy/report/TableReportModule.java index 13397d4cd5..037b6f16e9 100644 --- a/Core/src/org/sleuthkit/autopsy/report/TableReportModule.java +++ b/Core/src/org/sleuthkit/autopsy/report/TableReportModule.java @@ -51,8 +51,9 @@ public interface TableReportModule extends ReportModule { * It is up to the report how the differentiation is shown. * * @param title String name of the data type + * @param description RJCTODO: fix this header comment */ - public void startDataType(String title); + public void startDataType(String title, String description); /** * End the current data type and prepare for either the end of the report
").append(columnHeader).append("