Merge pull request #4609 from kellykelly3/1213-com-view-image-report

1213 communications snapshot report
This commit is contained in:
Richard Cordovano 2019-03-19 12:11:37 -04:00 committed by GitHub
commit 9f5f586aae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 683 additions and 184 deletions

View File

@ -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

View File

@ -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.

View File

@ -11,7 +11,7 @@
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,0,-93,0,0,3,71"/>
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,0,-93,0,0,4,-19"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
@ -49,9 +49,9 @@
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace pref="71" max="32767" attributes="0"/>
<EmptySpace pref="268" max="32767" attributes="0"/>
<Component id="jTextArea1" min="-2" pref="424" max="-2" attributes="0"/>
<EmptySpace pref="248" max="32767" attributes="0"/>
<EmptySpace pref="445" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
@ -120,6 +120,10 @@
<Component id="zoomActualButton" min="-2" pref="33" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="fitZoomButton" min="-2" pref="32" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="jSeparator3" min="-2" pref="10" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="snapshotButton" min="-2" max="-2" attributes="0"/>
<EmptySpace max="32767" attributes="0"/>
</Group>
</Group>
@ -143,6 +147,8 @@
<Component id="zoomLabel" alignment="2" min="-2" max="-2" attributes="0"/>
<Component id="clearVizButton" alignment="2" min="-2" max="-2" attributes="0"/>
<Component id="jSeparator2" alignment="2" max="32767" attributes="0"/>
<Component id="snapshotButton" alignment="2" min="-2" max="-2" attributes="0"/>
<Component id="jSeparator3" alignment="2" max="32767" attributes="0"/>
</Group>
<EmptySpace min="-2" pref="3" max="-2" attributes="0"/>
</Group>
@ -310,6 +316,24 @@
<Property name="orientation" type="int" value="1"/>
</Properties>
</Component>
<Component class="javax.swing.JButton" name="snapshotButton">
<Properties>
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
<Image iconType="3" name="/org/sleuthkit/autopsy/report/images/image.png"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/communications/Bundle.properties" key="VisualizationPanel.snapshotButton.text_1" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="snapshotButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JToolBar$Separator" name="jSeparator3">
<Properties>
<Property name="orientation" type="int" value="1"/>
</Properties>
</Component>
</SubComponents>
</Container>
<Container class="javafx.embed.swing.JFXPanel" name="notificationsJFXPanel">

View File

@ -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);
}
}
}
}

View File

@ -0,0 +1,175 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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<String, Object> snapShotContext = new HashMap<>();
snapShotContext.put("reportTitle", getReportName()); //NON-NLS
List<SubFilter> 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<Account.Type> selectedAccounts = ((AccountTypeFilter) filter).getAccountTypes();
ArrayList<ReportWriterHelper> 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<String> ids = ((DeviceFilter) filter).getDevices();
ArrayList<ReportWriterHelper> 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;
}
}
}

View File

@ -0,0 +1,21 @@
<html>
<head>
<title>Communications Snapshot: {{reportTitle}}</title>
<link rel="stylesheet" type="text/css" href="index.css" />
<link rel="icon" type="image/ico" href="favicon.ico" />
</head>
<body><div id="header">Communications Snapshot</div>
<div id="content">
<img id="snapshot" src="snapshot.png" alt="Snapshot" >
<table>
<tr><td><b>Date Range</b></td><td></td></tr>
<tr><td>Start:</td><td>{{startTime}}</td></tr>
<tr><td>End:</td><td>{{endTime}}</td></tr>
<tr><td><b>Devices:</b></td><td></td></tr>
{{#devices}}<tr><td>{{label}}</td><td><input type="checkbox" {{#selected}}checked{{/selected}}{{^selected}} {{/selected}} disabled></td></tr>{{/devices}}
<tr><td><b>Account Types</b></td><td></td></tr>
{{#accounts}}<tr><td>{{label}}</td><td><input type="checkbox" {{#selected}}checked{{/selected}}{{^selected}} {{/selected}} disabled></td></tr>{{/accounts}}
</table>
</div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 516 B

View File

@ -0,0 +1,241 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2016 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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<String, Object> 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<String, Object> 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
}
}
}

View File

@ -9,7 +9,7 @@
<h1>Report Navigation</h1>
<ul class="nav">
<li style="background: url(summary.png) left center no-repeat;"><a href="summary.html" target="content">Case Summary</a></li>
<li style="background: url(snapshot_icon.png) left center no-repeat;"><a href="snapshot.html" target="content">Timeline Snapshot</a></li>
<li style="background: url(snapshot_icon.png) left center no-repeat;"><a href="snapshot.html" target="content">Snapshot</a></li>
</ul>
</div>
</body>

View File

@ -9,7 +9,7 @@
<div id="wrapper">
<h1>{{reportBranding.getReportTitle}}: {{reportName}}{{#ingestRunning}}<span>Warning, this report was run before ingest services completed!</span>{{/ingestRunning}}</h1>
<p class="subheadding">Timeline Report generated on {{generationDateTime}}</p>
<p class="subheadding">Report generated on {{generationDateTime}}</p>
<div class="title">
{{#reportBranding.getAgencyLogoPath}}
<div class="left">

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2016 Basis Technology Corp.
* Copyright 2016 - 2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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<String, Object> 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<String, Object> 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<String, Object> 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
}
}

View File

@ -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