diff --git a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties index 358971eaa8..6665c2e958 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties @@ -38,3 +38,4 @@ VisualizationPanel.organicLayoutButton.text=Organic VisualizationPanel.fastOrganicLayoutButton.text=Fast Organic VisualizationPanel.hierarchyLayoutButton.text=Hierarchical VisualizationPanel.clearVizButton.text_1=Clear Viz. +VisualizationPanel.snapshotButton.text_1=Snapshot Report diff --git a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties-MERGED index 381ec7b337..e4daded09b 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties-MERGED @@ -32,6 +32,23 @@ ResetAndPinAccountsAction.singularText=Visualize Only Selected Account UnpinAccountsAction.pluralText=Remove Selected Accounts UnpinAccountsAction.singularText=Remove Selected Account VisalizationPanel.paintingError=Problem painting visualization. +# {0} - default name +VisualizationPane_accept_defaultName=Report name was empty. Press OK to accept default report name: {0} +VisualizationPane_blank_report_title=Blank Report Name +VisualizationPane_DisplayName=Open Report +VisualizationPane_fileName_prompt=Enter name for the Communications Snapshot Report: +VisualizationPane_MessageBoxTitle=Open Report Failure +VisualizationPane_MissingReportFileMessage=The report file no longer exists. +VisualizationPane_NoAssociatedEditorMessage=There is no associated editor for reports of this type or the associated application failed to launch. +VisualizationPane_NoOpenInEditorSupportMessage=This platform (operating system) does not support opening a file in an editor this way. +VisualizationPane_Open_Report=Open Report +# {0} - report name +VisualizationPane_overrite_exiting=Overwrite existing report?\n{0} +VisualizationPane_Report_OK_Button=OK +# {0} - report path +VisualizationPane_Report_Success=Report Successfully create at:\n{0} +VisualizationPane_ReportFileOpenPermissionDeniedMessage=Permission to open the report file was denied. +VisualizationPane_reportName=Communications Snapshot VisualizationPanel.cancelButton.text=Cancel VisualizationPanel.computingLayout=Computing Layout VisualizationPanel.jButton1.text=Fast Organic @@ -65,3 +82,8 @@ VisualizationPanel.organicLayoutButton.text=Organic VisualizationPanel.fastOrganicLayoutButton.text=Fast Organic VisualizationPanel.hierarchyLayoutButton.text=Hierarchical VisualizationPanel.clearVizButton.text_1=Clear Viz. +VisualizationPanel.snapshotButton.text_1=Snapshot Report +VisualizationPanel_action_dialogs_title=Communications +VisualizationPanel_action_name_text=Snapshot Report +VisualizationPanel_module_name=Communications +VisualizationPanel_snapshot_report_failure=Snapshot report not created. An error occurred during creation. diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form index 49b86ae014..4e0a73a6c7 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form @@ -11,7 +11,7 @@ - + @@ -49,9 +49,9 @@ - + - + @@ -120,6 +120,10 @@ + + + + @@ -143,6 +147,8 @@ + + @@ -310,6 +316,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java index 8f5e6e5c94..f3d4f95bb2 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java @@ -28,6 +28,7 @@ import com.mxgraph.model.mxCell; import com.mxgraph.model.mxICell; import com.mxgraph.swing.handler.mxRubberband; import com.mxgraph.swing.mxGraphComponent; +import com.mxgraph.util.mxCellRenderer; import com.mxgraph.util.mxEvent; import com.mxgraph.util.mxEventObject; import com.mxgraph.util.mxEventSource; @@ -41,19 +42,28 @@ import com.mxgraph.view.mxGraphView; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Cursor; +import java.awt.Desktop; import java.awt.Dimension; import java.awt.Font; import java.awt.Frame; import java.awt.Graphics; +import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseWheelEvent; +import java.awt.image.BufferedImage; import java.beans.PropertyChangeEvent; import java.beans.PropertyVetoException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.text.DecimalFormat; +import java.text.SimpleDateFormat; import java.util.Arrays; +import java.util.Date; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; @@ -75,14 +85,17 @@ import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JMenuItem; +import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JSplitPane; import javax.swing.JTextArea; +import javax.swing.JTextField; import javax.swing.JToolBar; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.SwingWorker; +import org.apache.commons.lang3.StringUtils; import org.controlsfx.control.Notifications; import org.jdesktop.layout.GroupLayout; import org.jdesktop.layout.LayoutStyle; @@ -92,8 +105,11 @@ import org.openide.nodes.Node; import org.openide.util.Lookup; import org.openide.util.NbBundle; import org.openide.util.lookup.ProxyLookup; +import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.communications.snapshot.CommSnapShotReportWriter; +import org.sleuthkit.autopsy.coreutils.FileUtil; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.progress.ModalDialogProgressIndicator; @@ -101,7 +117,6 @@ import org.sleuthkit.datamodel.CommunicationsFilter; import org.sleuthkit.datamodel.CommunicationsManager; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TskCoreException; - /** * A panel that goes in the Visualize tab of the Communications Visualization * Tool. Hosts an JGraphX mxGraphComponent that implements the communications @@ -172,7 +187,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider public void paint(Graphics graphics) { try { super.paint(graphics); - } catch (NullPointerException ex) { //NOPMD + } catch (NullPointerException ex) { //NOPMD /* We can't find the underlying cause of the NPE in * jgraphx, but it doesn't seem to cause any * noticeable problems, so we are just logging it @@ -387,6 +402,8 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider zoomLabel = new JLabel(); clearVizButton = new JButton(); jSeparator2 = new JToolBar.Separator(); + snapshotButton = new JButton(); + jSeparator3 = new JToolBar.Separator(); notificationsJFXPanel = new JFXPanel(); setLayout(new BorderLayout()); @@ -406,9 +423,9 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider placeHolderPanel.setLayout(placeHolderPanelLayout); placeHolderPanelLayout.setHorizontalGroup(placeHolderPanelLayout.createParallelGroup(GroupLayout.LEADING) .add(placeHolderPanelLayout.createSequentialGroup() - .addContainerGap(71, Short.MAX_VALUE) + .addContainerGap(268, Short.MAX_VALUE) .add(jTextArea1, GroupLayout.PREFERRED_SIZE, 424, GroupLayout.PREFERRED_SIZE) - .addContainerGap(248, Short.MAX_VALUE)) + .addContainerGap(445, Short.MAX_VALUE)) ); placeHolderPanelLayout.setVerticalGroup(placeHolderPanelLayout.createParallelGroup(GroupLayout.LEADING) .add(placeHolderPanelLayout.createSequentialGroup() @@ -505,6 +522,16 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider jSeparator2.setOrientation(SwingConstants.VERTICAL); + snapshotButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/report/images/image.png"))); // NOI18N + snapshotButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.snapshotButton.text_1")); // NOI18N + snapshotButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + snapshotButtonActionPerformed(evt); + } + }); + + jSeparator3.setOrientation(SwingConstants.VERTICAL); + GroupLayout toolbarLayout = new GroupLayout(toolbar); toolbar.setLayout(toolbarLayout); toolbarLayout.setHorizontalGroup(toolbarLayout.createParallelGroup(GroupLayout.LEADING) @@ -537,6 +564,10 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider .add(zoomActualButton, GroupLayout.PREFERRED_SIZE, 33, GroupLayout.PREFERRED_SIZE) .addPreferredGap(LayoutStyle.RELATED) .add(fitZoomButton, GroupLayout.PREFERRED_SIZE, 32, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.RELATED) + .add(jSeparator3, GroupLayout.PREFERRED_SIZE, 10, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.RELATED) + .add(snapshotButton) .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); toolbarLayout.setVerticalGroup(toolbarLayout.createParallelGroup(GroupLayout.LEADING) @@ -556,7 +587,9 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider .add(jLabel2) .add(zoomLabel) .add(clearVizButton) - .add(jSeparator2, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .add(jSeparator2, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .add(snapshotButton) + .add(jSeparator3, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) .add(3, 3, 3)) ); @@ -648,6 +681,24 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider setCursor(Cursor.getDefaultCursor()); }//GEN-LAST:event_clearVizButtonActionPerformed + @NbBundle.Messages({ + "VisualizationPanel_snapshot_report_failure=Snapshot report not created. An error occurred during creation." + }) + private void snapshotButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_snapshotButtonActionPerformed + try { + handleSnapshotEvent(); + } catch (NoCurrentCaseException | IOException ex) { + logger.log(Level.SEVERE, "Unable to create communications snapsot report", ex); //NON-NLS + + Platform.runLater(() + -> Notifications.create().owner(notificationsJFXPanel.getScene().getWindow()) + .text(Bundle.VisualizationPanel_snapshot_report_failure()) + .showWarning()); + } catch( TskCoreException ex) { + logger.log(Level.WARNING, "Unable to add report to currenct case", ex); //NON-NLS + } + }//GEN-LAST:event_snapshotButtonActionPerformed + private void fitGraph() { graphComponent.zoomTo(1, true); mxPoint translate = graph.getView().getTranslate(); @@ -674,7 +725,129 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider graphComponent.zoom((heightFactor + widthFactor) / 2.0); } + + /** + * Handle the ActionPerformed event from the Snapshot button. + * + * @throws NoCurrentCaseException + * @throws IOException + */ + @NbBundle.Messages({ + "VisualizationPanel_action_dialogs_title=Communications", + "VisualizationPanel_module_name=Communications", + "VisualizationPanel_action_name_text=Snapshot Report", + "VisualizationPane_fileName_prompt=Enter name for the Communications Snapshot Report:", + "VisualizationPane_reportName=Communications Snapshot", + "# {0} - default name", + "VisualizationPane_accept_defaultName=Report name was empty. Press OK to accept default report name: {0}", + "VisualizationPane_blank_report_title=Blank Report Name", + "# {0} - report name", + "VisualizationPane_overrite_exiting=Overwrite existing report?\n{0}" + }) + private void handleSnapshotEvent() throws NoCurrentCaseException, IOException, TskCoreException { + Case currentCase = Case.getCurrentCaseThrows(); + Date generationDate = new Date(); + final String defaultReportName = FileUtil.escapeFileName(currentCase.getDisplayName() + " " + new SimpleDateFormat("MMddyyyyHHmmss").format(generationDate)); //NON_NLS + + final JTextField text = new JTextField(50); + final JPanel panel = new JPanel(new GridLayout(2, 1)); + panel.add(new JLabel(Bundle.VisualizationPane_fileName_prompt())); + panel.add(text); + + text.setText(defaultReportName); + + int result = JOptionPane.showConfirmDialog(graphComponent, panel, + Bundle.VisualizationPanel_action_dialogs_title(), JOptionPane.OK_CANCEL_OPTION); + + if (result == JOptionPane.OK_OPTION) { + String enteredReportName = text.getText(); + + if(enteredReportName.trim().isEmpty()){ + result = JOptionPane.showConfirmDialog(graphComponent, Bundle.VisualizationPane_accept_defaultName(defaultReportName), Bundle.VisualizationPane_blank_report_title(), JOptionPane.OK_CANCEL_OPTION); + if(result != JOptionPane.OK_OPTION) { + return; + } + } + + String reportName = StringUtils.defaultIfBlank(enteredReportName, defaultReportName); + Path reportPath = Paths.get(currentCase.getReportDirectory(), reportName); + if (Files.exists(reportPath)) { + result = JOptionPane.showConfirmDialog(graphComponent, Bundle.VisualizationPane_overrite_exiting(reportName), + Bundle.VisualizationPanel_action_dialogs_title(), JOptionPane.OK_CANCEL_OPTION); + + if (result == JOptionPane.OK_OPTION) { + FileUtil.deleteFileDir(reportPath.toFile()); + createReport(currentCase, reportName); + } + } else { + createReport(currentCase, reportName); + currentCase.addReport(reportPath.toString(), Bundle.VisualizationPanel_module_name(), reportName); + + } + } + } + + /** + * Create the Snapshot Report. + * + * @param currentCase The current case + * @param reportName User selected name for the report + * + * @throws IOException + */ + @NbBundle.Messages({ + "VisualizationPane_DisplayName=Open Report", + "VisualizationPane_NoAssociatedEditorMessage=There is no associated editor for reports of this type or the associated application failed to launch.", + "VisualizationPane_MessageBoxTitle=Open Report Failure", + "VisualizationPane_NoOpenInEditorSupportMessage=This platform (operating system) does not support opening a file in an editor this way.", + "VisualizationPane_MissingReportFileMessage=The report file no longer exists.", + "VisualizationPane_ReportFileOpenPermissionDeniedMessage=Permission to open the report file was denied.", + "# {0} - report path", + "VisualizationPane_Report_Success=Report Successfully create at:\n{0}", + "VisualizationPane_Report_OK_Button=OK", + "VisualizationPane_Open_Report=Open Report",}) + private void createReport(Case currentCase, String reportName) throws IOException { + + // Create the report. + Path reportFolderPath = Paths.get(currentCase.getReportDirectory(), reportName, Bundle.VisualizationPane_reportName()); //NON_NLS + BufferedImage image = mxCellRenderer.createBufferedImage(graph, null, graph.getView().getScale(), Color.WHITE, true, null); + Path reportPath = new CommSnapShotReportWriter(currentCase, reportFolderPath, reportName, new Date(), image, currentFilter).writeReport(); + + // Report success to the user and offer to open the report. + String message = Bundle.VisualizationPane_Report_Success(reportPath.toAbsolutePath()); + String[] buttons = {Bundle.VisualizationPane_Open_Report(), Bundle.VisualizationPane_Report_OK_Button()}; + + int result = JOptionPane.showOptionDialog(graphComponent, message, + Bundle.VisualizationPanel_action_dialogs_title(), + JOptionPane.OK_CANCEL_OPTION, JOptionPane.INFORMATION_MESSAGE, + null, buttons, buttons[1]); + if (result == JOptionPane.YES_NO_OPTION) { + try { + Desktop.getDesktop().open(reportPath.toFile()); + } catch (IOException ex) { + JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), + Bundle.VisualizationPane_NoAssociatedEditorMessage(), + Bundle.VisualizationPane_MessageBoxTitle(), + JOptionPane.ERROR_MESSAGE); + } catch (UnsupportedOperationException ex) { + JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), + Bundle.VisualizationPane_NoOpenInEditorSupportMessage(), + Bundle.VisualizationPane_MessageBoxTitle(), + JOptionPane.ERROR_MESSAGE); + } catch (IllegalArgumentException ex) { + JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), + Bundle.VisualizationPane_MissingReportFileMessage(), + Bundle.VisualizationPane_MessageBoxTitle(), + JOptionPane.ERROR_MESSAGE); + } catch (SecurityException ex) { + JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), + Bundle.VisualizationPane_ReportFileOpenPermissionDeniedMessage(), + Bundle.VisualizationPane_MessageBoxTitle(), + JOptionPane.ERROR_MESSAGE); + } + } + } // Variables declaration - do not modify//GEN-BEGIN:variables private JPanel borderLayoutPanel; @@ -687,10 +860,12 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider private JLabel jLabel2; private JToolBar.Separator jSeparator1; private JToolBar.Separator jSeparator2; + private JToolBar.Separator jSeparator3; private JTextArea jTextArea1; private JFXPanel notificationsJFXPanel; private JButton organicLayoutButton; private JPanel placeHolderPanel; + private JButton snapshotButton; private JSplitPane splitPane; private JPanel toolbar; private JButton zoomActualButton; @@ -1001,4 +1176,4 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider lockedVertexModel.lock(selectedVertices); } } -} +} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/communications/snapshot/CommSnapShotReportWriter.java b/Core/src/org/sleuthkit/autopsy/communications/snapshot/CommSnapShotReportWriter.java new file mode 100755 index 0000000000..7b4dee6d19 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/snapshot/CommSnapShotReportWriter.java @@ -0,0 +1,175 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.communications.snapshot; + +import java.util.List; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.nio.file.Path; +import java.text.SimpleDateFormat; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.Set; +import javax.imageio.ImageIO; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.report.uisnapshot.UiSnapShotReportWriter; +import org.sleuthkit.datamodel.Account; +import org.sleuthkit.datamodel.CommunicationsFilter; +import org.sleuthkit.datamodel.CommunicationsFilter.AccountTypeFilter; +import org.sleuthkit.datamodel.CommunicationsFilter.DateRangeFilter; +import org.sleuthkit.datamodel.CommunicationsFilter.DeviceFilter; +import org.sleuthkit.datamodel.CommunicationsFilter.SubFilter; +import org.sleuthkit.datamodel.DataSource; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Generate and write the Communication snapshot report to disk. + */ +public class CommSnapShotReportWriter extends UiSnapShotReportWriter { + + private final BufferedImage image; + private final CommunicationsFilter filter; + + /** + * Constructor + * + * @param currentCase The Case to write a report for. + * @param reportFolderPath The Path to the folder that will contain the + * report. + * @param reportName The name of the report. + * @param generationDate The generation Date of the report. + * @param snapshot A snapshot of the view to include in the report. + */ + public CommSnapShotReportWriter(Case currentCase, Path reportFolderPath, String reportName, Date generationDate, BufferedImage snapshot, CommunicationsFilter filter) { + + super(currentCase, reportFolderPath, reportName, generationDate); + + this.image = snapshot; + this.filter = filter; + + } + + /** + * Generate and write the html page that shows the snapshot and the state of + * the CommunicationFilters + * + * @throws IOException If there is a problem writing the html file to disk. + */ + @Override + protected void writeSnapShotHTMLFile() throws IOException { + SimpleDateFormat formatter = new SimpleDateFormat("MMMMM dd, yyyy"); //NON-NLS + + ImageIO.write(image, "png", getReportFolderPath().resolve("snapshot.png").toFile()); //NON-NLS + + //make a map of context objects to resolve template paramaters against + HashMap snapShotContext = new HashMap<>(); + snapShotContext.put("reportTitle", getReportName()); //NON-NLS + + List filters = filter.getAndFilters(); + + for (SubFilter filter : filters) { + if (filter instanceof DateRangeFilter) { + long startDate = ((DateRangeFilter) filter).getStartDate(); + long endDate = ((DateRangeFilter) filter).getEndDate(); + + if (startDate > 0) { + + snapShotContext.put("startTime", formatter.format(new Date((Instant.ofEpochSecond(startDate)).toEpochMilli()))); //NON-NLS + } + + if (endDate > 0) { + snapShotContext.put("endTime", formatter.format(new Date((Instant.ofEpochSecond(endDate)).toEpochMilli()))); //NON-NLS + } + } else if (filter instanceof AccountTypeFilter) { + + Set selectedAccounts = ((AccountTypeFilter) filter).getAccountTypes(); + ArrayList fullAccountList = new ArrayList<>(); + for (Account.Type type : Account.Type.PREDEFINED_ACCOUNT_TYPES) { + if (type == Account.Type.CREDIT_CARD) { + continue; + } + + fullAccountList.add(new ReportWriterHelper(type.getDisplayName(), selectedAccounts.contains(type))); + } + + snapShotContext.put("accounts", fullAccountList); + } else if (filter instanceof DeviceFilter) { + Collection ids = ((DeviceFilter) filter).getDevices(); + ArrayList list = new ArrayList<>(); + try { + final SleuthkitCase sleuthkitCase = getCurrentCase().getSleuthkitCase(); + for (DataSource dataSource : sleuthkitCase.getDataSources()) { + boolean selected = ids.contains(dataSource.getDeviceId()); + String dsName = sleuthkitCase.getContentById(dataSource.getId()).getName(); + list.add(new ReportWriterHelper(dsName, selected)); + } + } catch (TskCoreException ex) { + + } + + snapShotContext.put("devices", list); + } + } + + fillTemplateAndWrite("/org/sleuthkit/autopsy/communications/snapshot/comm_snapshot_template.html", "Snapshot", snapShotContext, getReportFolderPath().resolve("snapshot.html")); //NON-NLS + } + + /** + * Helper class for use with the html template + */ + private final class ReportWriterHelper { + + private final String label; + private final boolean selected; + + /** + * Helper class for use with the html template. + * + * @param label Display label + * @param selected Boolean selected state + */ + ReportWriterHelper(String label, boolean selected) { + this.label = label; + this.selected = selected; + } + + /** + * Returns the display label + * + * @return The display label + */ + public String getLabel(){ + return label; + } + + /** + * Returns the selection state + * + * @return The selection state + */ + public boolean isSelected(){ + return selected; + } + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/snapshot/comm_snapshot_template.html b/Core/src/org/sleuthkit/autopsy/communications/snapshot/comm_snapshot_template.html new file mode 100755 index 0000000000..078f6fc978 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/snapshot/comm_snapshot_template.html @@ -0,0 +1,21 @@ + + + Communications Snapshot: {{reportTitle}} + + + + +
+ Snapshot + + + + + + {{#devices}}{{/devices}} + + {{#accounts}}{{/accounts}} +
Date Range
Start:{{startTime}}
End:{{endTime}}
Devices:
{{label}}
Account Types
{{label}}
+
+ + \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/report/images/image.png b/Core/src/org/sleuthkit/autopsy/report/images/image.png new file mode 100755 index 0000000000..fc3c393caa Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/report/images/image.png differ diff --git a/Core/src/org/sleuthkit/autopsy/report/uisnapshot/UiSnapShotReportWriter.java b/Core/src/org/sleuthkit/autopsy/report/uisnapshot/UiSnapShotReportWriter.java new file mode 100755 index 0000000000..1017e2e743 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/report/uisnapshot/UiSnapShotReportWriter.java @@ -0,0 +1,241 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2016 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.report.uisnapshot; + +import com.github.mustachejava.DefaultMustacheFactory; +import com.github.mustachejava.Mustache; +import com.github.mustachejava.MustacheFactory; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Writer; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import org.apache.commons.lang3.StringUtils; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.autopsy.report.ReportBranding; + +/** + * Generate and write the snapshot report to disk. + */ +public abstract class UiSnapShotReportWriter { + + /** + * mustache.java template factory. + */ + private final static MustacheFactory mf = new DefaultMustacheFactory(); + + private final Case currentCase; + private final Path reportFolderPath; + private final String reportName; + private final ReportBranding reportBranding; + + private Date generationDate; + + /** + * Constructor + * + * @param currentCase The Case to write a report for. + * @param reportFolderPath The Path to the folder that will contain the + * report. + * @param reportName The name of the report. + * @param generationDate The generation Date of the report. + * @param snapshot A snapshot of the view to include in the + * report. + */ + protected UiSnapShotReportWriter(Case currentCase, Path reportFolderPath, String reportName, Date generationDate) { + this.currentCase = currentCase; + this.reportFolderPath = reportFolderPath; + this.reportName = reportName; + this.generationDate = generationDate; + + this.reportBranding = new ReportBranding(); + } + + /** + * Generate and write the report to disk. + * + * @return The Path to the "main file" of the report. This is the file that + * Autopsy shows in the results view when the Reports Node is + * selected in the DirectoryTree. + * + * @throws IOException If there is a problem writing the report. + */ + public Path writeReport() throws IOException { + //ensure directory exists + Files.createDirectories(reportFolderPath); + + copyResources(); + + writeSummaryHTML(); + writeSnapShotHTMLFile(); + return writeIndexHTML(); + } + + /** + * Get the name for the report. + * + * @return Returns the reportName + */ + protected String getReportName() { + return reportName; + } + + /** + * Get the folder path for the report. + * + * @return Report folder path + */ + protected Path getReportFolderPath() { + return reportFolderPath; + } + + /** + * Get the case for this report. + * + * @return Current case object + */ + protected Case getCurrentCase() { + return currentCase; + } + + /** + * Generate and write the html page that shows the snapshot and the state of + * any filters. + * + * @throws IOException If there is a problem writing the html file to disk. + */ + protected abstract void writeSnapShotHTMLFile() throws IOException ; + + /** + * Generate and write the main html page with frames for navigation on the + * left and content on the right. + * + * @return The Path of the written html file. + * + * @throws IOException If there is a problem writing the html file to disk. + */ + private Path writeIndexHTML() throws IOException { + //make a map of context objects to resolve template paramaters against + HashMap indexContext = new HashMap<>(); + indexContext.put("reportBranding", reportBranding); //NON-NLS + indexContext.put("reportName", reportName); //NON-NLS + Path reportIndexFile = reportFolderPath.resolve("index.html"); //NON-NLS + + fillTemplateAndWrite("/org/sleuthkit/autopsy/report/uisnapshot/index_template.html", "Index", indexContext, reportIndexFile); //NON-NLS + return reportIndexFile; + } + + /** + * * Generate and write the summary of the current case for this report. + * + * @throws IOException If there is a problem writing the html file to disk. + */ + private void writeSummaryHTML() throws IOException { + //make a map of context objects to resolve template paramaters against + HashMap summaryContext = new HashMap<>(); + summaryContext.put("reportName", reportName); //NON-NLS + summaryContext.put("reportBranding", reportBranding); //NON-NLS + summaryContext.put("generationDateTime", new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(generationDate)); //NON-NLS + summaryContext.put("ingestRunning", IngestManager.getInstance().isIngestRunning()); //NON-NLS + summaryContext.put("currentCase", currentCase); //NON-NLS + String agencyLogo = "agency_logo.png"; //default name for agency logo. + if (StringUtils.isNotBlank(reportBranding.getAgencyLogoPath())){ + agencyLogo = Paths.get(reportBranding.getAgencyLogoPath()).getFileName().toString(); + } + summaryContext.put("agencyLogoFileName", agencyLogo); + fillTemplateAndWrite("/org/sleuthkit/autopsy/report/uisnapshot/summary_template.html", "Summary", summaryContext, reportFolderPath.resolve("summary.html")); //NON-NLS + } + + /** + * Fill in the mustache template at the given location using the values from + * the given context object and save it to the given outPutFile. + * + * @param templateLocation The location of the template. suitible for use + * with Class.getResourceAsStream + * @param templateName The name of the tempalte. (Used by mustache to + * cache templates?) + * @param context The contect to use to fill in the template + * values. + * @param outPutFile The filled in tempalte will be saced at this + * Path. + * + * @throws IOException If there is a problem saving the filled in template + * to disk. + */ + protected void fillTemplateAndWrite(final String templateLocation, final String templateName, Object context, final Path outPutFile) throws IOException { + + Mustache summaryMustache = mf.compile(new InputStreamReader(UiSnapShotReportWriter.class.getResourceAsStream(templateLocation)), templateName); + try (Writer writer = Files.newBufferedWriter(outPutFile, Charset.forName("UTF-8"))) { //NON-NLS + summaryMustache.execute(writer, context); + } + } + + /** + * Copy static resources (static html, css, images, etc) to the reports + * folder. + * + * @throws IOException If there is a problem copying the resources. + */ + private void copyResources() throws IOException { + + //pull generator and agency logos from branding + String generatorLogoPath = reportBranding.getGeneratorLogoPath(); + if (StringUtils.isNotBlank(generatorLogoPath)) { + Files.copy(Files.newInputStream(Paths.get(generatorLogoPath)), reportFolderPath.resolve("generator_logo.png")); //NON-NLS + } + String agencyLogoPath = reportBranding.getAgencyLogoPath(); + if (StringUtils.isNotBlank(agencyLogoPath)) { + Files.copy(Files.newInputStream(Paths.get(agencyLogoPath)), reportFolderPath.resolve(Paths.get(reportBranding.getAgencyLogoPath()).getFileName())); //NON-NLS + } + + //copy favicon + if (StringUtils.isBlank(agencyLogoPath)) { + copyInternalResource("/org/sleuthkit/autopsy/report/images/favicon.ico", "favicon.ico"); + } else { + Files.copy(Files.newInputStream(Paths.get(agencyLogoPath)), reportFolderPath.resolve("favicon.ico")); //NON-NLS + } + + copyInternalResource("/org/sleuthkit/autopsy/report/uisnapshot/navigation.html", "nav.html"); + copyInternalResource("/org/sleuthkit/autopsy/report/images/summary.png", "summary.png"); + copyInternalResource("/org/sleuthkit/autopsy/report/images/image.png", "snapshot_icon.png"); + copyInternalResource("/org/sleuthkit/autopsy/report/uisnapshot/index.css", "index.css"); + copyInternalResource("/org/sleuthkit/autopsy/report/uisnapshot/summary.css", "summary.css"); + } + + /** + * Copies internal resource to the report folder. + * + * @param internalPath Location in jar of the image + * @param fileName Name to give resource in new location + * + * @throws IOException + */ + private void copyInternalResource(String internalPath, String fileName) throws IOException{ + try (InputStream resource = UiSnapShotReportWriter.class.getResourceAsStream(internalPath)) { //NON-NLS + Files.copy(resource, reportFolderPath.resolve(fileName)); //NON-NLS + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/snapshot/index.css b/Core/src/org/sleuthkit/autopsy/report/uisnapshot/index.css old mode 100644 new mode 100755 similarity index 100% rename from Core/src/org/sleuthkit/autopsy/timeline/snapshot/index.css rename to Core/src/org/sleuthkit/autopsy/report/uisnapshot/index.css diff --git a/Core/src/org/sleuthkit/autopsy/timeline/snapshot/index_template.html b/Core/src/org/sleuthkit/autopsy/report/uisnapshot/index_template.html old mode 100644 new mode 100755 similarity index 100% rename from Core/src/org/sleuthkit/autopsy/timeline/snapshot/index_template.html rename to Core/src/org/sleuthkit/autopsy/report/uisnapshot/index_template.html diff --git a/Core/src/org/sleuthkit/autopsy/timeline/snapshot/navigation.html b/Core/src/org/sleuthkit/autopsy/report/uisnapshot/navigation.html old mode 100644 new mode 100755 similarity index 85% rename from Core/src/org/sleuthkit/autopsy/timeline/snapshot/navigation.html rename to Core/src/org/sleuthkit/autopsy/report/uisnapshot/navigation.html index 02b6705cfb..bfcf061e34 --- a/Core/src/org/sleuthkit/autopsy/timeline/snapshot/navigation.html +++ b/Core/src/org/sleuthkit/autopsy/report/uisnapshot/navigation.html @@ -9,7 +9,7 @@

Report Navigation

diff --git a/Core/src/org/sleuthkit/autopsy/timeline/snapshot/summary.css b/Core/src/org/sleuthkit/autopsy/report/uisnapshot/summary.css old mode 100644 new mode 100755 similarity index 100% rename from Core/src/org/sleuthkit/autopsy/timeline/snapshot/summary.css rename to Core/src/org/sleuthkit/autopsy/report/uisnapshot/summary.css diff --git a/Core/src/org/sleuthkit/autopsy/timeline/snapshot/summary_template.html b/Core/src/org/sleuthkit/autopsy/report/uisnapshot/summary_template.html old mode 100644 new mode 100755 similarity index 96% rename from Core/src/org/sleuthkit/autopsy/timeline/snapshot/summary_template.html rename to Core/src/org/sleuthkit/autopsy/report/uisnapshot/summary_template.html index 2d1e37a2a8..50aa65ff8a --- a/Core/src/org/sleuthkit/autopsy/timeline/snapshot/summary_template.html +++ b/Core/src/org/sleuthkit/autopsy/report/uisnapshot/summary_template.html @@ -9,7 +9,7 @@

{{reportBranding.getReportTitle}}: {{reportName}}{{#ingestRunning}}Warning, this report was run before ingest services completed!{{/ingestRunning}}

-

Timeline Report generated on {{generationDateTime}}

+

Report generated on {{generationDateTime}}

{{#reportBranding.getAgencyLogoPath}}
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/snapshot/SnapShotReportWriter.java b/Core/src/org/sleuthkit/autopsy/timeline/snapshot/SnapShotReportWriter.java index a9bce0acb8..961f357daa 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/snapshot/SnapShotReportWriter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/snapshot/SnapShotReportWriter.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2016 Basis Technology Corp. + * Copyright 2016 - 2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,46 +18,23 @@ */ package org.sleuthkit.autopsy.timeline.snapshot; -import com.github.mustachejava.DefaultMustacheFactory; -import com.github.mustachejava.Mustache; -import com.github.mustachejava.MustacheFactory; import java.awt.image.BufferedImage; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Writer; -import java.nio.charset.Charset; -import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; -import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import javax.imageio.ImageIO; -import org.apache.commons.lang3.StringUtils; import org.joda.time.format.DateTimeFormat; import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.ingest.IngestManager; -import org.sleuthkit.autopsy.report.ReportBranding; +import org.sleuthkit.autopsy.report.uisnapshot.UiSnapShotReportWriter; import org.sleuthkit.autopsy.timeline.zooming.ZoomParams; /** * Generate and write the Timeline snapshot report to disk. */ -public class SnapShotReportWriter { - - /** - * mustache.java template factory. - */ - private final static MustacheFactory mf = new DefaultMustacheFactory(); - - private final Case currentCase; - private final Path reportFolderPath; - private final String reportName; - private final ReportBranding reportBranding; +public class SnapShotReportWriter extends UiSnapShotReportWriter{ private final ZoomParams zoomParams; - private final Date generationDate; private final BufferedImage image; /** @@ -74,37 +51,9 @@ public class SnapShotReportWriter { * report. */ public SnapShotReportWriter(Case currentCase, Path reportFolderPath, String reportName, ZoomParams zoomParams, Date generationDate, BufferedImage snapshot) { - this.currentCase = currentCase; - this.reportFolderPath = reportFolderPath; - this.reportName = reportName; + super(currentCase, reportFolderPath, reportName, generationDate); this.zoomParams = zoomParams; - this.generationDate = generationDate; this.image = snapshot; - - this.reportBranding = new ReportBranding(); - } - - /** - * Generate and write the report to disk. - * - * @return The Path to the "main file" of the report. This is the file that - * Autopsy shows in the results view when the Reports Node is - * selected in the DirectoryTree. - * - * @throws IOException If there is a problem writing the report. - */ - public Path writeReport() throws IOException { - //ensure directory exists - Files.createDirectories(reportFolderPath); - - //save the snapshot in the report directory - ImageIO.write(image, "png", reportFolderPath.resolve("snapshot.png").toFile()); //NON-NLS - - copyResources(); - - writeSummaryHTML(); - writeSnapShotHTMLFile(); - return writeIndexHTML(); } /** @@ -113,128 +62,18 @@ public class SnapShotReportWriter { * * @throws IOException If there is a problem writing the html file to disk. */ - private void writeSnapShotHTMLFile() throws IOException { + @Override + protected void writeSnapShotHTMLFile() throws IOException { + //save the snapshot in the report directory + ImageIO.write(image, "png", getReportFolderPath().resolve("snapshot.png").toFile()); //NON-NLS + //make a map of context objects to resolve template paramaters against HashMap snapShotContext = new HashMap<>(); - snapShotContext.put("reportTitle", reportName); //NON-NLS + snapShotContext.put("reportTitle", getReportName()); //NON-NLS snapShotContext.put("startTime", zoomParams.getTimeRange().getStart().toString(DateTimeFormat.fullDateTime())); //NON-NLS snapShotContext.put("endTime", zoomParams.getTimeRange().getEnd().toString(DateTimeFormat.fullDateTime())); //NON-NLS snapShotContext.put("zoomParams", zoomParams); //NON-NLS - fillTemplateAndWrite("/org/sleuthkit/autopsy/timeline/snapshot/snapshot_template.html", "Snapshot", snapShotContext, reportFolderPath.resolve("snapshot.html")); //NON-NLS - } - - /** - * Generate and write the main html page with frames for navigation on the - * left and content on the right. - * - * @return The Path of the written html file. - * - * @throws IOException If there is a problem writing the html file to disk. - */ - private Path writeIndexHTML() throws IOException { - //make a map of context objects to resolve template paramaters against - HashMap indexContext = new HashMap<>(); - indexContext.put("reportBranding", reportBranding); //NON-NLS - indexContext.put("reportName", reportName); //NON-NLS - Path reportIndexFile = reportFolderPath.resolve("index.html"); //NON-NLS - - fillTemplateAndWrite("/org/sleuthkit/autopsy/timeline/snapshot/index_template.html", "Index", indexContext, reportIndexFile); //NON-NLS - return reportIndexFile; - } - - /** - * * Generate and write the summary of the current case for this report. - * - * @throws IOException If there is a problem writing the html file to disk. - */ - private void writeSummaryHTML() throws IOException { - //make a map of context objects to resolve template paramaters against - HashMap summaryContext = new HashMap<>(); - summaryContext.put("reportName", reportName); //NON-NLS - summaryContext.put("reportBranding", reportBranding); //NON-NLS - summaryContext.put("generationDateTime", new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(generationDate)); //NON-NLS - summaryContext.put("ingestRunning", IngestManager.getInstance().isIngestRunning()); //NON-NLS - summaryContext.put("currentCase", currentCase); //NON-NLS - String agencyLogo = "agency_logo.png"; //default name for agency logo. - if (StringUtils.isNotBlank(reportBranding.getAgencyLogoPath())){ - agencyLogo = Paths.get(reportBranding.getAgencyLogoPath()).getFileName().toString(); - } - summaryContext.put("agencyLogoFileName", agencyLogo); - fillTemplateAndWrite("/org/sleuthkit/autopsy/timeline/snapshot/summary_template.html", "Summary", summaryContext, reportFolderPath.resolve("summary.html")); //NON-NLS - } - - /** - * Fill in the mustache template at the given location using the values from - * the given context object and save it to the given outPutFile. - * - * @param templateLocation The location of the template. suitible for use - * with Class.getResourceAsStream - * @param templateName The name of the tempalte. (Used by mustache to - * cache templates?) - * @param context The contect to use to fill in the template - * values. - * @param outPutFile The filled in tempalte will be saced at this - * Path. - * - * @throws IOException If there is a problem saving the filled in template - * to disk. - */ - private void fillTemplateAndWrite(final String templateLocation, final String templateName, Object context, final Path outPutFile) throws IOException { - - Mustache summaryMustache = mf.compile(new InputStreamReader(SnapShotReportWriter.class.getResourceAsStream(templateLocation)), templateName); - try (Writer writer = Files.newBufferedWriter(outPutFile, Charset.forName("UTF-8"))) { //NON-NLS - summaryMustache.execute(writer, context); - } - } - - /** - * Copy static resources (static html, css, images, etc) to the reports - * folder. - * - * @throws IOException If there is a problem copying the resources. - */ - private void copyResources() throws IOException { - - //pull generator and agency logos from branding - String generatorLogoPath = reportBranding.getGeneratorLogoPath(); - if (StringUtils.isNotBlank(generatorLogoPath)) { - Files.copy(Files.newInputStream(Paths.get(generatorLogoPath)), reportFolderPath.resolve("generator_logo.png")); //NON-NLS - } - String agencyLogoPath = reportBranding.getAgencyLogoPath(); - if (StringUtils.isNotBlank(agencyLogoPath)) { - Files.copy(Files.newInputStream(Paths.get(agencyLogoPath)), reportFolderPath.resolve(Paths.get(reportBranding.getAgencyLogoPath()).getFileName())); //NON-NLS - } - - //copy navigation html - try (InputStream navStream = SnapShotReportWriter.class.getResourceAsStream("/org/sleuthkit/autopsy/timeline/snapshot/navigation.html")) { //NON-NLS - Files.copy(navStream, reportFolderPath.resolve("nav.html")); //NON-NLS - } - //copy favicon - if (StringUtils.isBlank(agencyLogoPath)) { - // use default Autopsy icon if custom icon is not set - try (InputStream faviconStream = SnapShotReportWriter.class.getResourceAsStream("/org/sleuthkit/autopsy/report/images/favicon.ico")) { //NON-NLS - Files.copy(faviconStream, reportFolderPath.resolve("favicon.ico")); //NON-NLS - } - } else { - Files.copy(Files.newInputStream(Paths.get(agencyLogoPath)), reportFolderPath.resolve("favicon.ico")); //NON-NLS - } - - //copy report summary icon - try (InputStream summaryStream = SnapShotReportWriter.class.getResourceAsStream("/org/sleuthkit/autopsy/report/images/summary.png")) { //NON-NLS - Files.copy(summaryStream, reportFolderPath.resolve("summary.png")); //NON-NLS - } - //copy snapshot icon - try (InputStream snapshotIconStream = SnapShotReportWriter.class.getResourceAsStream("/org/sleuthkit/autopsy/timeline/images/image.png")) { //NON-NLS - Files.copy(snapshotIconStream, reportFolderPath.resolve("snapshot_icon.png")); //NON-NLS - } - //copy main report css - try (InputStream resource = SnapShotReportWriter.class.getResourceAsStream("/org/sleuthkit/autopsy/timeline/snapshot/index.css")) { //NON-NLS - Files.copy(resource, reportFolderPath.resolve("index.css")); //NON-NLS - } - //copy summary css - try (InputStream resource = SnapShotReportWriter.class.getResourceAsStream("/org/sleuthkit/autopsy/timeline/snapshot/summary.css")) { //NON-NLS - Files.copy(resource, reportFolderPath.resolve("summary.css")); //NON-NLS - } + fillTemplateAndWrite("/org/sleuthkit/autopsy/timeline/snapshot/snapshot_template.html", "Snapshot", snapShotContext, getReportFolderPath().resolve("snapshot.html")); //NON-NLS } } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED index f64794953f..6f95dfc82f 100755 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED @@ -34,7 +34,8 @@ KeywordSearchIngestModule.startupMessage.failedToGetIndexSchema=Failed to get sc KeywordSearchResultFactory.createNodeForKey.noResultsFound.text=No results found. KeywordSearchResultFactory.query.exception.msg=Could not perform the query OpenIDE-Module-Display-Category=Ingest Module -OpenIDE-Module-Long-Description=Keyword Search ingest module.\n\nThe module indexes files found in the disk image at ingest time.\nIt then periodically runs the search on the indexed files using one or more keyword lists (containing pure words and/or regular expressions) and posts results.\n\n\The module also contains additional tools integrated in the main GUI, such as keyword list configuration, keyword search bar in the top-right corner, extracted text viewer and search results viewer showing highlighted keywords found. + +OpenIDE-Module-Long-Description=Keyword Search ingest module.\n\nThe module indexes files found in the disk image at ingest time.\nIt then periodically runs the search on the indexed files using one or more keyword lists (containing pure words and/or regular expressions) and posts results.\n\nThe module also contains additional tools integrated in the main GUI, such as keyword list configuration, keyword search bar in the top-right corner, extracted text viewer and search results viewer showing highlighted keywords found. OpenIDE-Module-Name=KeywordSearch OptionsCategory_Name_KeywordSearchOptions=Keyword Search OptionsCategory_Keywords_KeywordSearchOptions=Keyword Search