merge in develop and fixed conflicts

This commit is contained in:
Kelly Kelly 2019-05-22 10:28:11 -04:00
commit 886cc3a696
48 changed files with 2431 additions and 813 deletions

View File

@ -0,0 +1,145 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013-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.casemodule;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import org.apache.commons.io.FileUtils;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.imagewriter.ImageWriterSettings;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.TskCoreException;
/**
* A runnable that
* - copy the logical image folder to a destination folder
* - add alert.txt and users.txt files to report
* - add an image data source to the case database.
*/
public class AddLogicalImageTask extends AddImageTask {
private final static Logger logger = Logger.getLogger(AddLogicalImageTask.class.getName());
private final static String ALERT_TXT = "alert.txt"; //NON-NLS
private final static String USERS_TXT = "users.txt"; //NON-NLS
private final File src;
private final File dest;
private final DataSourceProcessorCallback callback;
private final DataSourceProcessorProgressMonitor progressMonitor;
public AddLogicalImageTask(String deviceId, String imagePath, int sectorSize,
String timeZone, boolean ignoreFatOrphanFiles,
String md5, String sha1, String sha256,
ImageWriterSettings imageWriterSettings,
File src, File dest,
DataSourceProcessorProgressMonitor progressMonitor,
DataSourceProcessorCallback callback
) {
super(deviceId, imagePath, sectorSize, timeZone, ignoreFatOrphanFiles,
md5, sha1, sha256, imageWriterSettings, progressMonitor, callback);
this.src = src;
this.dest = dest;
this.progressMonitor = progressMonitor;
this.callback = callback;
}
/**
* Copy the src directory to dest.
* Add alert.txt and users.txt to the case report
* Adds the image to the case database.
*/
@Messages({
"# {0} - src", "# {1} - dest", "AddLogicalImageTask.copyingImageFromTo=Copying image from {0} to {1}",
"AddLogicalImageTask.doneCopying=Done copying",
"# {0} - src", "# {1} - dest", "AddLogicalImageTask.failedToCopyDirectory=Failed to copy directory {0} to {1}",
"# {0} - file", "AddLogicalImageTask.addingToReport=Adding {0} to report",
"# {0} - file", "AddLogicalImageTask.doneAddingToReport=Done adding {0} to report"
})
@Override
public void run() {
List<String> errorList = new ArrayList<>();
List<Content> emptyDataSources = new ArrayList<>();
try {
progressMonitor.setProgressText(Bundle.AddLogicalImageTask_copyingImageFromTo(src.toString(), dest.toString()));
FileUtils.copyDirectory(src, dest);
progressMonitor.setProgressText(Bundle.AddLogicalImageTask_doneCopying());
} catch (IOException ex) {
// Copy directory failed
String msg = Bundle.AddLogicalImageTask_failedToCopyDirectory(src.toString(), dest.toString());
errorList.add(msg);
logger.log(Level.SEVERE, String.format("Failed to copy directory {0} to {1}", src.toString(), dest.toString()));
callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errorList, emptyDataSources);
return;
}
// Add the alert.txt and users.txt to the case report
progressMonitor.setProgressText(Bundle.AddLogicalImageTask_addingToReport(ALERT_TXT));
String status = addReport(Paths.get(dest.toString(), ALERT_TXT), ALERT_TXT + " " + src.getName());
if (status != null) {
errorList.add(status);
callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errorList, emptyDataSources);
return;
}
progressMonitor.setProgressText(Bundle.AddLogicalImageTask_doneAddingToReport(ALERT_TXT));
progressMonitor.setProgressText(Bundle.AddLogicalImageTask_addingToReport(USERS_TXT));
status = addReport(Paths.get(dest.toString(), USERS_TXT), USERS_TXT + " " + src.getName());
if (status != null) {
errorList.add(status);
callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errorList, emptyDataSources);
return;
}
progressMonitor.setProgressText(Bundle.AddLogicalImageTask_doneAddingToReport(USERS_TXT));
super.run();
}
/**
* Add a file specified by the reportPath to the case report.
*
* @param reportPath Path to the report to be added
* @param reportName Name associated the report
* @returns null if success, or exception message if failure
*
*/
@Messages({
"# {0} - file", "# {1} - exception message", "AddLogicalImageTask.failedToAddReport=Failed to add report {0}. Reason= {1}"
})
private String addReport(Path reportPath, String reportName) {
if (!reportPath.toFile().exists()) {
return null; // if the reportPath doesn't exist, just ignore it.
}
try {
Case.getCurrentCase().addReport(reportPath.toString(), "LogicalImager", reportName); //NON-NLS
return null;
} catch (TskCoreException ex) {
String msg = Bundle.AddLogicalImageTask_failedToAddReport(reportPath.toString(), ex.getMessage());
logger.log(Level.SEVERE, String.format("Failed to add report {0}. Reason= {1}", reportPath.toString(), ex.getMessage()));
return msg;
}
}
}

View File

@ -1,5 +1,19 @@
AddImageWizardIngestConfigPanel.name.text=Configure Ingest Modules
AddImageWizardSelectDspVisual.multiUserWarning.text=This type of Data Source Processor is not available in multi-user mode
# {0} - file
AddLogicalImageTask.addingToReport=Adding {0} to report
# {0} - src
# {1} - dest
AddLogicalImageTask.copyingImageFromTo=Copying image from {0} to {1}
# {0} - file
AddLogicalImageTask.doneAddingToReport=Done adding {0} to report
AddLogicalImageTask.doneCopying=Done copying
# {0} - file
# {1} - exception message
AddLogicalImageTask.failedToAddReport=Failed to add report {0}. Reason= {1}
# {0} - src
# {1} - dest
AddLogicalImageTask.failedToCopyDirectory=Failed to copy directory {0} to {1}
# {0} - exception message
Case.closeException.couldNotCloseCase=Error closing case: {0}
Case.creationException.couldNotAcquireDirLock=Failed to get lock on case directory
@ -169,10 +183,19 @@ LogicalEvidenceFilePanel.validatePanel.nonL01Error.text=Only files with the .l01
LogicalFilesDspPanel.subTypeComboBox.l01FileOption.text=Logical evidence file (L01)
LogicalFilesDspPanel.subTypeComboBox.localFilesOption.text=Local files and folders
LogicalImagerDSProcessor.dataSourceType=Autopsy Imager
# {0} - directory
LogicalImagerDSProcessor.directoryAlreadyExists=Directory {0} already exists
# {0} - directory
LogicalImagerDSProcessor.failToCreateDirectory=Failed to create directory {0}
# {0} - imageDirPath
LogicalImagerDSProcessor.imageDirPathNotFound={0} not found.\nUSB drive has been ejected.
LogicalImagerPanel.imageTable.columnModel.title0=Hostname
LogicalImagerPanel.imageTable.columnModel.title1=Extracted Date
LogicalImagerPanel.messageLabel.clickScanOrBrowse=Click SCAN or BROWSE button to find images
# {0} - sparseImageDirectory
# {1} - image
LogicalImagerPanel.messageLabel.directoryDoesNotContainSparseImage=Directory {0} does not contain {1}
# {0} - invalidFormatDirectory
LogicalImagerPanel.messageLabel.directoryFormatInvalid=Directory {0} does not match format Logical_Imager_HOSTNAME_yyyymmdd_HH_MM_SS
LogicalImagerPanel.messageLabel.driveHasNoImages=Drive has no images
LogicalImagerPanel.messageLabel.noExternalDriveFound=No drive found

View File

@ -18,7 +18,13 @@
*/
package org.sleuthkit.autopsy.casemodule;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.UUID;
import javax.swing.JPanel;
import org.openide.util.NbBundle.Messages;
import org.openide.util.lookup.ServiceProvider;
@ -26,6 +32,7 @@ import org.openide.util.lookup.ServiceProviders;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor;
import org.sleuthkit.datamodel.Content;
/**
* A Logical Imager data source processor that implements the DataSourceProcessor service
@ -38,7 +45,10 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgress
)
public class LogicalImagerDSProcessor implements DataSourceProcessor {
private static final String LOGICAL_IMAGER_DIR = "LogicalImager"; //NON-NLS
private static final String SPARSE_IMAGE_VHD = "sparse_image.vhd"; //NON-NLS
private final LogicalImagerPanel configPanel;
private AddLogicalImageTask addLogicalImageTask;
/*
* Constructs a Logical Imager data source processor that implements the
@ -114,12 +124,55 @@ public class LogicalImagerDSProcessor implements DataSourceProcessor {
* @param callback Callback that will be used by the background task
* to return results.
*/
@Messages({
"# {0} - imageDirPath", "LogicalImagerDSProcessor.imageDirPathNotFound={0} not found.\nUSB drive has been ejected.",
"# {0} - directory", "LogicalImagerDSProcessor.failToCreateDirectory=Failed to create directory {0}",
"# {0} - directory", "LogicalImagerDSProcessor.directoryAlreadyExists=Directory {0} already exists",
})
@Override
public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
configPanel.storeSettings();
Path dirPath = configPanel.getImageDirPath();
System.out.println("Choosen directory " + dirPath.toString());
// TODO: process the data source in 5011
Path imageDirPath = configPanel.getImageDirPath();
List<String> errorList = new ArrayList<>();
List<Content> emptyDataSources = new ArrayList<>();
if (!imageDirPath.toFile().exists()) {
// This can happen if the USB drive was selected in the panel, but
// was ejected before pressing the NEXT button
// TODO: Better ways to detect ejected USB drive?
String msg = Bundle.LogicalImagerDSProcessor_imageDirPathNotFound(imageDirPath.toString());
errorList.add(msg);
callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errorList, emptyDataSources);
return;
}
// Create the LogicalImager directory under ModuleDirectory
String moduleDirectory = Case.getCurrentCase().getModuleDirectory();
File logicalImagerDir = Paths.get(moduleDirectory, LOGICAL_IMAGER_DIR).toFile();
if (!logicalImagerDir.exists() && !logicalImagerDir.mkdir()) {
// create failed
String msg = Bundle.LogicalImagerDSProcessor_failToCreateDirectory(logicalImagerDir);
errorList.add(msg);
callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errorList, emptyDataSources);
return;
}
File dest = Paths.get(logicalImagerDir.toString(), imageDirPath.getFileName().toString()).toFile();
if (dest.exists()) {
// Destination directory already exists
String msg = Bundle.LogicalImagerDSProcessor_directoryAlreadyExists(dest.toString());
errorList.add(msg);
callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errorList, emptyDataSources);
return;
}
File src = imageDirPath.toFile();
String deviceId = UUID.randomUUID().toString();
String timeZone = Calendar.getInstance().getTimeZone().getID();
boolean ignoreFatOrphanFiles = false;
run(deviceId, Paths.get(src.toString(), SPARSE_IMAGE_VHD).toString(), 0,
timeZone, ignoreFatOrphanFiles, null, null, null, src, dest,
progressMonitor, callback);
}
/**
@ -140,17 +193,28 @@ public class LogicalImagerDSProcessor implements DataSourceProcessor {
* @param chunkSize The maximum size of each chunk of the raw
* data source as it is divided up into virtual
* unallocated space files.
* @param src The source directory of image.
* @param dest The destination directory to copy the source.
* @param progressMonitor Progress monitor for reporting progress
* during processing.
* @param callback Callback to call when processing is done.
*/
private void run(String deviceId, String imagePath, int sectorSize, String timeZone, boolean ignoreFatOrphanFiles, String md5, String sha1, String sha256, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
AddImageTask addImageTask = new AddImageTask(deviceId, imagePath, sectorSize, timeZone, ignoreFatOrphanFiles, md5, sha1, sha256, null, progressMonitor, callback);
new Thread(addImageTask).start();
private void run(String deviceId, String imagePath, int sectorSize, String timeZone,
boolean ignoreFatOrphanFiles, String md5, String sha1, String sha256,
File src, File dest,
DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback
) {
addLogicalImageTask = new AddLogicalImageTask(deviceId, imagePath, sectorSize,
timeZone, ignoreFatOrphanFiles, md5, sha1, sha256, null, src, dest,
progressMonitor, callback);
new Thread(addLogicalImageTask).start();
}
@Override
public void cancel() {
if (addLogicalImageTask != null) {
addLogicalImageTask.cancelTask();
}
}
/**

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.8" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<Form version="1.6" maxVersion="1.8" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<Properties>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[0, 65]"/>
@ -213,6 +213,9 @@
<Property name="columnModel" type="javax.swing.table.TableColumnModel" editor="org.netbeans.modules.form.editors2.TableColumnModelEditor">
<TableColumnModel selectionModel="1"/>
</Property>
<Property name="selectionModel" type="javax.swing.ListSelectionModel" editor="org.netbeans.modules.form.editors2.JTableSelectionModelEditor">
<JTableSelectionModel selectionMode="0"/>
</Property>
<Property name="showHorizontalLines" type="boolean" value="false"/>
<Property name="showVerticalLines" type="boolean" value="false"/>
<Property name="tableHeader" type="javax.swing.table.JTableHeader" editor="org.netbeans.modules.form.editors2.JTableHeaderEditor">

View File

@ -42,15 +42,14 @@ import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor;
/**
* Panel for adding an logical image file from drive letters. Allows the user
* to select a file.
* Panel for adding an logical image file from drive letters. Allows the user to
* select a file.
*/
@Messages({
"LogicalImagerPanel.messageLabel.selectedImage=Selected folder",
"LogicalImagerPanel.messageLabel.noImageSelected=No image selected",
"LogicalImagerPanel.messageLabel.driveHasNoImages=Drive has no images",
"LogicalImagerPanel.selectAcquisitionFromDriveLabel.text=Select acquisition from Drive",
})
"LogicalImagerPanel.selectAcquisitionFromDriveLabel.text=Select acquisition from Drive",})
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
public class LogicalImagerPanel extends JPanel implements DocumentListener {
@ -70,8 +69,8 @@ public class LogicalImagerPanel extends JPanel implements DocumentListener {
/**
* Creates new form LogicalImagerPanel
*
* @param context A string context name used to read/store last
* used settings.
* @param context A string context name used to read/store last used
* settings.
*/
private LogicalImagerPanel(String context) {
this.contextName = context;
@ -82,8 +81,8 @@ public class LogicalImagerPanel extends JPanel implements DocumentListener {
/**
* Creates and returns an instance of a LogicalImagerPanel.
*
* @param context A string context name used to read/store last
* used settings.
* @param context A string context name used to read/store last used
* settings.
*
* @return instance of the LogicalImagerPanel
*/
@ -173,6 +172,7 @@ public class LogicalImagerPanel extends JPanel implements DocumentListener {
}
));
imageTable.setAutoResizeMode(javax.swing.JTable.AUTO_RESIZE_OFF);
imageTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
imageTable.setShowHorizontalLines(false);
imageTable.setShowVerticalLines(false);
imageTable.getTableHeader().setReorderingAllowed(false);
@ -264,7 +264,7 @@ public class LogicalImagerPanel extends JPanel implements DocumentListener {
return bytes + " B"; //NON-NLS
}
int exp = (int) (Math.log(bytes) / Math.log(unit));
String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp-1) + (si ? "" : "i"); //NON-NLS
String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i"); //NON-NLS
return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre); //NON-NLS
}
@ -310,7 +310,10 @@ public class LogicalImagerPanel extends JPanel implements DocumentListener {
}//GEN-LAST:event_scanButtonActionPerformed
@Messages({
"# {0} - sparseImageDirectory",
"# {1} - image",
"LogicalImagerPanel.messageLabel.directoryDoesNotContainSparseImage=Directory {0} does not contain {1}",
"# {0} - invalidFormatDirectory",
"LogicalImagerPanel.messageLabel.directoryFormatInvalid=Directory {0} does not match format Logical_Imager_HOSTNAME_yyyymmdd_HH_MM_SS"
})
private void browseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseButtonActionPerformed
@ -325,7 +328,7 @@ public class LogicalImagerPanel extends JPanel implements DocumentListener {
if (m.find()) {
Path vhdPath = Paths.get(path, SPARSE_IMAGE_VHD);
if (!vhdPath.toFile().exists()) {
setErrorMessage(Bundle.LogicalImagerPanel_messageLabel_directoryDoesNotContainSparseImage(path,SPARSE_IMAGE_VHD));
setErrorMessage(Bundle.LogicalImagerPanel_messageLabel_directoryDoesNotContainSparseImage(path, SPARSE_IMAGE_VHD));
firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), true, false);
return;
}
@ -374,7 +377,7 @@ public class LogicalImagerPanel extends JPanel implements DocumentListener {
// and has a sparse_image.vhd file in it
for (File file : fList) {
if (file.isDirectory()
&& Paths.get(driveLetter, file.getName(), SPARSE_IMAGE_VHD).toFile().exists()) {
&& Paths.get(driveLetter, file.getName(), SPARSE_IMAGE_VHD).toFile().exists()) {
String dir = file.getName();
Matcher m = regex.matcher(dir);
if (m.find()) {
@ -485,6 +488,10 @@ public class LogicalImagerPanel extends JPanel implements DocumentListener {
return choosenImageDirPath;
}
public void setMessageLabel(String message) {
messageLabel.setText(message);
}
@Override
public void insertUpdate(DocumentEvent e) {
}
@ -501,6 +508,7 @@ public class LogicalImagerPanel extends JPanel implements DocumentListener {
}
private class ImageTableModel extends AbstractTableModel {
private final List<String> hostnames = new ArrayList<>();
private final List<String> extractDates = new ArrayList<>();
private final List<String> imageDirPaths = new ArrayList<>();

View File

@ -22,7 +22,11 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.eventbus.Subscribe;
import java.awt.event.ItemListener;
import java.beans.PropertyChangeListener;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Collection;
import java.util.EnumSet;
@ -51,6 +55,7 @@ import static org.sleuthkit.autopsy.ingest.IngestManager.IngestModuleEvent.DATA_
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
import org.sleuthkit.datamodel.Account;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.CaseDbAccessManager.CaseDbAccessQueryCallback;
import org.sleuthkit.datamodel.CommunicationsFilter;
import org.sleuthkit.datamodel.CommunicationsFilter.AccountTypeFilter;
import org.sleuthkit.datamodel.CommunicationsFilter.DateRangeFilter;
@ -157,6 +162,29 @@ final public class FiltersPanel extends JPanel {
applyFiltersButton.addActionListener(e -> applyFilters());
refreshButton.addActionListener(e -> applyFilters());
try {
String queryString = "max(date_time) as max, min(date_time) as min from account_relationships"; // NON-NLS
Case.getCurrentCaseThrows().getSleuthkitCase().getCaseDbAccessManager().select(queryString, new FilterPanelQueryCallback() {
@Override
public void process(ResultSet rs) {
try {
if (rs.next()) {
int startDate = rs.getInt("min"); // NON-NLS
int endData = rs.getInt("max"); // NON-NLS
startDatePicker.setDate(LocalDateTime.ofInstant(Instant.ofEpochSecond(startDate), Utils.getUserPreferredZoneId()).toLocalDate());
endDatePicker.setDate(LocalDateTime.ofInstant(Instant.ofEpochSecond(endData), Utils.getUserPreferredZoneId()).toLocalDate());
}
} catch (SQLException ex) {
logger.log(Level.WARNING, "Unable to set filter date pickers due to SQL exception", ex); //NON-NLS
}
}
});
} catch (NoCurrentCaseException | TskCoreException ex) {
logger.log(Level.SEVERE, "Unable to set filter date pickers due to exception", ex); //NON-NLS
}
}
/**
@ -958,4 +986,17 @@ final public class FiltersPanel extends JPanel {
checkbox.addItemListener(l);
}
}
/**
* A simple class that implements CaseDbAccessQueryCallback. Can be used
* as an anonymous innerclass with the CaseDbAccessManager select function.
*/
class FilterPanelQueryCallback implements CaseDbAccessQueryCallback {
@Override
public void process(ResultSet rs) {
// Subclasses can implement their own process function.
}
}
}

View File

@ -16,11 +16,11 @@
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="htmlScrollPane" pref="300" max="32767" attributes="0"/>
<Group type="102" attributes="0">
<Component id="showImagesToggleButton" min="-2" max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
<EmptySpace min="0" pref="95" max="32767" attributes="0"/>
</Group>
<Component id="htmlJPanel" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
@ -28,29 +28,12 @@
<Group type="102" alignment="0" attributes="0">
<Component id="showImagesToggleButton" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="htmlScrollPane" pref="71" max="32767" attributes="0"/>
<Component id="htmlJPanel" pref="33" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Container class="javax.swing.JScrollPane" name="htmlScrollPane">
<Properties>
<Property name="verticalScrollBarPolicy" type="int" value="22"/>
</Properties>
<AuxValues>
<AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents>
<Component class="javax.swing.JTextPane" name="htmlbodyTextPane">
<Properties>
<Property name="editable" type="boolean" value="false"/>
</Properties>
</Component>
</SubComponents>
</Container>
<Component class="javax.swing.JToggleButton" name="showImagesToggleButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
@ -61,5 +44,9 @@
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="showImagesToggleButtonActionPerformed"/>
</Events>
</Component>
<Container class="javax.swing.JPanel" name="htmlJPanel">
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
</Container>
</SubComponents>
</Form>

View File

@ -18,9 +18,19 @@
*/
package org.sleuthkit.autopsy.contentviewers;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Worker;
import javafx.scene.web.WebView;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Node;
import org.openide.util.NbBundle.Messages;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.w3c.dom.events.EventTarget;
/**
* A file content viewer for HTML files.
@ -29,7 +39,9 @@ import org.openide.util.NbBundle.Messages;
final class HtmlPanel extends javax.swing.JPanel {
private static final long serialVersionUID = 1L;
private static final String TEXT_TYPE = "text/plain";
private final JFXPanel jfxPanel = new JFXPanel();
private WebView webView;
private String htmlText;
/**
@ -37,8 +49,26 @@ final class HtmlPanel extends javax.swing.JPanel {
*/
HtmlPanel() {
initComponents();
Utilities.configureTextPaneAsHtml(htmlbodyTextPane);
Platform.runLater(() -> {
webView = new WebView();
//disable the context menu so they can't open linked pages by right clicking
webView.setContextMenuEnabled(false);
//disable java script
webView.getEngine().setJavaScriptEnabled(false);
//disable clicking on links
webView.getEngine().getLoadWorker().stateProperty().addListener(new ChangeListener<Worker.State>() {
@Override
public void changed(ObservableValue<? extends Worker.State> observable, Worker.State oldValue, Worker.State newValue) {
if (newValue == Worker.State.SUCCEEDED) {
disableHyperLinks();
}
}
});
Scene scene = new Scene(webView);
jfxPanel.setScene(scene);
jfxPanel.setPreferredSize(htmlJPanel.getPreferredSize());
htmlJPanel.add(jfxPanel);
});
}
/**
@ -55,21 +85,12 @@ final class HtmlPanel extends javax.swing.JPanel {
* Clear the HTML in the text pane and disable the show/hide button.
*/
void reset() {
htmlbodyTextPane.setText("");
Platform.runLater(() -> {
webView.getEngine().loadContent("", TEXT_TYPE);
});
showImagesToggleButton.setEnabled(false);
}
/**
* Guarantee the HTML text has 'html' and 'body' tags.
*
* @param htmlText The HTML text
*
* @return The HTML text with the 'html' and 'body' tags applied.
*/
private String wrapInHtmlBody(String htmlText) {
return "<html><body>" + htmlText + "</body></html>";
}
/**
* Cleans out input HTML string
*
@ -78,12 +99,11 @@ final class HtmlPanel extends javax.swing.JPanel {
* @return The cleansed HTML String
*/
private String cleanseHTML(String htmlInString) {
Document doc = Jsoup.parse(htmlInString);
// Update all 'img' tags.
doc.select("img[src]").forEach(img -> img.attr("src", ""));
org.jsoup.nodes.Document doc = Jsoup.parse(htmlInString);
// remove all 'img' tags.
doc.select("img").stream().forEach(Node::remove);
// remove all 'span' tags, these are often images which are ads
doc.select("span").stream().forEach(Node::remove);
return doc.html();
}
@ -93,22 +113,26 @@ final class HtmlPanel extends javax.swing.JPanel {
@Messages({
"HtmlPanel_showImagesToggleButton_show=Show Images",
"HtmlPanel_showImagesToggleButton_hide=Hide Images",
"Html_text_display_error=The HTML text cannot be displayed, it may not be correctly formed HTML.",
})
"Html_text_display_error=The HTML text cannot be displayed, it may not be correctly formed HTML.",})
private void refresh() {
if (false == htmlText.isEmpty()) {
try {
if (showImagesToggleButton.isSelected()) {
showImagesToggleButton.setText(Bundle.HtmlPanel_showImagesToggleButton_hide());
this.htmlbodyTextPane.setText(wrapInHtmlBody(htmlText));
Platform.runLater(() -> {
webView.getEngine().loadContent(htmlText);
});
} else {
showImagesToggleButton.setText(Bundle.HtmlPanel_showImagesToggleButton_show());
this.htmlbodyTextPane.setText(wrapInHtmlBody(cleanseHTML(htmlText)));
Platform.runLater(() -> {
webView.getEngine().loadContent(cleanseHTML(htmlText));
});
}
showImagesToggleButton.setEnabled(true);
htmlbodyTextPane.setCaretPosition(0);
} catch(Exception ex) {
this.htmlbodyTextPane.setText(wrapInHtmlBody(Bundle.Html_text_display_error()));
} catch (Exception ignored) {
Platform.runLater(() -> {
webView.getEngine().loadContent(Bundle.Html_text_display_error(), TEXT_TYPE);
});
}
}
}
@ -122,14 +146,8 @@ final class HtmlPanel extends javax.swing.JPanel {
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
htmlScrollPane = new javax.swing.JScrollPane();
htmlbodyTextPane = new javax.swing.JTextPane();
showImagesToggleButton = new javax.swing.JToggleButton();
htmlScrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
htmlbodyTextPane.setEditable(false);
htmlScrollPane.setViewportView(htmlbodyTextPane);
htmlJPanel = new javax.swing.JPanel();
org.openide.awt.Mnemonics.setLocalizedText(showImagesToggleButton, org.openide.util.NbBundle.getMessage(HtmlPanel.class, "HtmlPanel.showImagesToggleButton.text")); // NOI18N
showImagesToggleButton.addActionListener(new java.awt.event.ActionListener() {
@ -138,21 +156,23 @@ final class HtmlPanel extends javax.swing.JPanel {
}
});
htmlJPanel.setLayout(new java.awt.BorderLayout());
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(htmlScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 300, Short.MAX_VALUE)
.addGroup(layout.createSequentialGroup()
.addComponent(showImagesToggleButton)
.addGap(0, 0, Short.MAX_VALUE))
.addGap(0, 95, Short.MAX_VALUE))
.addComponent(htmlJPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(showImagesToggleButton)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(htmlScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 71, Short.MAX_VALUE))
.addComponent(htmlJPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 33, Short.MAX_VALUE))
);
}// </editor-fold>//GEN-END:initComponents
@ -160,10 +180,27 @@ final class HtmlPanel extends javax.swing.JPanel {
refresh();
}//GEN-LAST:event_showImagesToggleButtonActionPerformed
/**
* Disable the click events on hyper links so that new pages can not be
* opened.
*/
private void disableHyperLinks() {
Platform.runLater(() -> {
Document document = webView.getEngine().getDocument();
if (document != null) {
NodeList nodeList = document.getElementsByTagName("a");
for (int i = 0; i < nodeList.getLength(); i++) {
((EventTarget) nodeList.item(i)).addEventListener("click", (evt) -> {
evt.preventDefault();
evt.stopPropagation();
}, true);
}
}
});
}
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JScrollPane htmlScrollPane;
private javax.swing.JTextPane htmlbodyTextPane;
private javax.swing.JPanel htmlJPanel;
private javax.swing.JToggleButton showImagesToggleButton;
// End of variables declaration//GEN-END:variables
}

View File

@ -77,6 +77,7 @@ public final class UserPreferences {
public static final String DISPLAY_TRANSLATED_NAMES = "DisplayTranslatedNames";
public static final String EXTERNAL_HEX_EDITOR_PATH = "ExternalHexEditorPath";
public static final String SOLR_MAX_JVM_SIZE = "SolrMaxJVMSize";
public static final String RESULTS_TABLE_PAGE_SIZE = "ResultsTablePageSize";
// Prevent instantiation.
private UserPreferences() {
@ -500,6 +501,24 @@ public final class UserPreferences {
preferences.putInt(SOLR_MAX_JVM_SIZE, maxSize);
}
/**
* Get the maximum number of results to display in a result table.
*
* @return Saved value or default (0) which indicates no max.
*/
public static int getResultsTablePageSize() {
return preferences.getInt(RESULTS_TABLE_PAGE_SIZE, 0);
}
/**
* Set the maximum number of results to display in a result table.
*
* @param pageSize
*/
public static void setResultsTablePageSize(int pageSize) {
preferences.putInt(RESULTS_TABLE_PAGE_SIZE, pageSize);
}
/**
* Set the HdX path.
*

View File

@ -207,3 +207,13 @@ ExternalViewerGlobalSettingsPanel.jTable1.columnModel.title0_1=Mime type/Extensi
AutopsyOptionsPanel.maxSolrMemoryLabel.text=Maximum Solr JVM Memory:
AutopsyOptionsPanel.maxMemoryUnitsLabel2.text=MB
AutopsyOptionsPanel.solrJVMHeapWarning.text=NOTE: Setting this too large may impact overall performance.
DataResultViewerTable.gotoPageTextField.text=
DataResultViewerTable.gotoPageLabel.AccessibleContext.accessibleName=
DataResultViewerTable.gotoPageLabel.text=Go to Page:
DataResultViewerTable.pageNextButton.text=
DataResultViewerTable.pagePrevButton.text=
DataResultViewerTable.pagesLabel.text=Pages:
DataResultViewerTable.pageNumLabel.text=
DataResultViewerTable.pageLabel.text=Page:
ViewPreferencesPanel.maxResultsLabel.text=Maximum number of Results to show in table:
ViewPreferencesPanel.maxResultsLabel.toolTipText=<html>\nAll results are shown in the results table if this value is 0 (default).\n<br>You may want to consider setting this value if you have large numbers of results that are taking a long time to display.\n</html>

View File

@ -33,6 +33,12 @@ DataResultViewerTable.commentRenderer.tagComment.toolTip=Comment exists on assoc
DataResultViewerTable.countRender.name=O
DataResultViewerTable.countRender.toolTip=O(ccurrences) indicates the number of data sources containing the item in the Central Repository
DataResultViewerTable.firstColLbl=Name
DataResultViewerTable.goToPageTextField.err=Invalid page number
# {0} - totalPages
DataResultViewerTable.goToPageTextField.msgDlg=Please enter a valid page number between 1 and {0}
# {0} - currentPage
# {1} - totalPages
DataResultViewerTable.pageNumbers.curOfTotal={0} of {1}
DataResultViewerTable.scoreRender.name=S
DataResultViewerTable.scoreRender.toolTip=S(core) indicates whether the item is interesting or notable
DataResultViewerTable.title=Table
@ -254,3 +260,13 @@ ExternalViewerGlobalSettingsPanel.jTable1.columnModel.title0_1=Mime type/Extensi
AutopsyOptionsPanel.maxSolrMemoryLabel.text=Maximum Solr JVM Memory:
AutopsyOptionsPanel.maxMemoryUnitsLabel2.text=MB
AutopsyOptionsPanel.solrJVMHeapWarning.text=NOTE: Setting this too large may impact overall performance.
DataResultViewerTable.gotoPageTextField.text=
DataResultViewerTable.gotoPageLabel.AccessibleContext.accessibleName=
DataResultViewerTable.gotoPageLabel.text=Go to Page:
DataResultViewerTable.pageNextButton.text=
DataResultViewerTable.pagePrevButton.text=
DataResultViewerTable.pagesLabel.text=Pages:
DataResultViewerTable.pageNumLabel.text=
DataResultViewerTable.pageLabel.text=Page:
ViewPreferencesPanel.maxResultsLabel.text=Maximum number of Results to show in table:
ViewPreferencesPanel.maxResultsLabel.toolTipText=<html>\nAll results are shown in the results table if this value is 0 (default).\n<br>You may want to consider setting this value if you have large numbers of results that are taking a long time to display.\n</html>

View File

@ -16,16 +16,127 @@
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="outlineView" alignment="0" pref="691" max="32767" attributes="0"/>
<Component id="outlineView" alignment="0" max="32767" attributes="0"/>
<Group type="102" alignment="1" attributes="0">
<EmptySpace pref="608" max="32767" attributes="0"/>
<Component id="pageLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="pageNumLabel" min="-2" pref="53" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="pagesLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="pagePrevButton" linkSize="1" min="-2" pref="16" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="pageNextButton" linkSize="1" min="-2" pref="16" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="gotoPageLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="gotoPageTextField" min="-2" pref="33" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="outlineView" alignment="0" pref="366" max="32767" attributes="0"/>
<Group type="102" alignment="1" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="2" attributes="0">
<Component id="pageLabel" alignment="2" min="-2" max="-2" attributes="0"/>
<Component id="pageNumLabel" alignment="2" min="-2" max="-2" attributes="0"/>
<Component id="pagesLabel" alignment="2" min="-2" max="-2" attributes="0"/>
<Component id="pagePrevButton" linkSize="2" alignment="2" min="-2" pref="14" max="-2" attributes="0"/>
<Component id="pageNextButton" linkSize="2" alignment="2" min="-2" pref="15" max="-2" attributes="0"/>
<Component id="gotoPageLabel" alignment="2" min="-2" max="-2" attributes="0"/>
<Component id="gotoPageTextField" alignment="2" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace min="-2" max="-2" attributes="0"/>
<Component id="outlineView" pref="324" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Component class="javax.swing.JLabel" name="pageLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultViewerTable.pageLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="pageNumLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultViewerTable.pageNumLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="pagesLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultViewerTable.pagesLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JButton" name="pagePrevButton">
<Properties>
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
<Image iconType="3" name="/org/sleuthkit/autopsy/corecomponents/btn_step_back.png"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultViewerTable.pagePrevButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="disabledIcon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
<Image iconType="3" name="/org/sleuthkit/autopsy/corecomponents/btn_step_back_disabled.png"/>
</Property>
<Property name="focusable" type="boolean" value="false"/>
<Property name="horizontalTextPosition" type="int" value="0"/>
<Property name="margin" type="java.awt.Insets" editor="org.netbeans.beaninfo.editors.InsetsEditor">
<Insets value="[2, 0, 2, 0]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[55, 23]"/>
</Property>
<Property name="rolloverIcon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
<Image iconType="3" name="/org/sleuthkit/autopsy/corecomponents/btn_step_back_hover.png"/>
</Property>
<Property name="verticalTextPosition" type="int" value="3"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="pagePrevButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JButton" name="pageNextButton">
<Properties>
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
<Image iconType="3" name="/org/sleuthkit/autopsy/corecomponents/btn_step_forward.png"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultViewerTable.pageNextButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="disabledIcon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
<Image iconType="3" name="/org/sleuthkit/autopsy/corecomponents/btn_step_forward_disabled.png"/>
</Property>
<Property name="focusable" type="boolean" value="false"/>
<Property name="horizontalTextPosition" type="int" value="0"/>
<Property name="margin" type="java.awt.Insets" editor="org.netbeans.beaninfo.editors.InsetsEditor">
<Insets value="[2, 0, 2, 0]"/>
</Property>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[27, 23]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[27, 23]"/>
</Property>
<Property name="rolloverIcon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
<Image iconType="3" name="/org/sleuthkit/autopsy/corecomponents/btn_step_forward_hover.png"/>
</Property>
<Property name="verticalTextPosition" type="int" value="3"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="pageNextButtonActionPerformed"/>
</Events>
</Component>
<Container class="org.openide.explorer.view.OutlineView" name="outlineView">
<AuxValues>
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="new OutlineView(DataResultViewerTable.FIRST_COLUMN_LABEL);"/>
@ -33,5 +144,25 @@
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
</Container>
<Component class="javax.swing.JLabel" name="gotoPageLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultViewerTable.gotoPageLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<AccessibilityProperties>
<Property name="AccessibleContext.accessibleName" type="java.lang.String" value=""/>
</AccessibilityProperties>
</Component>
<Component class="javax.swing.JTextField" name="gotoPageTextField">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultViewerTable.gotoPageTextField.text" 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="gotoPageTextFieldActionPerformed"/>
</Events>
</Component>
</SubComponents>
</Form>

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2012-2018 Basis Technology Corp.
* Copyright 2012-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -18,6 +18,7 @@
*/
package org.sleuthkit.autopsy.corecomponents;
import com.google.common.eventbus.Subscribe;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.FontMetrics;
@ -26,6 +27,7 @@ import java.awt.dnd.DnDConstants;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.FeatureDescriptor;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyVetoException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
@ -35,9 +37,12 @@ import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.prefs.PreferenceChangeEvent;
import java.util.prefs.Preferences;
import javax.swing.ImageIcon;
import javax.swing.JOptionPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import static javax.swing.SwingConstants.CENTER;
@ -61,15 +66,24 @@ import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.nodes.Node.Property;
import org.openide.nodes.NodeEvent;
import org.openide.nodes.NodeListener;
import org.openide.nodes.NodeMemberEvent;
import org.openide.nodes.NodeReorderEvent;
import org.openide.util.ImageUtilities;
import org.openide.util.NbBundle;
import org.openide.util.NbPreferences;
import org.openide.util.lookup.ServiceProvider;
import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.datamodel.NodeProperty;
import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo;
import org.sleuthkit.autopsy.datamodel.BaseChildFactory;
import org.sleuthkit.autopsy.datamodel.BaseChildFactory.PageChangeEvent;
import org.sleuthkit.autopsy.datamodel.BaseChildFactory.PageCountChangeEvent;
import org.sleuthkit.autopsy.datamodel.BaseChildFactory.PageSizeChangeEvent;
/**
* A tabular result viewer that displays the children of the given root node
@ -104,6 +118,18 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
private final IconRendererTableListener iconRendererListener;
private Node rootNode;
/**
* Multiple nodes may have been visited in the context of this
* DataResultViewerTable. We keep track of the page state for these nodes in
* the following map.
*/
private final Map<String, PagingSupport> nodeNameToPagingSupportMap = new ConcurrentHashMap<>();
/**
* The paging support instance for the current node.
*/
private PagingSupport pagingSupport = null;
/**
* Constructs a tabular result viewer that displays the children of the
* given root node using an OutlineView. The viewer should have an ancestor
@ -149,6 +175,8 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
*/
initComponents();
initializePagingSupport();
/*
* Configure the child OutlineView (explorer view) component.
*/
@ -177,6 +205,32 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
outline.getTableHeader().addMouseListener(outlineViewListener);
}
private void initializePagingSupport() {
if (pagingSupport == null) {
pagingSupport = new PagingSupport("");
}
// Start out with paging controls invisible
pagingSupport.togglePageControls(false);
/**
* Set up a change listener so we know when the user changes the page
* size
*/
UserPreferences.addChangeListener((PreferenceChangeEvent evt) -> {
if (evt.getKey().equals(UserPreferences.RESULTS_TABLE_PAGE_SIZE)) {
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
/**
* If multiple nodes have been viewed we have to notify all of
* them about the change in page size.
*/
nodeNameToPagingSupportMap.values().forEach((ps) -> {
ps.postPageSizeChangeEvent();
});
}
});
}
/**
* Creates a new instance of a tabular result viewer that displays the
* children of a given root node using an OutlineView. This method exists to
@ -252,6 +306,56 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
*/
if (rootNode != null && rootNode.getChildren().getNodesCount() > 0) {
this.rootNode = rootNode;
/**
* Check to see if we have previously created a paging support
* class for this node.
*/
String nodeName = rootNode.getName();
pagingSupport = nodeNameToPagingSupportMap.get(nodeName);
if (pagingSupport == null) {
pagingSupport = new PagingSupport(nodeName);
nodeNameToPagingSupportMap.put(nodeName, pagingSupport);
}
pagingSupport.updateControls();
rootNode.addNodeListener(new NodeListener() {
@Override
public void childrenAdded(NodeMemberEvent nme) {
/**
* This is the only somewhat reliable way I could find
* to reset the cursor after a page change. When you
* change page the old children nodes will be removed
* and new ones added.
*/
SwingUtilities.invokeLater(() -> {
setCursor(null);
});
}
@Override
public void childrenRemoved(NodeMemberEvent nme) {
SwingUtilities.invokeLater(() -> {
setCursor(null);
});
}
@Override
public void childrenReordered(NodeReorderEvent nre) {
// No-op
}
@Override
public void nodeDestroyed(NodeEvent ne) {
// No-op
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
// No-op
}
});
this.getExplorerManager().setRootContext(this.rootNode);
setupTable();
} else {
@ -665,6 +769,152 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
}
}
/**
* Maintains the current page state for a node and provides support for
* paging through results. Uses an EventBus to communicate with child
* factory implementations.
*/
private class PagingSupport {
private int currentPage;
private int totalPages;
private final String nodeName;
PagingSupport(String nodeName) {
currentPage = 1;
totalPages = 0;
this.nodeName = nodeName;
initialize();
}
private void initialize() {
if (!nodeName.isEmpty()) {
BaseChildFactory.register(nodeName, this);
}
updateControls();
}
void nextPage() {
currentPage++;
postPageChangeEvent();
}
void previousPage() {
currentPage--;
postPageChangeEvent();
}
@NbBundle.Messages({"# {0} - totalPages",
"DataResultViewerTable.goToPageTextField.msgDlg=Please enter a valid page number between 1 and {0}",
"DataResultViewerTable.goToPageTextField.err=Invalid page number"})
void gotoPage() {
try {
currentPage = Integer.decode(gotoPageTextField.getText());
} catch (NumberFormatException e) {
//ignore input
return;
}
if (currentPage > totalPages || currentPage < 1) {
currentPage = 1;
JOptionPane.showMessageDialog(DataResultViewerTable.this,
Bundle.DataResultViewerTable_goToPageTextField_msgDlg(totalPages),
Bundle.DataResultViewerTable_goToPageTextField_err(),
JOptionPane.WARNING_MESSAGE);
return;
}
postPageChangeEvent();
}
/**
* Notify subscribers (i.e. child factories) that a page change has
* occurred.
*/
void postPageChangeEvent() {
try {
BaseChildFactory.post(nodeName, new PageChangeEvent(currentPage));
} catch (BaseChildFactory.NoSuchEventBusException ex) {
LOGGER.log(Level.WARNING, "Failed to post page change event.", ex); //NON-NLS
}
DataResultViewerTable.this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
updateControls();
}
/**
* Notify subscribers (i.e. child factories) that a page size change has
* occurred.
*/
void postPageSizeChangeEvent() {
// Reset page variables when page size changes
currentPage = 1;
totalPages = 0;
if (this == pagingSupport) {
updateControls();
}
try {
BaseChildFactory.post(nodeName, new PageSizeChangeEvent(UserPreferences.getResultsTablePageSize()));
} catch (BaseChildFactory.NoSuchEventBusException ex) {
LOGGER.log(Level.WARNING, "Failed to post page size change event.", ex); //NON-NLS
}
}
/**
* Subscribe to notification that the number of pages has changed.
*
* @param event
*/
@Subscribe
public void subscribeToPageCountChange(PageCountChangeEvent event) {
if (event != null) {
totalPages = event.getPageCount();
if (totalPages > 1) {
// Make paging controls visible if there is more than one page.
togglePageControls(true);
}
updateControls();
}
}
/**
* Make paging controls visible or invisible based on flag.
*
* @param onOff
*/
private void togglePageControls(boolean onOff) {
pageLabel.setVisible(onOff);
pagesLabel.setVisible(onOff);
pagePrevButton.setVisible(onOff);
pageNextButton.setVisible(onOff);
pageNumLabel.setVisible(onOff);
gotoPageLabel.setVisible(onOff);
gotoPageTextField.setVisible(onOff);
gotoPageTextField.setVisible(onOff);
validate();
repaint();
}
@NbBundle.Messages({"# {0} - currentPage", "# {1} - totalPages",
"DataResultViewerTable.pageNumbers.curOfTotal={0} of {1}"})
private void updateControls() {
if (totalPages == 0) {
pagePrevButton.setEnabled(false);
pageNextButton.setEnabled(false);
pageNumLabel.setText("");
gotoPageTextField.setText("");
gotoPageTextField.setEnabled(false);
} else {
pageNumLabel.setText(Bundle.DataResultViewerTable_pageNumbers_curOfTotal(Integer.toString(currentPage), Integer.toString(totalPages)));
pageNextButton.setEnabled(currentPage != totalPages);
pagePrevButton.setEnabled(currentPage != 1);
gotoPageTextField.setEnabled(totalPages > 1);
gotoPageTextField.setText("");
}
}
}
/**
* Listener which sets the custom icon renderer on columns which contain
* icons instead of text when a column is added.
@ -1033,21 +1283,129 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
pageLabel = new javax.swing.JLabel();
pageNumLabel = new javax.swing.JLabel();
pagesLabel = new javax.swing.JLabel();
pagePrevButton = new javax.swing.JButton();
pageNextButton = new javax.swing.JButton();
outlineView = new OutlineView(DataResultViewerTable.FIRST_COLUMN_LABEL);
gotoPageLabel = new javax.swing.JLabel();
gotoPageTextField = new javax.swing.JTextField();
pageLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.pageLabel.text")); // NOI18N
pageNumLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.pageNumLabel.text")); // NOI18N
pagesLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.pagesLabel.text")); // NOI18N
pagePrevButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back.png"))); // NOI18N
pagePrevButton.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.pagePrevButton.text")); // NOI18N
pagePrevButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back_disabled.png"))); // NOI18N
pagePrevButton.setFocusable(false);
pagePrevButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
pagePrevButton.setMargin(new java.awt.Insets(2, 0, 2, 0));
pagePrevButton.setPreferredSize(new java.awt.Dimension(55, 23));
pagePrevButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back_hover.png"))); // NOI18N
pagePrevButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
pagePrevButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
pagePrevButtonActionPerformed(evt);
}
});
pageNextButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward.png"))); // NOI18N
pageNextButton.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.pageNextButton.text")); // NOI18N
pageNextButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward_disabled.png"))); // NOI18N
pageNextButton.setFocusable(false);
pageNextButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
pageNextButton.setMargin(new java.awt.Insets(2, 0, 2, 0));
pageNextButton.setMaximumSize(new java.awt.Dimension(27, 23));
pageNextButton.setMinimumSize(new java.awt.Dimension(27, 23));
pageNextButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward_hover.png"))); // NOI18N
pageNextButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
pageNextButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
pageNextButtonActionPerformed(evt);
}
});
gotoPageLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.gotoPageLabel.text")); // NOI18N
gotoPageTextField.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.gotoPageTextField.text")); // NOI18N
gotoPageTextField.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
gotoPageTextFieldActionPerformed(evt);
}
});
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 691, Short.MAX_VALUE)
.addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addContainerGap(608, Short.MAX_VALUE)
.addComponent(pageLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(pageNumLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 53, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(pagesLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(pagePrevButton, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(pageNextButton, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(gotoPageLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(gotoPageTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 33, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap())
);
layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {pageNextButton, pagePrevButton});
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 366, Short.MAX_VALUE)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER)
.addComponent(pageLabel)
.addComponent(pageNumLabel)
.addComponent(pagesLabel)
.addComponent(pagePrevButton, javax.swing.GroupLayout.PREFERRED_SIZE, 14, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(pageNextButton, javax.swing.GroupLayout.PREFERRED_SIZE, 15, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(gotoPageLabel)
.addComponent(gotoPageTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 324, Short.MAX_VALUE)
.addContainerGap())
);
layout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {pageNextButton, pagePrevButton});
gotoPageLabel.getAccessibleContext().setAccessibleName("");
}// </editor-fold>//GEN-END:initComponents
private void pagePrevButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pagePrevButtonActionPerformed
pagingSupport.previousPage();
}//GEN-LAST:event_pagePrevButtonActionPerformed
private void pageNextButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pageNextButtonActionPerformed
pagingSupport.nextPage();
}//GEN-LAST:event_pageNextButtonActionPerformed
private void gotoPageTextFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_gotoPageTextFieldActionPerformed
pagingSupport.gotoPage();
}//GEN-LAST:event_gotoPageTextFieldActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JLabel gotoPageLabel;
private javax.swing.JTextField gotoPageTextField;
private org.openide.explorer.view.OutlineView outlineView;
private javax.swing.JLabel pageLabel;
private javax.swing.JButton pageNextButton;
private javax.swing.JLabel pageNumLabel;
private javax.swing.JButton pagePrevButton;
private javax.swing.JLabel pagesLabel;
// End of variables declaration//GEN-END:variables
}

View File

@ -68,7 +68,7 @@
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<Component id="globalSettingsPanel" min="-2" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<EmptySpace type="unrelated" min="-2" max="-2" attributes="0"/>
<Component id="currentCaseSettingsPanel" min="-2" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="currentSessionSettingsPanel" min="-2" max="-2" attributes="0"/>
@ -92,7 +92,7 @@
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<Group type="102" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
@ -153,6 +153,11 @@
</Group>
</Group>
</Group>
<Group type="102" alignment="0" attributes="0">
<Component id="maxResultsLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="maxResultsSpinner" min="-2" pref="74" max="-2" attributes="0"/>
</Group>
</Group>
<EmptySpace max="32767" attributes="0"/>
</Group>
@ -206,6 +211,11 @@
<Component id="fileNameTranslationColumnCheckbox" min="-2" max="-2" attributes="0"/>
</Group>
</Group>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="maxResultsLabel" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="maxResultsSpinner" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace max="32767" attributes="0"/>
</Group>
</Group>
@ -407,6 +417,26 @@
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="fileNameTranslationColumnCheckboxActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JLabel" name="maxResultsLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="ViewPreferencesPanel.maxResultsLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="ViewPreferencesPanel.maxResultsLabel.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JSpinner" name="maxResultsSpinner">
<Properties>
<Property name="model" type="javax.swing.SpinnerModel" editor="org.netbeans.modules.form.editors2.SpinnerModelEditor">
<SpinnerModel initial="0" maximum="50000" minimum="0" numberType="java.lang.Integer" stepSize="10000" type="number"/>
</Property>
</Properties>
<Events>
<EventHandler event="stateChanged" listener="javax.swing.event.ChangeListener" parameters="javax.swing.event.ChangeEvent" handler="maxResultsSpinnerStateChanged"/>
</Events>
</Component>
</SubComponents>
</Container>
<Container class="javax.swing.JPanel" name="currentCaseSettingsPanel">

View File

@ -35,6 +35,7 @@ import org.sleuthkit.autopsy.texttranslation.TextTranslationService;
/**
* Panel for configuring view preferences.
*/
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
private final boolean immediateUpdates;
@ -85,6 +86,8 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
TextTranslationService tts = TextTranslationService.getInstance();
fileNameTranslationColumnCheckbox.setEnabled(tts.hasProvider());
maxResultsSpinner.setValue(UserPreferences.getResultsTablePageSize());
// Current Case Settings
boolean caseIsOpen = Case.isCaseOpen();
currentCaseSettingsPanel.setEnabled(caseIsOpen);
@ -114,6 +117,7 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
UserPreferences.setShowOnlyCurrentUserTags(hideOtherUsersTagsCheckbox.isSelected());
UserPreferences.setHideCentralRepoCommentsAndOccurrences(commentsOccurencesColumnsCheckbox.isSelected());
UserPreferences.setDisplayTranslatedFileNames(fileNameTranslationColumnCheckbox.isSelected());
UserPreferences.setResultsTablePageSize((int)maxResultsSpinner.getValue());
storeGroupItemsInTreeByDataSource();
@ -167,6 +171,8 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
translateTextLabel = new javax.swing.JLabel();
commentsOccurencesColumnWrapAroundText = new javax.swing.JLabel();
fileNameTranslationColumnCheckbox = new javax.swing.JCheckBox();
maxResultsLabel = new javax.swing.JLabel();
maxResultsSpinner = new javax.swing.JSpinner();
currentCaseSettingsPanel = new javax.swing.JPanel();
groupByDataSourceCheckbox = new javax.swing.JCheckBox();
currentSessionSettingsPanel = new javax.swing.JPanel();
@ -284,6 +290,16 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
}
});
org.openide.awt.Mnemonics.setLocalizedText(maxResultsLabel, org.openide.util.NbBundle.getMessage(ViewPreferencesPanel.class, "ViewPreferencesPanel.maxResultsLabel.text")); // NOI18N
maxResultsLabel.setToolTipText(org.openide.util.NbBundle.getMessage(ViewPreferencesPanel.class, "ViewPreferencesPanel.maxResultsLabel.toolTipText")); // NOI18N
maxResultsSpinner.setModel(new javax.swing.SpinnerNumberModel(0, 0, 50000, 10000));
maxResultsSpinner.addChangeListener(new javax.swing.event.ChangeListener() {
public void stateChanged(javax.swing.event.ChangeEvent evt) {
maxResultsSpinnerStateChanged(evt);
}
});
javax.swing.GroupLayout globalSettingsPanelLayout = new javax.swing.GroupLayout(globalSettingsPanel);
globalSettingsPanel.setLayout(globalSettingsPanelLayout);
globalSettingsPanelLayout.setHorizontalGroup(
@ -333,7 +349,11 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
.addComponent(keepCurrentViewerRadioButton)
.addComponent(useBestViewerRadioButton)
.addComponent(useLocalTimeRadioButton)
.addComponent(useAnotherTimeRadioButton))))))
.addComponent(useAnotherTimeRadioButton)))))
.addGroup(globalSettingsPanelLayout.createSequentialGroup()
.addComponent(maxResultsLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(maxResultsSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, 74, javax.swing.GroupLayout.PREFERRED_SIZE)))
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
globalSettingsPanelLayout.setVerticalGroup(
@ -381,6 +401,10 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
.addComponent(translateTextLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(fileNameTranslationColumnCheckbox)))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addGroup(globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(maxResultsLabel)
.addComponent(maxResultsSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
@ -593,6 +617,14 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
}
}//GEN-LAST:event_fileNameTranslationColumnCheckboxActionPerformed
private void maxResultsSpinnerStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_maxResultsSpinnerStateChanged
if (immediateUpdates) {
UserPreferences.setResultsTablePageSize((int)maxResultsSpinner.getValue());
} else {
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
}
}//GEN-LAST:event_maxResultsSpinnerStateChanged
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JLabel centralRepoLabel;
@ -613,6 +645,8 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
private javax.swing.JLabel hideSlackFilesLabel;
private javax.swing.JScrollPane jScrollPane1;
private javax.swing.JRadioButton keepCurrentViewerRadioButton;
private javax.swing.JLabel maxResultsLabel;
private javax.swing.JSpinner maxResultsSpinner;
private javax.swing.JLabel selectFileLabel;
private javax.swing.JList<String> timeZoneList;
private javax.swing.JLabel translateTextLabel;

View File

@ -34,7 +34,6 @@ import java.util.stream.Collectors;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.openide.nodes.Children;
import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
import org.openide.util.WeakListeners;
@ -55,6 +54,8 @@ import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable.Score;
import org.sleuthkit.autopsy.coreutils.Logger;
import static org.sleuthkit.autopsy.datamodel.Bundle.*;
import static org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.AbstractFilePropertyType.*;
import org.sleuthkit.autopsy.datamodel.BaseChildFactory.NoSuchEventBusException;
import org.sleuthkit.autopsy.datamodel.BaseChildFactory.RefreshKeysEvent;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.autopsy.ingest.ModuleContentEvent;
import org.sleuthkit.autopsy.texttranslation.NoServiceProviderException;
@ -164,17 +165,15 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
if (getContent().getId() == newContent.getId()) {
// If so, refresh our children.
try {
Children parentsChildren = getParentNode().getChildren();
// We only want to refresh our parents children if we are in the
// data sources branch of the tree. The parent nodes in other
// branches of the tree (e.g. File Types and Deleted Files) do
// not need to be refreshed.
if (parentsChildren instanceof ContentChildren) {
((ContentChildren) parentsChildren).refreshChildren();
parentsChildren.getNodesCount();
}
BaseChildFactory.post(getParentNode().getName(), new RefreshKeysEvent());
} catch (NullPointerException ex) {
// Skip
} catch (NoSuchEventBusException ex) {
logger.log(Level.WARNING, "Failed to post key refresh event", ex); //NON-NLS
}
}
} else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2016 Basis Technology Corp.
* Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -18,228 +18,28 @@
*/
package org.sleuthkit.autopsy.datamodel;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children.Keys;
import org.openide.nodes.Node;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.datamodel.FileTypes.FileTypesNode;
import org.sleuthkit.autopsy.datamodel.accounts.Accounts;
import org.sleuthkit.autopsy.datamodel.accounts.Accounts.AccountsRootNode;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.DerivedFile;
import org.sleuthkit.datamodel.Directory;
import org.sleuthkit.datamodel.File;
import org.sleuthkit.datamodel.Image;
import org.sleuthkit.datamodel.LayoutFile;
import org.sleuthkit.datamodel.LocalFile;
import org.sleuthkit.datamodel.LocalDirectory;
import org.sleuthkit.datamodel.SlackFile;
import org.sleuthkit.datamodel.SleuthkitItemVisitor;
import org.sleuthkit.datamodel.SleuthkitVisitableItem;
import org.sleuthkit.datamodel.VirtualDirectory;
import org.sleuthkit.datamodel.Volume;
/**
* Abstract subclass for ContentChildren and RootContentChildren implementations
* Abstract subclass for ContentChildren implementation
* that handles creating Nodes from Content objects.
*/
abstract class AbstractContentChildren<T> extends Keys<T> {
abstract class AbstractContentChildren<T extends Content> extends BaseChildFactory<T> {
private final CreateSleuthkitNodeVisitor createSleuthkitNodeVisitor = new CreateSleuthkitNodeVisitor();
private final CreateAutopsyNodeVisitor createAutopsyNodeVisitor = new CreateAutopsyNodeVisitor();
/**
* Uses lazy Content.Keys
*/
AbstractContentChildren() {
/*
* This was turned off because we were getting out of memory errors when
* the filter nodes were hiding nodes. Turning this off seemed to help
*/
super(false); //don't use lazy behavior
AbstractContentChildren(String nodeName) {
super(nodeName, new DataSourcesKnownAndSlackFilter<>());
}
@Override
protected Node[] createNodes(T key) {
protected Node createNodeForKey(T key) {
if (key instanceof SleuthkitVisitableItem) {
return new Node[]{((SleuthkitVisitableItem) key).accept(createSleuthkitNodeVisitor)};
return ((SleuthkitVisitableItem) key).accept(createSleuthkitNodeVisitor);
} else {
return new Node[]{((AutopsyVisitableItem) key).accept(createAutopsyNodeVisitor)};
}
}
/**
* Creates appropriate Node for each sub-class of Content
*/
public static class CreateSleuthkitNodeVisitor extends SleuthkitItemVisitor.Default<AbstractContentNode<? extends Content>> {
@Override
public AbstractContentNode<? extends Content> visit(Directory drctr) {
return new DirectoryNode(drctr);
}
@Override
public AbstractContentNode<? extends Content> visit(File file) {
return new FileNode(file);
}
@Override
public AbstractContentNode<? extends Content> visit(Image image) {
return new ImageNode(image);
}
@Override
public AbstractContentNode<? extends Content> visit(Volume volume) {
return new VolumeNode(volume);
}
@Override
public AbstractContentNode<? extends Content> visit(LayoutFile lf) {
return new LayoutFileNode(lf);
}
@Override
public AbstractContentNode<? extends Content> visit(DerivedFile df) {
return new LocalFileNode(df);
}
@Override
public AbstractContentNode<? extends Content> visit(LocalFile lf) {
return new LocalFileNode(lf);
}
@Override
public AbstractContentNode<? extends Content> visit(VirtualDirectory ld) {
return new VirtualDirectoryNode(ld);
}
@Override
public AbstractContentNode<? extends Content> visit(LocalDirectory ld) {
return new LocalDirectoryNode(ld);
}
@Override
public AbstractContentNode<? extends Content> visit(SlackFile sf) {
return new SlackFileNode(sf);
}
@Override
public AbstractContentNode<? extends Content> visit(BlackboardArtifact art) {
return new BlackboardArtifactNode(art);
}
@Override
protected AbstractContentNode<? extends Content> defaultVisit(SleuthkitVisitableItem di) {
throw new UnsupportedOperationException(NbBundle.getMessage(this.getClass(),
"AbstractContentChildren.CreateTSKNodeVisitor.exception.noNodeMsg"));
}
}
/**
* Gets a DisplayableItemNode for use as a subtree root node for the Autopsy
* tree view from each type of AutopsyVisitableItem visited. There are
* AutopsyVisitableItems for the Data Sources, Views, Results, and Reports
* subtrees, and for the subtrees of Results (e.g., Extracted Content, Hash
* Set Hits, etc.).
*/
static class CreateAutopsyNodeVisitor extends AutopsyItemVisitor.Default<AbstractNode> {
@Override
public ExtractedContent.RootNode visit(ExtractedContent ec) {
return ec.new RootNode(ec.getSleuthkitCase());
}
@Override
public AbstractNode visit(FileTypesByExtension sf) {
return sf.new FileTypesByExtNode(sf.getSleuthkitCase(), null);
}
@Override
public AbstractNode visit(RecentFiles rf) {
return new RecentFilesNode(rf.getSleuthkitCase());
}
@Override
public AbstractNode visit(DeletedContent dc) {
return new DeletedContent.DeletedContentsNode(dc.getSleuthkitCase(), dc.filteringDataSourceObjId());
}
@Override
public AbstractNode visit(FileSize dc) {
return new FileSize.FileSizeRootNode(dc.getSleuthkitCase(), dc.filteringDataSourceObjId());
}
@Override
public AbstractNode visit(KeywordHits kh) {
return kh.new RootNode();
}
@Override
public AbstractNode visit(HashsetHits hh) {
return hh.new RootNode();
}
@Override
public AbstractNode visit(InterestingHits ih) {
return ih.new RootNode();
}
@Override
public AbstractNode visit(EmailExtracted ee) {
return ee.new RootNode();
}
@Override
public AbstractNode visit(Tags tagsNodeKey) {
return tagsNodeKey.new RootNode(tagsNodeKey.filteringDataSourceObjId());
}
@Override
public AbstractNode visit(DataSources i) {
return new DataSourcesNode(i.filteringDataSourceObjId());
}
@Override
public AbstractNode visit(DataSourceGrouping datasourceGrouping) {
return new DataSourceGroupingNode(datasourceGrouping.getDataSource());
}
@Override
public AbstractNode visit(Views v) {
return new ViewsNode(v.getSleuthkitCase(), v.filteringDataSourceObjId());
}
@Override
public AbstractNode visit(Results results) {
return new ResultsNode(results.getSleuthkitCase(), results.filteringDataSourceObjId() );
}
@Override
public AbstractNode visit(FileTypes ft) {
return ft.new FileTypesNode();
}
@Override
public AbstractNode visit(Reports reportsItem) {
return new Reports.ReportsListNode();
}
@Override
public AbstractNode visit(Accounts accountsItem) {
return accountsItem.new AccountsRootNode();
}
@Override
protected AbstractNode defaultVisit(AutopsyVisitableItem di) {
throw new UnsupportedOperationException(
NbBundle.getMessage(this.getClass(),
"AbstractContentChildren.createAutopsyNodeVisitor.exception.noNodeMsg"));
}
@Override
public AbstractNode visit(FileTypesByMimeType ftByMimeTypeItem) {
return ftByMimeTypeItem.new ByMimeTypeNode();
return null;
}
}
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2018 Basis Technology Corp.
* Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -22,6 +22,7 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.logging.Level;
import org.openide.nodes.Children;
import org.openide.util.lookup.Lookups;
import org.openide.util.Lookup;
@ -65,8 +66,7 @@ public abstract class AbstractContentNode<T extends Content> extends ContentNode
* @param lookup The Lookup object for the node.
*/
AbstractContentNode(T content, Lookup lookup) {
//TODO consider child factory for the content children
super(new ContentChildren(content), lookup);
super(Children.create(new ContentChildren(content), true), lookup);
this.content = content;
//super.setName(ContentUtils.getSystemName(content));
super.setName("content_" + Long.toString(content.getId())); //NON-NLS

View File

@ -135,7 +135,7 @@ public final class AutopsyTreeChildFactory extends ChildFactory.Detachable<Objec
protected Node createNodeForKey(Object key) {
if (key instanceof SleuthkitVisitableItem) {
return ((SleuthkitVisitableItem) key).accept(new RootContentChildren.CreateSleuthkitNodeVisitor());
return ((SleuthkitVisitableItem) key).accept(new CreateSleuthkitNodeVisitor());
} else if (key instanceof AutopsyVisitableItem) {
return ((AutopsyVisitableItem) key).accept(new RootContentChildren.CreateAutopsyNodeVisitor());
}

View File

@ -0,0 +1,346 @@
/*
* 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.datamodel;
import com.google.common.collect.Lists;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.PreferenceChangeEvent;
import java.util.stream.Collectors;
import org.openide.nodes.ChildFactory;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.datamodel.Content;
/**
* Abstract child factory that provides paging and filtering functionality to
* subclasses.
*
* @param <T>
*/
public abstract class BaseChildFactory<T extends Content> extends ChildFactory.Detachable<T> {
private static final Logger logger = Logger.getLogger(BaseChildFactory.class.getName());
private Predicate<T> filter;
private boolean isPageChangeEvent;
private boolean isPageSizeChangeEvent;
private final PagingSupport pagingSupport;
/**
* This static map is used to facilitate communication between the UI and
* the child factory.
*/
private static Map<String, EventBus> nodeNameToEventBusMap = new ConcurrentHashMap<>();
@Messages({
"# {0} - node name", "BaseChildFactory.NoSuchEventBusException.message=No event bus for node: {0}"
})
public static class NoSuchEventBusException extends Exception {
public NoSuchEventBusException(String nodeName) {
super(Bundle.BaseChildFactory_NoSuchEventBusException_message(nodeName));
}
}
/**
* Register the given subscriber for the given node name. Will create the
* event bus for the given node name if it does not exist.
*
* @param nodeName The name of the node.
* @param subscriber The subscriber to register.
*/
public static void register(String nodeName, Object subscriber) {
EventBus bus = nodeNameToEventBusMap.get(nodeName);
if (bus == null) {
bus = new EventBus(nodeName);
nodeNameToEventBusMap.put(nodeName, bus);
}
bus.register(subscriber);
}
/**
* Post the given event for the given node name.
*
* @param nodeName The name of the node.
* @param event The event to post.
*
* @throws
* org.sleuthkit.autopsy.datamodel.BaseChildFactory.NoSuchEventBusException
*/
public static void post(String nodeName, Object event) throws NoSuchEventBusException {
EventBus bus = nodeNameToEventBusMap.get(nodeName);
if (bus == null) {
throw new NoSuchEventBusException(nodeName);
}
bus.post(event);
}
public BaseChildFactory(String nodeName) {
/**
* Initialize a no-op filter that always returns true.
*/
this(nodeName, x -> true);
}
public BaseChildFactory(String nodeName, Predicate<T> filter) {
pagingSupport = new PagingSupport(nodeName);
pagingSupport.initialize();
isPageChangeEvent = false;
isPageSizeChangeEvent = false;
this.filter = filter;
}
@Override
protected void addNotify() {
onAdd();
}
@Override
protected void removeNotify() {
onRemove();
}
/**
* Subclasses implement this to construct a collection of keys.
*
* @return
*/
protected abstract List<T> makeKeys();
/**
* Subclasses implement this to initialize any required resources.
*/
protected abstract void onAdd();
/**
* Subclasses implement this to clean up any resources they acquired in
* onAdd()
*/
protected abstract void onRemove();
@Override
protected boolean createKeys(List<T> toPopulate) {
/**
* For page change events and page size change events we simply return
* the previously calculated set of keys, otherwise we make a new set of
* keys.
*/
if (!isPageChangeEvent && !isPageSizeChangeEvent) {
List<T> allKeys = makeKeys();
pagingSupport.splitKeysIntoPages(allKeys.stream().filter(filter).collect(Collectors.toList()));
}
toPopulate.addAll(pagingSupport.getCurrentPage());
// Reset page change and page size change event flags
isPageChangeEvent = false;
isPageSizeChangeEvent = false;
return true;
}
/**
* Event used to trigger recreation of the keys.
*/
public static class RefreshKeysEvent {
}
/**
* Event used to let subscribers know that the user has navigated to a
* different page.
*/
public static class PageChangeEvent {
private final int pageNumber;
public PageChangeEvent(int newPageNumber) {
pageNumber = newPageNumber;
}
public int getPageNumber() {
return pageNumber;
}
}
/**
* Event used to let subscribers know that the number of pages has changed.
*/
public static class PageCountChangeEvent {
private final int pageCount;
public PageCountChangeEvent(int newPageCount) {
pageCount = newPageCount;
}
public int getPageCount() {
return pageCount;
}
}
/**
* Event used to let subscribers know that the page size has changed.
*/
public static class PageSizeChangeEvent {
private final int pageSize;
public PageSizeChangeEvent(int newPageSize) {
pageSize = newPageSize;
}
public int getPageSize() {
return pageSize;
}
}
/**
* Class that supplies paging related functionality to the base child
* factory class.
*/
private class PagingSupport {
private final String nodeName;
private int pageSize;
private int currentPage;
private List<List<T>> pages;
/**
* Construct PagingSupport instance for the given node name.
*
* @param nodeName Name of the node in the tree for which results are
* being displayed. The node name is used to allow
* communication between the UI and the ChildFactory via
* an EventBus.
*/
PagingSupport(String nodeName) {
currentPage = 1;
pageSize = UserPreferences.getResultsTablePageSize();
pages = new ArrayList<>();
this.nodeName = nodeName;
}
void initialize() {
/**
* Set up a change listener so we know when the user changes the
* page size.
*/
UserPreferences.addChangeListener((PreferenceChangeEvent evt) -> {
if (evt.getKey().equals(UserPreferences.RESULTS_TABLE_PAGE_SIZE)) {
pageSize = UserPreferences.getResultsTablePageSize();
}
});
register(nodeName, this);
}
/**
* Get the list of keys at the current page.
*
* @return List of keys.
*/
List<T> getCurrentPage() {
if (!pages.isEmpty()) {
return pages.get(currentPage - 1);
}
return Collections.emptyList();
}
/**
* Split the given collection of keys into pages based on page size.
*
* @param keys
*/
void splitKeysIntoPages(List<T> keys) {
int oldPageCount = pages.size();
/**
* If pageSize is set split keys into pages, otherwise create a
* single page containing all keys.
*/
pages = Lists.partition(keys, pageSize > 0 ? pageSize : keys.size());
if (pages.size() != oldPageCount) {
try {
// Number of pages has changed so we need to send out a notification.
post(nodeName, new PageCountChangeEvent(pages.size()));
} catch (NoSuchEventBusException ex) {
logger.log(Level.WARNING, "Failed to post page change event.", ex);
}
}
}
/**
* Receives page change events from UI components and triggers a refresh
* in the child factory.
*
* @param event
*/
@Subscribe
private void subscribeToPageChange(PageChangeEvent event) {
if (event != null) {
currentPage = event.getPageNumber();
isPageChangeEvent = true;
refresh(true);
}
}
/**
* Receives page size change events from UI components and triggers a
* refresh in the child factory if necessary.
*
* @param event
*/
@Subscribe
private void subscribeToPageSizeChange(PageSizeChangeEvent event) {
if (event != null) {
int newPageSize = event.getPageSize();
if (pageSize == newPageSize) {
// No change...nothing to do.
return;
}
pageSize = newPageSize;
splitKeysIntoPages(pages.stream().flatMap(List::stream).collect(Collectors.toList()));
currentPage = 1;
isPageSizeChangeEvent = true;
refresh(true);
}
}
@Subscribe
private void subscribeToRefreshKeys(RefreshKeysEvent event) {
if (event != null) {
refresh(true);
}
}
}
}

View File

@ -43,6 +43,8 @@ ArtifactStringContent.attrsTableHeader.type=Type
ArtifactStringContent.attrsTableHeader.value=Value
ArtifactStringContent.failedToGetAttributes.message=Failed to get some or all attributes from case database
ArtifactStringContent.failedToGetSourcePath.message=Failed to get source file path from case database
# {0} - node name
BaseChildFactory.NoSuchEventBusException.message=No event bus for node: {0}
BlackboardArtifactNode.createSheet.artifactDetails.displayName=Result Details
BlackboardArtifactNode.createSheet.artifactDetails.name=Result Details
BlackboardArtifactNode.createSheet.artifactMD5.displayName=MD5 Hash

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2014 Basis Technology Corp.
* Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -33,17 +33,15 @@ import org.sleuthkit.datamodel.VolumeSystem;
/**
* Makes the children nodes / keys for a given content object. Has knowledge
* about the structure of the directory tree and what levels should be ignored.
* TODO consider a ContentChildren child factory
*/
class ContentChildren extends AbstractContentChildren<Content> {
private static final Logger logger = Logger.getLogger(ContentChildren.class.getName());
//private static final int MAX_CHILD_COUNT = 1000000;
private final Content parent;
ContentChildren(Content parent) {
super(); //initialize lazy behavior
super("content_" + Long.toString(parent.getId()));
this.parent = parent;
}
@ -90,7 +88,7 @@ class ContentChildren extends AbstractContentChildren<Content> {
children.add(c);
}
} else if (c instanceof LocalDirectory) {
LocalDirectory localDir = (LocalDirectory)c;
LocalDirectory localDir = (LocalDirectory) c;
if (localDir.isRoot()) {
children.addAll(getDisplayChildren(localDir));
} else {
@ -104,27 +102,13 @@ class ContentChildren extends AbstractContentChildren<Content> {
}
@Override
protected void addNotify() {
super.addNotify();
//TODO check global settings
//if above limit, query and return subrange
//StopWatch s2 = new StopWatch();
//s2.start();
//logger.log(Level.INFO, "GETTING CHILDREN CONTENT for parent: " + parent.getName());
List<Content> children = getDisplayChildren(parent);
//s2.stop();
//logger.log(Level.INFO, "GOT CHILDREN CONTENTS:" + children.size() + ", took: " + s2.getElapsedTime());
//limit number children
//setKeys(children.subList(0, Math.min(children.size(), MAX_CHILD_COUNT)));
setKeys(children);
protected List<Content> makeKeys() {
return getDisplayChildren(parent);
}
@Override
protected void removeNotify() {
super.removeNotify();
setKeys(new ArrayList<>());
protected void onAdd() {
// No-op
}
/**
@ -133,7 +117,11 @@ class ContentChildren extends AbstractContentChildren<Content> {
* them).
*/
void refreshChildren() {
List<Content> children = getDisplayChildren(parent);
setKeys(children);
refresh(true);
}
@Override
protected void onRemove() {
// No-op
}
}

View File

@ -0,0 +1,102 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-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.datamodel;
import org.openide.util.NbBundle;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.DerivedFile;
import org.sleuthkit.datamodel.Directory;
import org.sleuthkit.datamodel.File;
import org.sleuthkit.datamodel.Image;
import org.sleuthkit.datamodel.LayoutFile;
import org.sleuthkit.datamodel.LocalDirectory;
import org.sleuthkit.datamodel.LocalFile;
import org.sleuthkit.datamodel.SlackFile;
import org.sleuthkit.datamodel.SleuthkitItemVisitor;
import org.sleuthkit.datamodel.SleuthkitVisitableItem;
import org.sleuthkit.datamodel.VirtualDirectory;
import org.sleuthkit.datamodel.Volume;
/**
* Creates appropriate Node for each sub-class of Content
*/
public class CreateSleuthkitNodeVisitor extends SleuthkitItemVisitor.Default<AbstractContentNode<? extends Content>> {
@Override
public AbstractContentNode<? extends Content> visit(Directory drctr) {
return new DirectoryNode(drctr);
}
@Override
public AbstractContentNode<? extends Content> visit(File file) {
return new FileNode(file);
}
@Override
public AbstractContentNode<? extends Content> visit(Image image) {
return new ImageNode(image);
}
@Override
public AbstractContentNode<? extends Content> visit(Volume volume) {
return new VolumeNode(volume);
}
@Override
public AbstractContentNode<? extends Content> visit(LayoutFile lf) {
return new LayoutFileNode(lf);
}
@Override
public AbstractContentNode<? extends Content> visit(DerivedFile df) {
return new LocalFileNode(df);
}
@Override
public AbstractContentNode<? extends Content> visit(LocalFile lf) {
return new LocalFileNode(lf);
}
@Override
public AbstractContentNode<? extends Content> visit(VirtualDirectory ld) {
return new VirtualDirectoryNode(ld);
}
@Override
public AbstractContentNode<? extends Content> visit(LocalDirectory ld) {
return new LocalDirectoryNode(ld);
}
@Override
public AbstractContentNode<? extends Content> visit(SlackFile sf) {
return new SlackFileNode(sf);
}
@Override
public AbstractContentNode<? extends Content> visit(BlackboardArtifact art) {
return new BlackboardArtifactNode(art);
}
@Override
protected AbstractContentNode<? extends Content> defaultVisit(SleuthkitVisitableItem di) {
throw new UnsupportedOperationException(NbBundle.getMessage(this.getClass(),
"AbstractContentChildren.CreateTSKNodeVisitor.exception.noNodeMsg"));
}
}

View File

@ -0,0 +1,52 @@
/*
* 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.datamodel;
import java.util.prefs.PreferenceChangeEvent;
import org.sleuthkit.autopsy.core.UserPreferences;
import static org.sleuthkit.autopsy.datamodel.KnownAndSlackFilterBase.filterKnown;
import static org.sleuthkit.autopsy.datamodel.KnownAndSlackFilterBase.filterSlack;
import org.sleuthkit.datamodel.Content;
/**
* Known and Slack filter for Data Sources section of the tree.
*
* @param <T>
*/
class DataSourcesKnownAndSlackFilter<T extends Content> extends KnownAndSlackFilterBase<T> {
static {
/**
* Watch for user preference changes and update variables inherited from
* our parent. The actual filtering is provided by our parent class.
*/
UserPreferences.addChangeListener((PreferenceChangeEvent evt) -> {
if (evt.getKey().equals(UserPreferences.HIDE_KNOWN_FILES_IN_DATA_SRCS_TREE)) {
filterKnown = UserPreferences.hideKnownFilesInDataSourcesTree();
} else if (evt.getKey().equals(UserPreferences.HIDE_SLACK_FILES_IN_DATA_SRCS_TREE)) {
filterSlack = UserPreferences.hideSlackFilesInDataSourcesTree();
}
});
}
DataSourcesKnownAndSlackFilter() {
filterKnown = UserPreferences.hideKnownFilesInDataSourcesTree();
filterSlack = UserPreferences.hideSlackFilesInDataSourcesTree();
}
}

View File

@ -27,6 +27,7 @@ import java.util.Comparator;
import java.util.EnumSet;
import java.util.List;
import java.util.logging.Level;
import org.openide.nodes.Children;
import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
import org.openide.util.lookup.Lookups;
@ -56,7 +57,7 @@ public class DataSourcesNode extends DisplayableItemNode {
}
public DataSourcesNode(long dsObjId) {
super(new DataSourcesNodeChildren(dsObjId), Lookups.singleton(NAME));
super(Children.create(new DataSourcesNodeChildren(dsObjId), true), Lookups.singleton(NAME));
displayName = (dsObjId > 0) ? NbBundle.getMessage(DataSourcesNode.class, "DataSourcesNode.group_by_datasource.name") : NAME;
init();
}
@ -87,7 +88,7 @@ public class DataSourcesNode extends DisplayableItemNode {
}
public DataSourcesNodeChildren(long dsObjId) {
super();
super("ds_" + Long.toString(dsObjId));
this.currentKeys = new ArrayList<>();
this.datasourceObjId = dsObjId;
}
@ -97,25 +98,24 @@ public class DataSourcesNode extends DisplayableItemNode {
public void propertyChange(PropertyChangeEvent evt) {
String eventType = evt.getPropertyName();
if (eventType.equals(Case.Events.DATA_SOURCE_ADDED.toString())) {
reloadKeys();
refresh(true);
}
}
};
@Override
protected void addNotify() {
protected void onAdd() {
Case.addEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED), pcl);
reloadKeys();
}
@Override
protected void removeNotify() {
protected void onRemove() {
Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED), pcl);
currentKeys.clear();
setKeys(Collections.<Content>emptySet());
}
private void reloadKeys() {
@Override
protected List<Content> makeKeys() {
try {
if (datasourceObjId == 0) {
currentKeys = Case.getCurrentCaseThrows().getDataSources();
@ -135,20 +135,11 @@ public class DataSourcesNode extends DisplayableItemNode {
});
setKeys(currentKeys);
} catch (TskCoreException | NoCurrentCaseException | TskDataException ex) {
logger.log(Level.SEVERE, "Error getting data sources: {0}", ex.getMessage()); // NON-NLS
setKeys(Collections.<Content>emptySet());
}
}
/**
* Refresh all content keys This creates new nodes of keys have changed.
*/
public void refreshContentKeys() {
for (Content key : currentKeys) {
refreshKey(key);
}
return currentKeys;
}
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2018 Basis Technology Corp.
* Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -39,7 +39,6 @@ import org.openide.util.lookup.Lookups;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.CasePreferences;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.datamodel.AbstractFile;
@ -358,7 +357,7 @@ public class DeletedContent implements AutopsyVisitableItem {
}
}
static class DeletedContentChildren extends ChildFactory.Detachable<AbstractFile> {
static class DeletedContentChildren extends BaseChildFactory<AbstractFile> {
private final SleuthkitCase skCase;
private final DeletedContent.DeletedContentFilter filter;
@ -368,6 +367,7 @@ public class DeletedContent implements AutopsyVisitableItem {
private final long datasourceObjId;
DeletedContentChildren(DeletedContent.DeletedContentFilter filter, SleuthkitCase skCase, Observable o, long datasourceObjId) {
super(filter.getName(), new ViewsKnownAndSlackFilter<>());
this.skCase = skCase;
this.filter = filter;
this.notifier = o;
@ -376,6 +376,11 @@ public class DeletedContent implements AutopsyVisitableItem {
private final Observer observer = new DeletedContentChildrenObserver();
@Override
protected List<AbstractFile> makeKeys() {
return runFsQuery();
}
// Cause refresh of children if there are changes
private class DeletedContentChildrenObserver implements Observer {
@ -386,25 +391,19 @@ public class DeletedContent implements AutopsyVisitableItem {
}
@Override
protected void addNotify() {
protected void onAdd() {
if (notifier != null) {
notifier.addObserver(observer);
}
}
@Override
protected void removeNotify() {
protected void onRemove() {
if (notifier != null) {
notifier.deleteObserver(observer);
}
}
@Override
protected boolean createKeys(List<AbstractFile> list) {
list.addAll(runFsQuery());
return true;
}
static private String makeQuery(DeletedContent.DeletedContentFilter filter, long filteringDSObjId) {
String query = "";
switch (filter) {
@ -440,11 +439,6 @@ public class DeletedContent implements AutopsyVisitableItem {
}
if (UserPreferences.hideKnownFilesInViewsTree()) {
query += " AND (known != " + TskData.FileKnown.KNOWN.getFileKnownValue() //NON-NLS
+ " OR known IS NULL)"; //NON-NLS
}
if (Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true)) {
query += " AND data_source_obj_id = " + filteringDSObjId;
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2012-2018 Basis Technology Corp.
* Copyright 2012-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -42,7 +42,6 @@ import org.openide.util.lookup.Lookups;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.CasePreferences;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
@ -494,41 +493,52 @@ public class EmailExtracted implements AutopsyVisitableItem {
/**
* Node representing mail folder content (mail messages)
*/
private class MessageFactory extends ChildFactory<Long> implements Observer {
private class MessageFactory extends BaseChildFactory<BlackboardArtifact> implements Observer {
private final String accountName;
private final String folderName;
private MessageFactory(String accountName, String folderName) {
super();
super(accountName + "_" + folderName);
this.accountName = accountName;
this.folderName = folderName;
emailResults.addObserver(this);
}
@Override
protected boolean createKeys(List<Long> list) {
list.addAll(emailResults.getArtifactIds(accountName, folderName));
return true;
}
@Override
protected Node createNodeForKey(Long artifactId) {
if (skCase == null) {
return null;
}
try {
BlackboardArtifact artifact = skCase.getBlackboardArtifact(artifactId);
return new BlackboardArtifactNode(artifact);
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "Error creating mail messages nodes", ex); //NON-NLS
}
return null;
protected Node createNodeForKey(BlackboardArtifact art) {
return new BlackboardArtifactNode(art);
}
@Override
public void update(Observable o, Object arg) {
refresh(true);
}
@Override
protected List<BlackboardArtifact> makeKeys() {
List<BlackboardArtifact> keys = new ArrayList<>();
if (skCase != null) {
emailResults.getArtifactIds(accountName, folderName).forEach((id) -> {
try {
keys.add(skCase.getBlackboardArtifact(id));
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "Error getting mail messages keys", ex); //NON-NLS
}
});
}
return keys;
}
@Override
protected void onAdd() {
// No-op
}
@Override
protected void onRemove() {
// No-op
}
}
}

View File

@ -423,12 +423,12 @@ public class ExtractedContent implements AutopsyVisitableItem {
/**
* Creates children for a given artifact type
*/
private class ArtifactFactory extends ChildFactory.Detachable<BlackboardArtifact> {
private class ArtifactFactory extends BaseChildFactory<BlackboardArtifact> {
private BlackboardArtifact.Type type;
public ArtifactFactory(BlackboardArtifact.Type type) {
super();
super(type.getTypeName());
this.type = type;
}
@ -481,36 +481,34 @@ public class ExtractedContent implements AutopsyVisitableItem {
};
@Override
protected void addNotify() {
protected void onAdd() {
IngestManager.getInstance().addIngestJobEventListener(pcl);
IngestManager.getInstance().addIngestModuleEventListener(pcl);
}
@Override
protected void removeNotify() {
protected void onRemove() {
IngestManager.getInstance().removeIngestJobEventListener(pcl);
IngestManager.getInstance().removeIngestModuleEventListener(pcl);
}
@Override
protected boolean createKeys(List<BlackboardArtifact> list) {
if (skCase != null) {
try {
List<BlackboardArtifact> arts =
Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true) ?
blackboard.getArtifacts(type.getTypeID(), datasourceObjId) :
skCase.getBlackboardArtifacts(type.getTypeID());
list.addAll(arts);
} catch (TskException ex) {
Logger.getLogger(ArtifactFactory.class.getName()).log(Level.SEVERE, "Couldn't get blackboard artifacts from database", ex); //NON-NLS
}
}
return true;
}
@Override
protected Node createNodeForKey(BlackboardArtifact key) {
return new BlackboardArtifactNode(key);
}
@Override
protected List<BlackboardArtifact> makeKeys() {
if (skCase != null) {
try {
return Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true)
? blackboard.getArtifacts(type.getTypeID(), datasourceObjId)
: skCase.getBlackboardArtifacts(type.getTypeID());
} catch (TskException ex) {
Logger.getLogger(ArtifactFactory.class.getName()).log(Level.SEVERE, "Couldn't get blackboard artifacts from database", ex); //NON-NLS
}
}
return Collections.emptyList();
}
}
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013-2018 Basis Technology Corp.
* Copyright 2013-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -361,7 +361,7 @@ public class FileSize implements AutopsyVisitableItem {
/*
* Makes children, which are nodes for files of a given range
*/
static class FileSizeChildren extends ChildFactory.Detachable<AbstractFile> {
static class FileSizeChildren extends BaseChildFactory<AbstractFile> {
private final SleuthkitCase skCase;
private final FileSizeFilter filter;
@ -377,6 +377,7 @@ public class FileSize implements AutopsyVisitableItem {
* added to case
*/
FileSizeChildren(FileSizeFilter filter, SleuthkitCase skCase, Observable o, long dsObjId) {
super(filter.getName(), new ViewsKnownAndSlackFilter<>());
this.skCase = skCase;
this.filter = filter;
this.notifier = o;
@ -385,14 +386,14 @@ public class FileSize implements AutopsyVisitableItem {
}
@Override
protected void addNotify() {
protected void onAdd() {
if (notifier != null) {
notifier.addObserver(observer);
}
}
@Override
protected void removeNotify() {
protected void onRemove() {
if (notifier != null) {
notifier.deleteObserver(observer);
}
@ -400,6 +401,11 @@ public class FileSize implements AutopsyVisitableItem {
private final Observer observer = new FileSizeChildrenObserver();
@Override
protected List<AbstractFile> makeKeys() {
return runFsQuery();
}
// Cause refresh of children if there are changes
private class FileSizeChildrenObserver implements Observer {
@ -409,12 +415,6 @@ public class FileSize implements AutopsyVisitableItem {
}
}
@Override
protected boolean createKeys(List<AbstractFile> list) {
list.addAll(runFsQuery());
return true;
}
private static String makeQuery(FileSizeFilter filter, long filteringDSObjId) {
String query;
switch (filter) {
@ -436,17 +436,6 @@ public class FileSize implements AutopsyVisitableItem {
// Ignore unallocated block files.
query = query + " AND (type != " + TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS.getFileType() + ")"; //NON-NLS
// Hide known files if indicated in the user preferences.
if(UserPreferences.hideKnownFilesInViewsTree()) {
query += " AND (known != " + TskData.FileKnown.KNOWN.getFileKnownValue() //NON-NLS
+ " OR known IS NULL)"; //NON-NLS
}
// Hide slack files if indicated in the user preferences.
if(UserPreferences.hideSlackFilesInViewsTree()) {
query += " AND (type != " + TskData.TSK_DB_FILES_TYPE_ENUM.SLACK.getFileType() + ")"; //NON-NLS
}
// filter by datasource if indicated in case preferences
if (Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true)) {
query += " AND data_source_obj_id = " + filteringDSObjId;

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2018 Basis Technology Corp.
* Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.datamodel;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
@ -292,7 +293,7 @@ public final class FileTypesByExtension implements AutopsyVisitableItem {
* should refresh
*/
FileExtensionNode(FileTypesByExtension.SearchFilterInterface filter, SleuthkitCase skCase, FileTypesByExtObservable o) {
super(typesRoot, Children.create(new FileExtensionNodeChildren(filter, skCase, o), true),
super(typesRoot, Children.create(new FileExtensionNodeChildren(filter, skCase, o, filter.getDisplayName()), true),
Lookups.singleton(filter.getDisplayName()));
this.filter = filter;
super.setName(filter.getDisplayName());
@ -377,7 +378,7 @@ public final class FileTypesByExtension implements AutopsyVisitableItem {
/**
* Child node factory for a specific file type - does the database query.
*/
private class FileExtensionNodeChildren extends ChildFactory.Detachable<FileTypesKey> implements Observer {
private class FileExtensionNodeChildren extends BaseChildFactory<FileTypesKey> implements Observer {
private final SleuthkitCase skCase;
private final FileTypesByExtension.SearchFilterInterface filter;
@ -390,22 +391,22 @@ public final class FileTypesByExtension implements AutopsyVisitableItem {
* @param o Observable that will notify when there could be new
* data to display
*/
private FileExtensionNodeChildren(FileTypesByExtension.SearchFilterInterface filter, SleuthkitCase skCase, Observable o) {
super();
private FileExtensionNodeChildren(FileTypesByExtension.SearchFilterInterface filter, SleuthkitCase skCase, Observable o, String nodeName) {
super(nodeName, new ViewsKnownAndSlackFilter<>());
this.filter = filter;
this.skCase = skCase;
notifier = o;
}
@Override
protected void addNotify() {
protected void onAdd() {
if (notifier != null) {
notifier.addObserver(this);
}
}
@Override
protected void removeNotify() {
protected void onRemove() {
if (notifier != null) {
notifier.deleteObserver(this);
}
@ -417,19 +418,19 @@ public final class FileTypesByExtension implements AutopsyVisitableItem {
}
@Override
protected boolean createKeys(List<FileTypesKey> list) {
try {
list.addAll(skCase.findAllFilesWhere(createQuery(filter))
.stream().map(f -> new FileTypesKey(f)).collect(Collectors.toList()));
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Couldn't get search results", ex); //NON-NLS
}
return true;
protected Node createNodeForKey(FileTypesKey key) {
return key.accept(new FileTypes.FileNodeCreationVisitor());
}
@Override
protected Node createNodeForKey(FileTypesKey key) {
return key.accept(new FileTypes.FileNodeCreationVisitor());
protected List<FileTypesKey> makeKeys() {
try {
return skCase.findAllFilesWhere(createQuery(filter))
.stream().map(f -> new FileTypesKey(f)).collect(Collectors.toList());
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Couldn't get search results", ex); //NON-NLS
}
return Collections.emptyList();
}
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2018 Basis Technology Corp.
* Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -445,27 +445,16 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi
* files that match MimeType which is represented by this position in the
* tree.
*/
private class MediaSubTypeNodeChildren extends ChildFactory.Detachable<FileTypesKey> implements Observer {
private class MediaSubTypeNodeChildren extends BaseChildFactory<FileTypesKey> implements Observer {
private final String mimeType;
private MediaSubTypeNodeChildren(String mimeType) {
super();
super(mimeType, new ViewsKnownAndSlackFilter<>());
addObserver(this);
this.mimeType = mimeType;
}
@Override
protected boolean createKeys(List<FileTypesKey> list) {
try {
list.addAll(skCase.findAllFilesWhere(createBaseWhereExpr() + " AND mime_type = '" + mimeType + "'")
.stream().map(f -> new FileTypesKey(f)).collect(Collectors.toList())); //NON-NLS
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Couldn't get search results", ex); //NON-NLS
}
return true;
}
@Override
public void update(Observable o, Object arg) {
refresh(true);
@ -475,5 +464,26 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi
protected Node createNodeForKey(FileTypesKey key) {
return key.accept(new FileTypes.FileNodeCreationVisitor());
}
@Override
protected List<FileTypesKey> makeKeys() {
try {
return skCase.findAllFilesWhere(createBaseWhereExpr() + " AND mime_type = '" + mimeType + "'")
.stream().map(f -> new FileTypesKey(f)).collect(Collectors.toList()); //NON-NLS
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Couldn't get search results", ex); //NON-NLS
}
return Collections.emptyList();
}
@Override
protected void onAdd() {
// No-op
}
@Override
protected void onRemove() {
// No-op
}
}
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2018 Basis Technology Corp.
* Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -44,7 +44,6 @@ import org.openide.util.lookup.Lookups;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.CasePreferences;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
@ -378,60 +377,53 @@ public class HashsetHits implements AutopsyVisitableItem {
/**
* Creates the nodes for the hits in a given set.
*/
private class HitFactory extends ChildFactory.Detachable<Long> implements Observer {
private class HitFactory extends BaseChildFactory<BlackboardArtifact> implements Observer {
private String hashsetName;
private Map<Long, BlackboardArtifact> artifactHits = new HashMap<>();
private HitFactory(String hashsetName) {
super();
super(hashsetName);
this.hashsetName = hashsetName;
}
@Override
protected void addNotify() {
protected void onAdd() {
hashsetResults.addObserver(this);
}
@Override
protected void removeNotify() {
protected void onRemove() {
hashsetResults.deleteObserver(this);
}
@Override
protected boolean createKeys(List<Long> list) {
if (skCase == null) {
return true;
}
hashsetResults.getArtifactIds(hashsetName).forEach((id) -> {
try {
if (!artifactHits.containsKey(id)) {
BlackboardArtifact art = skCase.getBlackboardArtifact(id);
artifactHits.put(id, art);
}
} catch (TskException ex) {
logger.log(Level.SEVERE, "TSK Exception occurred", ex); //NON-NLS
}
});
// Adding all keys at once is more efficient than adding one at a
// time because Netbeans triggers internal processing each time an
// element is added to the list.
list.addAll(artifactHits.keySet());
return true;
}
@Override
protected Node createNodeForKey(Long id) {
BlackboardArtifact art = artifactHits.get(id);
return (null == art) ? null : new BlackboardArtifactNode(art);
protected Node createNodeForKey(BlackboardArtifact key) {
return new BlackboardArtifactNode(key);
}
@Override
public void update(Observable o, Object arg) {
refresh(true);
}
@Override
protected List<BlackboardArtifact> makeKeys() {
if (skCase != null) {
hashsetResults.getArtifactIds(hashsetName).forEach((id) -> {
try {
if (!artifactHits.containsKey(id)) {
BlackboardArtifact art = skCase.getBlackboardArtifact(id);
artifactHits.put(id, art);
}
} catch (TskException ex) {
logger.log(Level.SEVERE, "TSK Exception occurred", ex); //NON-NLS
}
});
return new ArrayList<>(artifactHits.values());
}
return Collections.emptyList();
}
}
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2018 Basis Technology Corp.
* Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -28,7 +28,6 @@ import java.util.EnumSet;
import java.util.List;
import java.util.logging.Level;
import javax.swing.Action;
import org.openide.nodes.Children;
import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages;
@ -47,6 +46,7 @@ import org.sleuthkit.datamodel.Image;
import org.sleuthkit.datamodel.SleuthkitCase.CaseDbQuery;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.VirtualDirectory;
import org.sleuthkit.autopsy.datamodel.BaseChildFactory.NoSuchEventBusException;
/**
* This class is used to represent the "Node" for the image. The children of
@ -224,18 +224,23 @@ public class ImageNode extends AbstractContentNode<Image> {
if (parent != null) {
// Is this a new carved file?
if (parent.getName().equals(VirtualDirectory.NAME_CARVED)) {
// Was this new carved file produced from this image?
if (parent.getParent().getId() == getContent().getId()) {
Children children = getChildren();
if (children != null) {
((ContentChildren) children).refreshChildren();
children.getNodesCount();
// Is this new carved file for this data source?
if (newContent.getDataSource().getId() == getContent().getDataSource().getId()) {
// Find the image (if any) associated with the new content and
// trigger a refresh if it matches the image wrapped by this node.
while ((parent = parent.getParent()) != null) {
if (parent.getId() == getContent().getId()) {
BaseChildFactory.post(getName(), new BaseChildFactory.RefreshKeysEvent());
break;
}
}
}
}
}
} catch (TskCoreException ex) {
// Do nothing.
} catch (NoSuchEventBusException ex) {
logger.log(Level.WARNING, "Failed to post key refresh event.", ex); // NON-NLS
}
} else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
if (evt.getNewValue() == null) {

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2018 Basis Technology Corp.
* Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -449,51 +449,57 @@ public class InterestingHits implements AutopsyVisitableItem {
}
}
private class HitFactory extends ChildFactory<Long> implements Observer {
private class HitFactory extends BaseChildFactory<BlackboardArtifact> implements Observer {
private final String setName;
private final String typeName;
private final Map<Long, BlackboardArtifact> artifactHits = new HashMap<>();
private HitFactory(String setName, String typeName) {
super();
super(typeName);
this.setName = setName;
this.typeName = typeName;
interestingResults.addObserver(this);
}
@Override
protected boolean createKeys(List<Long> list) {
protected List<BlackboardArtifact> makeKeys() {
if (skCase == null) {
return true;
}
interestingResults.getArtifactIds(setName, typeName).forEach((id) -> {
try {
if (!artifactHits.containsKey(id)) {
BlackboardArtifact art = skCase.getBlackboardArtifact(id);
artifactHits.put(id, art);
if (skCase != null) {
interestingResults.getArtifactIds(setName, typeName).forEach((id) -> {
try {
if (!artifactHits.containsKey(id)) {
BlackboardArtifact art = skCase.getBlackboardArtifact(id);
artifactHits.put(id, art);
}
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "TSK Exception occurred", ex); //NON-NLS
}
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "TSK Exception occurred", ex); //NON-NLS
}
});
});
list.addAll(artifactHits.keySet());
return true;
return new ArrayList<>(artifactHits.values());
}
return Collections.emptyList();
}
@Override
protected Node createNodeForKey(Long l) {
BlackboardArtifact art = artifactHits.get(l);
return (null == art) ? null : new BlackboardArtifactNode(art);
protected Node createNodeForKey(BlackboardArtifact art) {
return new BlackboardArtifactNode(art);
}
@Override
public void update(Observable o, Object arg) {
refresh(true);
}
@Override
protected void onAdd() {
// No-op
}
@Override
protected void onRemove() {
// No-op
}
}
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2018 Basis Technology Corp.
* Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -25,6 +25,7 @@ import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
@ -34,7 +35,6 @@ import java.util.Observable;
import java.util.Observer;
import java.util.Set;
import java.util.logging.Level;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.openide.nodes.ChildFactory;
import org.openide.nodes.Children;
@ -46,7 +46,6 @@ import org.openide.util.lookup.Lookups;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.CasePreferences;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.coreutils.Logger;
import static org.sleuthkit.autopsy.datamodel.Bundle.*;
import org.sleuthkit.autopsy.ingest.IngestManager;
@ -85,7 +84,6 @@ public class KeywordHits implements AutopsyVisitableItem {
*/
private static final String DEFAULT_INSTANCE_NAME = "DEFAULT_INSTANCE_NAME";
/**
* query attributes table for the ones that we need for the tree
*/
@ -109,7 +107,7 @@ public class KeywordHits implements AutopsyVisitableItem {
/**
* Constructor
*
* @param skCase Case DB
* @param skCase Case DB
*/
KeywordHits(SleuthkitCase skCase) {
this(skCase, 0);
@ -118,7 +116,7 @@ public class KeywordHits implements AutopsyVisitableItem {
/**
* Constructor
*
* @param skCase Case DB
* @param skCase Case DB
* @param objId Object id of the data source
*
*/
@ -324,7 +322,7 @@ public class KeywordHits implements AutopsyVisitableItem {
String queryStr = KEYWORD_HIT_ATTRIBUTES_QUERY;
if (Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true)) {
queryStr += " AND blackboard_artifacts.data_source_obj_id = " + datasourceObjId;
queryStr += " AND blackboard_artifacts.data_source_obj_id = " + datasourceObjId;
}
try (CaseDbQuery dbQuery = skCase.executeQuery(queryStr)) {
@ -510,9 +508,11 @@ public class KeywordHits implements AutopsyVisitableItem {
}
private abstract class KWHitsNodeBase extends DisplayableItemNode implements Observer {
private String displayName;
private KWHitsNodeBase(Children children, Lookup lookup) {
private KWHitsNodeBase(Children children, Lookup lookup, String displayName) {
super(children, lookup);
this.displayName = displayName;
}
private KWHitsNodeBase(Children children) {
@ -530,7 +530,7 @@ public class KeywordHits implements AutopsyVisitableItem {
}
final void updateDisplayName() {
super.setDisplayName(getName() + " (" + countTotalDescendants() + ")");
super.setDisplayName(displayName + " (" + countTotalDescendants() + ")");
}
abstract int countTotalDescendants();
@ -545,7 +545,7 @@ public class KeywordHits implements AutopsyVisitableItem {
private final String listName;
private ListNode(String listName) {
super(Children.create(new TermFactory(listName), true), Lookups.singleton(listName));
super(Children.create(new TermFactory(listName), true), Lookups.singleton(listName), listName);
super.setName(listName);
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/keyword_hits.png"); //NON-NLS
this.listName = listName;
@ -631,6 +631,24 @@ public class KeywordHits implements AutopsyVisitableItem {
}
}
/**
* Create a ChildFactory object for the given set name and keyword.
*
* The type of ChildFactory we create is based on whether the node
* represents a regular expression keyword search or not. For regular
* expression keyword searches there will be an extra layer in the tree that
* represents each of the individual terms found by the regular expression.
* E.g., for an email regular expression search there will be a node in the
* tree for every email address hit.
*/
ChildFactory<?> createChildFactory(String setName, String keyword) {
if (isOnlyDefaultInstance(keywordResults.getKeywordInstances(setName, keyword))) {
return new HitsFactory(setName, keyword, DEFAULT_INSTANCE_NAME);
} else {
return new RegExpInstancesFactory(setName, keyword);
}
}
/**
* Represents the search term or regexp that user searched for
*/
@ -640,8 +658,16 @@ public class KeywordHits implements AutopsyVisitableItem {
private final String keyword;
private TermNode(String setName, String keyword) {
super(Children.create(new RegExpInstancesFactory(setName, keyword), true), Lookups.singleton(keyword));
super.setName(keyword);
super(Children.create(createChildFactory(setName, keyword), true), Lookups.singleton(keyword), keyword);
/**
* We differentiate between the programmatic name and the display
* name. The programmatic name is used to create an association with
* an event bus and must be the same as the node name passed by our
* ChildFactory to it's parent constructor. See the HitsFactory
* constructor for an example.
*/
super.setName(setName + "_" + keyword);
this.setName = setName;
this.keyword = keyword;
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/keyword_hits.png"); //NON-NLS
@ -694,45 +720,11 @@ public class KeywordHits implements AutopsyVisitableItem {
}
}
/**
* Allows us to pass in either longs or strings as they keys for different
* types of nodes at the same level. Probably a better way to do this, but
* it works.
*/
private class RegExpInstanceKey {
private final boolean isRegExp;
private String strKey;
private Long longKey;
RegExpInstanceKey(String key) {
isRegExp = true;
strKey = key;
}
RegExpInstanceKey(Long key) {
isRegExp = false;
longKey = key;
}
boolean isRegExp() {
return isRegExp;
}
Long getIdKey() {
return longKey;
}
String getRegExpKey() {
return strKey;
}
}
/**
* Creates the nodes for a given regexp that represent the specific terms
* that were found
*/
private class RegExpInstancesFactory extends DetachableObserverChildFactory<RegExpInstanceKey> {
private class RegExpInstancesFactory extends DetachableObserverChildFactory<String> {
private final String keyword;
private final String setName;
@ -744,33 +736,15 @@ public class KeywordHits implements AutopsyVisitableItem {
}
@Override
protected boolean createKeys(List<RegExpInstanceKey> list) {
List<String> instances = keywordResults.getKeywordInstances(setName, keyword);
// The keys are different depending on what we are displaying.
// regexp get another layer to show instances.
// Exact/substring matches don't.
if (isOnlyDefaultInstance(instances)) {
list.addAll(keywordResults.getArtifactIds(setName, keyword, DEFAULT_INSTANCE_NAME).stream()
.map(RegExpInstanceKey::new)
.collect(Collectors.toList()));
} else {
list.addAll(instances.stream()
.map(RegExpInstanceKey::new)
.collect(Collectors.toList()));
}
protected boolean createKeys(List<String> list) {
list.addAll(keywordResults.getKeywordInstances(setName, keyword));
return true;
}
@Override
protected Node createNodeForKey(RegExpInstanceKey key) {
if (key.isRegExp()) {
return new RegExpInstanceNode(setName, keyword, key.getRegExpKey());
} else {
// if it isn't a regexp, then skip the 'instance' layer of the tree
return createBlackboardArtifactNode(key.getIdKey());
}
protected Node createNodeForKey(String key) {
return new RegExpInstanceNode(setName, keyword, key);
}
}
/**
@ -783,8 +757,16 @@ public class KeywordHits implements AutopsyVisitableItem {
private final String instance;
private RegExpInstanceNode(String setName, String keyword, String instance) {
super(Children.create(new HitsFactory(setName, keyword, instance), true), Lookups.singleton(instance));
super.setName(instance); //the instance represents the name of the keyword hit at this point as the keyword is the regex
super(Children.create(new HitsFactory(setName, keyword, instance), true), Lookups.singleton(instance), instance);
/**
* We differentiate between the programmatic name and the display
* name. The programmatic name is used to create an association with
* an event bus and must be the same as the node name passed by our
* ChildFactory to it's parent constructor. See the HitsFactory
* constructor for an example.
*/
super.setName(setName + "_" + keyword + "_" + instance);
this.setName = setName;
this.keyword = keyword;
this.instance = instance;
@ -837,7 +819,7 @@ public class KeywordHits implements AutopsyVisitableItem {
/**
* Create a blackboard node for the given Keyword Hit artifact
*
* @param artifactId
* @param art
*
* @return Node or null on error
*/
@ -850,81 +832,110 @@ public class KeywordHits implements AutopsyVisitableItem {
"KeywordHits.createNodeForKey.chgTime.name=ChangeTime",
"KeywordHits.createNodeForKey.chgTime.displayName=Change Time",
"KeywordHits.createNodeForKey.chgTime.desc=Change Time"})
private BlackboardArtifactNode createBlackboardArtifactNode(Long artifactId) {
private BlackboardArtifactNode createBlackboardArtifactNode(BlackboardArtifact art) {
if (skCase == null) {
return null;
}
try {
BlackboardArtifact art = skCase.getBlackboardArtifact(artifactId);
BlackboardArtifactNode n = new BlackboardArtifactNode(art);
// The associated file should be available through the Lookup that
// gets created when the BlackboardArtifactNode is constructed.
AbstractFile file = n.getLookup().lookup(AbstractFile.class);
if (file == null) {
try {
file = skCase.getAbstractFileById(art.getObjectID());
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "TskCoreException while constructing BlackboardArtifact Node from KeywordHitsKeywordChildren", ex); //NON-NLS
return n;
}
}
/*
* It is possible to get a keyword hit on artifacts generated for
* the underlying image in which case MAC times are not
* available/applicable/useful.
*/
if (file == null) {
BlackboardArtifactNode n = new BlackboardArtifactNode(art); //NON-NLS
// The associated file should be available through the Lookup that
// gets created when the BlackboardArtifactNode is constructed.
AbstractFile file = n.getLookup().lookup(AbstractFile.class);
if (file == null) {
try {
file = skCase.getAbstractFileById(art.getObjectID());
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "TskCoreException while constructing BlackboardArtifact Node from KeywordHitsKeywordChildren", ex); //NON-NLS
return n;
}
n.addNodeProperty(new NodeProperty<>(
KeywordHits_createNodeForKey_modTime_name(),
KeywordHits_createNodeForKey_modTime_displayName(),
KeywordHits_createNodeForKey_modTime_desc(),
ContentUtils.getStringTime(file.getMtime(), file)));
n.addNodeProperty(new NodeProperty<>(
KeywordHits_createNodeForKey_accessTime_name(),
KeywordHits_createNodeForKey_accessTime_displayName(),
KeywordHits_createNodeForKey_accessTime_desc(),
ContentUtils.getStringTime(file.getAtime(), file)));
n.addNodeProperty(new NodeProperty<>(
KeywordHits_createNodeForKey_chgTime_name(),
KeywordHits_createNodeForKey_chgTime_displayName(),
KeywordHits_createNodeForKey_chgTime_desc(),
ContentUtils.getStringTime(file.getCtime(), file)));
return n;
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "TSK Exception occurred", ex); //NON-NLS
}
return null;
/*
* It is possible to get a keyword hit on artifacts generated for the
* underlying image in which case MAC times are not
* available/applicable/useful.
*/
if (file == null) {
return n;
}
n.addNodeProperty(new NodeProperty<>(
KeywordHits_createNodeForKey_modTime_name(),
KeywordHits_createNodeForKey_modTime_displayName(),
KeywordHits_createNodeForKey_modTime_desc(),
ContentUtils.getStringTime(file.getMtime(), file)));
n.addNodeProperty(new NodeProperty<>(
KeywordHits_createNodeForKey_accessTime_name(),
KeywordHits_createNodeForKey_accessTime_displayName(),
KeywordHits_createNodeForKey_accessTime_desc(),
ContentUtils.getStringTime(file.getAtime(), file)));
n.addNodeProperty(new NodeProperty<>(
KeywordHits_createNodeForKey_chgTime_name(),
KeywordHits_createNodeForKey_chgTime_displayName(),
KeywordHits_createNodeForKey_chgTime_desc(),
ContentUtils.getStringTime(file.getCtime(), file)));
return n;
}
/**
* Creates nodes for individual files that had hits
*/
private class HitsFactory extends DetachableObserverChildFactory<Long> {
private class HitsFactory extends BaseChildFactory<BlackboardArtifact> implements Observer {
private final String keyword;
private final String setName;
private final String instance;
private final Map<Long, BlackboardArtifact> artifactHits = new HashMap<>();
private HitsFactory(String setName, String keyword, String instance) {
super();
/**
* The node name passed to the parent constructor will consist of
* the set name, keyword and optionally the instance name (in the
* case of regular expression hits. This name must match the name
* set in the TermNode or RegExpInstanceNode constructors.
*/
super(setName + "_" + keyword + (DEFAULT_INSTANCE_NAME.equals(instance) ? "" : "_" + instance));
this.setName = setName;
this.keyword = keyword;
this.instance = instance;
}
@Override
protected boolean createKeys(List<Long> list) {
list.addAll(keywordResults.getArtifactIds(setName, keyword, instance));
return true;
protected List<BlackboardArtifact> makeKeys() {
if (skCase != null) {
keywordResults.getArtifactIds(setName, keyword, instance).forEach((id) -> {
try {
if (!artifactHits.containsKey(id)) {
BlackboardArtifact art = skCase.getBlackboardArtifact(id);
artifactHits.put(id, art);
}
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "TSK Exception occurred", ex); //NON-NLS
}
});
return new ArrayList<>(artifactHits.values());
}
return Collections.emptyList();
}
@Override
protected Node createNodeForKey(Long artifactId) {
return createBlackboardArtifactNode(artifactId);
protected Node createNodeForKey(BlackboardArtifact art) {
return createBlackboardArtifactNode(art);
}
@Override
protected void onAdd() {
keywordResults.addObserver(this);
}
@Override
protected void onRemove() {
keywordResults.deleteObserver(this);
}
@Override
public void update(Observable o, Object arg) {
refresh(true);
}
}
}

View File

@ -0,0 +1,53 @@
/*
* 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.datamodel;
import java.util.function.Predicate;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.TskData;
/**
* Predicate that can be used to filter known and/or slack files from
* Content collections based on user preferences.
*/
abstract class KnownAndSlackFilterBase<T extends Content> implements Predicate<T> {
protected static boolean filterKnown;
protected static boolean filterSlack;
@Override
public boolean test(T t) {
AbstractFile af = null;
if (t instanceof AbstractFile) {
af = (AbstractFile) t;
}
if (af != null) {
if (af.getKnown() == TskData.FileKnown.KNOWN && filterKnown) {
return false;
}
if (af.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK) && filterSlack) {
return false;
}
}
return true;
}
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2017 Basis Technology Corp.
* Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -20,14 +20,20 @@ package org.sleuthkit.autopsy.datamodel;
import java.util.Collection;
import java.util.Collections;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.datamodel.accounts.Accounts;
/**
* Children implementation for the root node of a ContentNode tree. Accepts a
* list of root Content objects for the tree.
*/
public class RootContentChildren extends AbstractContentChildren<Object> {
public class RootContentChildren extends Children.Keys<Object> {
private final Collection<? extends Object> contentKeys;
private final CreateAutopsyNodeVisitor createAutopsyNodeVisitor = new CreateAutopsyNodeVisitor();
/**
* @param contentKeys root Content objects for the Node tree
@ -56,4 +62,120 @@ public class RootContentChildren extends AbstractContentChildren<Object> {
public void refreshContentKeys() {
contentKeys.forEach(this::refreshKey);
}
@Override
protected Node[] createNodes(Object key) {
if (key instanceof AutopsyVisitableItem) {
return new Node[] {((AutopsyVisitableItem)key).accept(createAutopsyNodeVisitor)};
} else {
return null;
}
}
/**
* Gets a DisplayableItemNode for use as a subtree root node for the Autopsy
* tree view from each type of AutopsyVisitableItem visited. There are
* AutopsyVisitableItems for the Data Sources, Views, Results, and Reports
* subtrees, and for the subtrees of Results (e.g., Extracted Content, Hash
* Set Hits, etc.).
*/
static class CreateAutopsyNodeVisitor extends AutopsyItemVisitor.Default<AbstractNode> {
@Override
public ExtractedContent.RootNode visit(ExtractedContent ec) {
return ec.new RootNode(ec.getSleuthkitCase());
}
@Override
public AbstractNode visit(FileTypesByExtension sf) {
return sf.new FileTypesByExtNode(sf.getSleuthkitCase(), null);
}
@Override
public AbstractNode visit(RecentFiles rf) {
return new RecentFilesNode(rf.getSleuthkitCase());
}
@Override
public AbstractNode visit(DeletedContent dc) {
return new DeletedContent.DeletedContentsNode(dc.getSleuthkitCase(), dc.filteringDataSourceObjId());
}
@Override
public AbstractNode visit(FileSize dc) {
return new FileSize.FileSizeRootNode(dc.getSleuthkitCase(), dc.filteringDataSourceObjId());
}
@Override
public AbstractNode visit(KeywordHits kh) {
return kh.new RootNode();
}
@Override
public AbstractNode visit(HashsetHits hh) {
return hh.new RootNode();
}
@Override
public AbstractNode visit(InterestingHits ih) {
return ih.new RootNode();
}
@Override
public AbstractNode visit(EmailExtracted ee) {
return ee.new RootNode();
}
@Override
public AbstractNode visit(Tags tagsNodeKey) {
return tagsNodeKey.new RootNode(tagsNodeKey.filteringDataSourceObjId());
}
@Override
public AbstractNode visit(DataSources i) {
return new DataSourcesNode(i.filteringDataSourceObjId());
}
@Override
public AbstractNode visit(DataSourceGrouping datasourceGrouping) {
return new DataSourceGroupingNode(datasourceGrouping.getDataSource());
}
@Override
public AbstractNode visit(Views v) {
return new ViewsNode(v.getSleuthkitCase(), v.filteringDataSourceObjId());
}
@Override
public AbstractNode visit(Results results) {
return new ResultsNode(results.getSleuthkitCase(), results.filteringDataSourceObjId() );
}
@Override
public AbstractNode visit(FileTypes ft) {
return ft.new FileTypesNode();
}
@Override
public AbstractNode visit(Reports reportsItem) {
return new Reports.ReportsListNode();
}
@Override
public AbstractNode visit(Accounts accountsItem) {
return accountsItem.new AccountsRootNode();
}
@Override
protected AbstractNode defaultVisit(AutopsyVisitableItem di) {
throw new UnsupportedOperationException(
NbBundle.getMessage(this.getClass(),
"AbstractContentChildren.createAutopsyNodeVisitor.exception.noNodeMsg"));
}
@Override
public AbstractNode visit(FileTypesByMimeType ftByMimeTypeItem) {
return ftByMimeTypeItem.new ByMimeTypeNode();
}
}
}

View File

@ -0,0 +1,52 @@
/*
* 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.datamodel;
import java.util.prefs.PreferenceChangeEvent;
import org.sleuthkit.autopsy.core.UserPreferences;
import static org.sleuthkit.autopsy.datamodel.KnownAndSlackFilterBase.filterKnown;
import static org.sleuthkit.autopsy.datamodel.KnownAndSlackFilterBase.filterSlack;
import org.sleuthkit.datamodel.Content;
/**
* Known and Slack filter for Views section of the tree.
*
* @param <T>
*/
class ViewsKnownAndSlackFilter<T extends Content> extends KnownAndSlackFilterBase<T> {
static {
/**
* Watch for user preference changes and update variables inherited from
* our parent. The actual filtering is provided by our parent class.
*/
UserPreferences.addChangeListener((PreferenceChangeEvent evt) -> {
if (evt.getKey().equals(UserPreferences.HIDE_KNOWN_FILES_IN_VIEWS_TREE)) {
filterKnown = UserPreferences.hideKnownFilesInViewsTree();
} else if (evt.getKey().equals(UserPreferences.HIDE_SLACK_FILES_IN_VIEWS_TREE)) {
filterSlack = UserPreferences.hideSlackFilesInViewsTree();
}
});
}
ViewsKnownAndSlackFilter() {
filterKnown = UserPreferences.hideKnownFilesInViewsTree();
filterSlack = UserPreferences.hideSlackFilesInViewsTree();
}
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2014 Basis Technology Corp.
* Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -23,11 +23,13 @@ import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.logging.Level;
import javax.swing.Action;
import org.openide.nodes.Children;
import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.BaseChildFactory.NoSuchEventBusException;
import org.sleuthkit.autopsy.directorytree.ExplorerNodeActionVisitor;
import org.sleuthkit.autopsy.directorytree.NewWindowViewAction;
import org.sleuthkit.autopsy.ingest.IngestManager;
@ -43,6 +45,7 @@ import org.sleuthkit.autopsy.directorytree.FileSystemDetailsAction;
* root directory of a file system
*/
public class VolumeNode extends AbstractContentNode<Volume> {
private static final Logger logger = Logger.getLogger(VolumeNode.class.getName());
/**
* Helper so that the display name and the name used in building the path
@ -105,18 +108,23 @@ public class VolumeNode extends AbstractContentNode<Volume> {
if (parent != null) {
// Is this a new carved file?
if (parent.getName().equals(VirtualDirectory.NAME_CARVED)) {
// Was this new carved file produced from this volume?
if (parent.getParent().getId() == getContent().getId()) {
Children children = getChildren();
if (children != null) {
((ContentChildren) children).refreshChildren();
children.getNodesCount();
// Is this new carved file for this data source?
if (newContent.getDataSource().getId() == getContent().getDataSource().getId()) {
// Find the volume (if any) associated with the new content and
// trigger a refresh if it matches the volume wrapped by this node.
while ((parent = parent.getParent()) != null) {
if (parent.getId() == getContent().getId()) {
BaseChildFactory.post(getName(), new BaseChildFactory.RefreshKeysEvent());
break;
}
}
}
}
}
} catch (TskCoreException ex) {
// Do nothing.
} catch (NoSuchEventBusException ex) {
logger.log(Level.WARNING, eventType, ex);
}
} else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
if (evt.getNewValue() == null) {

View File

@ -26,8 +26,6 @@ import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.logging.Level;
import java.util.prefs.PreferenceChangeEvent;
import java.util.prefs.PreferenceChangeListener;
import javax.swing.AbstractAction;
import javax.swing.Action;
import org.openide.explorer.ExplorerManager;
@ -41,7 +39,6 @@ import org.sleuthkit.autopsy.actions.AddBlackboardArtifactTagAction;
import org.sleuthkit.autopsy.actions.AddContentTagAction;
import org.sleuthkit.autopsy.actions.DeleteFileBlackboardArtifactTagAction;
import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction;
import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.AbstractFilePropertyType;
@ -80,7 +77,6 @@ import org.sleuthkit.datamodel.LayoutFile;
import org.sleuthkit.datamodel.LocalFile;
import org.sleuthkit.datamodel.LocalDirectory;
import org.sleuthkit.datamodel.SlackFile;
import org.sleuthkit.datamodel.TskData;
import org.sleuthkit.datamodel.TskException;
import org.sleuthkit.datamodel.VirtualDirectory;
import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
@ -96,33 +92,6 @@ public class DataResultFilterNode extends FilterNode {
private static final Logger LOGGER = Logger.getLogger(DataResultFilterNode.class.getName());
private static boolean filterKnownFromDataSources = UserPreferences.hideKnownFilesInDataSourcesTree();
private static boolean filterKnownFromViews = UserPreferences.hideKnownFilesInViewsTree();
private static boolean filterSlackFromDataSources = UserPreferences.hideSlackFilesInDataSourcesTree();
private static boolean filterSlackFromViews = UserPreferences.hideSlackFilesInViewsTree();
static {
UserPreferences.addChangeListener(new PreferenceChangeListener() {
@Override
public void preferenceChange(PreferenceChangeEvent evt) {
switch (evt.getKey()) {
case UserPreferences.HIDE_KNOWN_FILES_IN_DATA_SRCS_TREE:
filterKnownFromDataSources = UserPreferences.hideKnownFilesInDataSourcesTree();
break;
case UserPreferences.HIDE_KNOWN_FILES_IN_VIEWS_TREE:
filterKnownFromViews = UserPreferences.hideKnownFilesInViewsTree();
break;
case UserPreferences.HIDE_SLACK_FILES_IN_DATA_SRCS_TREE:
filterSlackFromDataSources = UserPreferences.hideSlackFilesInDataSourcesTree();
break;
case UserPreferences.HIDE_SLACK_FILES_IN_VIEWS_TREE:
filterSlackFromViews = UserPreferences.hideSlackFilesInViewsTree();
break;
}
}
});
}
static private final DisplayableItemNodeVisitor<List<Action>> getActionsDIV = new GetPopupActionsDisplayableItemNodeVisitor();
private final DisplayableItemNodeVisitor<AbstractAction> getPreferredActionsDIV = new GetPreferredActionsDisplayableItemNodeVisitor();
@ -155,24 +124,6 @@ public class DataResultFilterNode extends FilterNode {
this.sourceEm = em;
}
/**
* Constructs a node used to wrap another node before passing it to the
* result viewers. The wrapper node defines the actions associated with the
* wrapped node and may filter out some of its children.
*
* @param node The node to wrap.
* @param em The ExplorerManager for the component that is creating
* the node.
* @param filterKnown Whether or not to filter out children that represent
* known files.
* @param filterSlack Whether or not to filter out children that represent
* virtual slack space files.
*/
private DataResultFilterNode(Node node, ExplorerManager em, boolean filterKnown, boolean filterSlack) {
super(node, new DataResultFilterChildren(node, em, filterKnown, filterSlack));
this.sourceEm = em;
}
/**
* Right click action for the nodes that we want to pass to the directory
* table and the output view.
@ -294,10 +245,7 @@ public class DataResultFilterNode extends FilterNode {
private static class DataResultFilterChildren extends FilterNode.Children {
private final ExplorerManager sourceEm;
private boolean filterKnown;
private boolean filterSlack;
private boolean filterArtifacts; // display message artifacts in the DataSource subtree
private final boolean filterArtifacts; // display message artifacts in the DataSource subtree
/**
* the constructor
@ -305,46 +253,13 @@ public class DataResultFilterNode extends FilterNode {
private DataResultFilterChildren(Node arg, ExplorerManager sourceEm) {
super(arg);
this.filterArtifacts = false;
switch (SelectionContext.getSelectionContext(arg)) {
case DATA_SOURCES:
filterSlack = filterSlackFromDataSources;
filterKnown = filterKnownFromDataSources;
filterArtifacts = true;
break;
case VIEWS:
filterSlack = filterSlackFromViews;
filterKnown = filterKnownFromViews;
break;
default:
filterSlack = false;
filterKnown = false;
break;
}
this.sourceEm = sourceEm;
}
filterArtifacts = SelectionContext.getSelectionContext(arg).equals(SelectionContext.DATA_SOURCES);
private DataResultFilterChildren(Node arg, ExplorerManager sourceEm, boolean filterKnown, boolean filterSlack) {
super(arg);
this.filterKnown = filterKnown;
this.filterSlack = filterSlack;
this.sourceEm = sourceEm;
}
@Override
protected Node[] createNodes(Node key) {
AbstractFile file = key.getLookup().lookup(AbstractFile.class);
if (file != null) {
if (filterKnown && (file.getKnown() == TskData.FileKnown.KNOWN)) {
// Filter out child nodes that represent known files
return new Node[]{};
}
if (filterSlack && file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK)) {
// Filter out child nodes that represent slack files
return new Node[]{};
}
}
// filter out all non-message artifacts, if displaying the results from the Data Source tree
BlackboardArtifact art = key.getLookup().lookup(BlackboardArtifact.class);
if (art != null
@ -354,9 +269,8 @@ public class DataResultFilterNode extends FilterNode {
return new Node[]{};
}
return new Node[]{new DataResultFilterNode(key, sourceEm, filterKnown, filterSlack)};
return new Node[]{new DataResultFilterNode(key, sourceEm)};
}
}
@NbBundle.Messages("DataResultFilterNode.viewSourceArtifact.text=View Source Result")

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011 Basis Technology Corp.
* Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -22,7 +22,7 @@ import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import org.sleuthkit.autopsy.corecomponents.DataContentTopComponent;
import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode;
import org.sleuthkit.autopsy.datamodel.RootContentChildren;
import org.sleuthkit.autopsy.datamodel.CreateSleuthkitNodeVisitor;
import org.sleuthkit.datamodel.Content;
/**
@ -30,7 +30,7 @@ import org.sleuthkit.datamodel.Content;
*/
class ViewAssociatedContentAction extends AbstractAction {
private Content content;
private final Content content;
public ViewAssociatedContentAction(String title, BlackboardArtifactNode node) {
super(title);
@ -39,6 +39,6 @@ class ViewAssociatedContentAction extends AbstractAction {
@Override
public void actionPerformed(ActionEvent e) {
DataContentTopComponent.getDefault().setNode(content.accept(new RootContentChildren.CreateSleuthkitNodeVisitor()));
DataContentTopComponent.getDefault().setNode(content.accept(new CreateSleuthkitNodeVisitor()));
}
}

View File

@ -22,8 +22,10 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -33,13 +35,11 @@ import org.apache.commons.io.IOUtils;
import org.apache.poi.hwpf.usermodel.Picture;
import org.apache.poi.hslf.usermodel.HSLFPictureData;
import org.apache.poi.hslf.usermodel.HSLFSlideShow;
import org.apache.poi.hssf.record.RecordInputStream.LeftoverDataException;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.model.PicturesTable;
import org.apache.poi.sl.usermodel.PictureData.PictureType;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.util.RecordFormatException;
import org.apache.tika.config.TikaConfig;
import org.apache.tika.detect.Detector;
import org.apache.tika.exception.TikaException;
@ -72,13 +72,13 @@ import org.xml.sax.SAXException;
/**
* Extracts embedded content (e.g. images, audio, video) from Microsoft Office
* documents (both original and OOXML forms).
* documents (both original and OOXML forms) and PDF documents.
*/
class MSOfficeEmbeddedContentExtractor {
class DocumentEmbeddedContentExtractor {
private final FileManager fileManager;
private final IngestServices services;
private static final Logger LOGGER = Logger.getLogger(MSOfficeEmbeddedContentExtractor.class.getName());
private static final Logger LOGGER = Logger.getLogger(DocumentEmbeddedContentExtractor.class.getName());
private final IngestJobContext context;
private String parentFileName;
private final String UNKNOWN_IMAGE_NAME_PREFIX = "image_"; //NON-NLS
@ -101,7 +101,8 @@ class MSOfficeEmbeddedContentExtractor {
PPT("application/vnd.ms-powerpoint"), //NON-NLS
PPTX("application/vnd.openxmlformats-officedocument.presentationml.presentation"), //NON-NLS
XLS("application/vnd.ms-excel"), //NON-NLS
XLSX("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); //NON-NLS
XLSX("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"), //NON-NLS
PDF("application/pdf"); //NON-NLS
private final String mimeType;
@ -116,7 +117,7 @@ class MSOfficeEmbeddedContentExtractor {
}
private SupportedExtractionFormats abstractFileExtractionFormat;
MSOfficeEmbeddedContentExtractor(IngestJobContext context, FileTypeDetector fileTypeDetector, String moduleDirRelative, String moduleDirAbsolute) throws NoCurrentCaseException {
DocumentEmbeddedContentExtractor(IngestJobContext context, FileTypeDetector fileTypeDetector, String moduleDirRelative, String moduleDirAbsolute) throws NoCurrentCaseException {
this.fileManager = Case.getCurrentCaseThrows().getServices().getFileManager();
this.services = IngestServices.getInstance();
@ -190,6 +191,9 @@ class MSOfficeEmbeddedContentExtractor {
case XLS:
listOfExtractedImages = extractImagesFromXls(abstractFile);
break;
case PDF:
listOfExtractedImages = extractEmbeddedContentFromPDF(abstractFile);
break;
default:
break;
}
@ -471,6 +475,38 @@ class MSOfficeEmbeddedContentExtractor {
}
/**
* Extracts embedded attachments from PDF files.
*
* @param abstractFile Input PDF file
* @return List of extracted files to be made into derived file instances.
*/
private List<ExtractedFile> extractEmbeddedContentFromPDF(AbstractFile abstractFile) {
PDFAttachmentExtractor pdfExtractor = new PDFAttachmentExtractor(parser);
try {
Path outputDirectory = Paths.get(getOutputFolderPath(parentFileName));
//Get map of attachment name -> location disk.
Map<String, Path> extractedAttachments = pdfExtractor.extract(
new ReadContentInputStream(abstractFile), abstractFile.getId(),
outputDirectory);
//Convert output to hook into the existing logic for creating derived files
List<ExtractedFile> extractedFiles = new ArrayList<>();
extractedAttachments.entrySet().forEach((pathEntry) -> {
String fileName = pathEntry.getKey();
Path writeLocation = pathEntry.getValue();
extractedFiles.add(new ExtractedFile(fileName,
getFileRelativePath(writeLocation.getFileName().toString()),
writeLocation.toFile().length()));
});
return extractedFiles;
} catch (IOException | SAXException | TikaException ex) {
LOGGER.log(Level.WARNING, "Error attempting to extract attachments from PDFs", ex); //NON-NLS
}
return Collections.emptyList();
}
/**
* Writes image to the module output location.
*

View File

@ -50,7 +50,7 @@ public final class EmbeddedFileExtractorIngestModule extends FileIngestModuleAda
//Outer concurrent hashmap with keys of JobID, inner concurrentHashmap with keys of objectID
private static final ConcurrentHashMap<Long, ConcurrentHashMap<Long, Archive>> mapOfDepthTrees = new ConcurrentHashMap<>();
private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter();
private MSOfficeEmbeddedContentExtractor officeExtractor;
private DocumentEmbeddedContentExtractor documentExtractor;
private SevenZipExtractor archiveExtractor;
private FileTypeDetector fileTypeDetector;
private long jobId;
@ -115,10 +115,10 @@ public final class EmbeddedFileExtractorIngestModule extends FileIngestModuleAda
}
/*
* Construct an embedded content extractor for processing Microsoft
* Office documents.
* Office documents and PDF documents.
*/
try {
this.officeExtractor = new MSOfficeEmbeddedContentExtractor(context, fileTypeDetector, moduleDirRelative, moduleDirAbsolute);
this.documentExtractor = new DocumentEmbeddedContentExtractor(context, fileTypeDetector, moduleDirRelative, moduleDirAbsolute);
} catch (NoCurrentCaseException ex) {
throw new IngestModuleException(Bundle.EmbeddedFileExtractorIngestModule_UnableToGetMSOfficeExtractor_errMsg(), ex);
}
@ -155,8 +155,8 @@ public final class EmbeddedFileExtractorIngestModule extends FileIngestModuleAda
*/
if (archiveExtractor.isSevenZipExtractionSupported(abstractFile)) {
archiveExtractor.unpack(abstractFile, mapOfDepthTrees.get(jobId));
} else if (officeExtractor.isContentExtractionSupported(abstractFile)) {
officeExtractor.extractEmbeddedContent(abstractFile);
} else if (documentExtractor.isContentExtractionSupported(abstractFile)) {
documentExtractor.extractEmbeddedContent(abstractFile);
}
return ProcessResult.OK;
}

View File

@ -0,0 +1,183 @@
/*
* 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.modules.embeddedfileextractor;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.tika.exception.TikaException;
import org.apache.tika.extractor.EmbeddedDocumentExtractor;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.parser.AutoDetectParser;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.parser.Parser;
import org.apache.tika.sax.BodyContentHandler;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.EncodedFileOutputStream;
import org.sleuthkit.datamodel.TskData;
/**
* Facility for extracting and storing attachments from PDF documents.
* Implementation specifics, however, are generic enough to be used on any
* document with embedded resources. The current name reflects the only known
* use case for this class.
*/
final class PDFAttachmentExtractor {
private static final Logger logger = Logger.getLogger(PDFAttachmentExtractor.class.getName());
private final AutoDetectParser parser;
public PDFAttachmentExtractor() {
parser = new AutoDetectParser();
}
public PDFAttachmentExtractor(AutoDetectParser parser) {
this.parser = parser;
}
/**
* Extracts PDF attachments from a given input and writes them to the supplied
* output directory.
*
* @param input Input PDF to extract attachments from
* @param parentID ID for unique extraction names
* @param outputDir Directory to write attachments
* @return Map containing file name -> location on disk
* @throws IOException
* @throws SAXException
* @throws TikaException
*/
public Map<String, Path> extract(InputStream input, long parentID, Path outputDir) throws IOException, SAXException, TikaException {
ExtractionPreconditions.checkArgument(Files.exists(outputDir),
String.format("Output directory: %s, does not exist.", outputDir.toString())); //NON-NLS
ParseContext parseContext = new ParseContext();
parseContext.set(Parser.class, parser);
//Keep track of the attachment files as they are being extracted and written to disk.
NewResourceWatcher watcher = new NewResourceWatcher();
parseContext.set(EmbeddedDocumentExtractor.class, new EmbeddedAttachmentHandler(outputDir, parentID, watcher));
//Parse input with default params, except for our ParseContext
parser.parse(input, new BodyContentHandler(-1), new Metadata(), parseContext);
return watcher.getSnapshot();
}
/**
* Internal Tika class that is invoked upon encountering an embedded
* resource.
*/
static class EmbeddedAttachmentHandler implements EmbeddedDocumentExtractor {
private final Path outputDirectory;
private final NewResourceWatcher watcher;
private final Long parentID;
private Integer attachmentCount;
public EmbeddedAttachmentHandler(Path outputDirectory, long parentID, NewResourceWatcher watcher) {
this.outputDirectory = outputDirectory;
this.watcher = watcher;
this.parentID = parentID;
attachmentCount = 0;
}
@Override
public boolean shouldParseEmbedded(Metadata mtdt) {
//Grab every available attachment
return true;
}
@Override
public void parseEmbedded(InputStream in, ContentHandler ch, Metadata mtdt, boolean bln) throws SAXException, IOException {
//Resource naming scheme is used internally in autopsy, therefore we can guarentee uniqueness.
String uniqueExtractedName = parentID + "_attch_" + attachmentCount++; //NON-NLS
String name = mtdt.get(Metadata.RESOURCE_NAME_KEY);
String ext = FilenameUtils.getExtension(name);
//Append the extension if we can.
if(ext == null) {
name = uniqueExtractedName;
} else if(!ext.isEmpty()) {
uniqueExtractedName += "." + ext;
}
Path outputFile = outputDirectory.resolve(uniqueExtractedName);
try (EncodedFileOutputStream outputStream = new EncodedFileOutputStream(
new FileOutputStream(outputFile.toFile()), TskData.EncodingType.XOR1)){
IOUtils.copy(in, outputStream);
watcher.notify(name, outputFile);
} catch (IOException ex) {
logger.log(Level.WARNING, String.format("Could not extract attachment %s into directory %s", //NON-NLS
uniqueExtractedName, outputFile), ex);
}
}
}
/**
* Convenient wrapper for keeping track of new resource paths and the display
* name for each of these resources.
*
* It is necessary to maintain a snapshot of only our changes when the
* output directory is shared among other processes/threads.
*/
static class NewResourceWatcher {
private final Map<String, Path> newResourcePaths;
public NewResourceWatcher() {
newResourcePaths = new HashMap<>();
}
public void notify(String name, Path newResource) {
newResourcePaths.put(name, newResource);
}
public Map<String, Path> getSnapshot() {
return newResourcePaths;
}
}
/**
* Static convenience methods that ensure the PDF extractor is being invoked
* correctly.
*/
static class ExtractionPreconditions {
public static void checkArgument(boolean expression, String msg) throws IOException {
if (!expression) {
throw new IOException(msg);
}
}
private ExtractionPreconditions(){
}
}
}

View File

@ -58,7 +58,7 @@ public class FileTypeDetector {
* @return A list of all detectable file types.
*
* @throws FileTypeDetectorInitException If an error occurs while assembling
* the list of types
* the list of types
*/
public static synchronized SortedSet<String> getDetectedTypes() throws FileTypeDetectorInitException {
TreeSet<String> detectedTypes = new TreeSet<>((String string1, String string2) -> {
@ -109,7 +109,9 @@ public class FileTypeDetector {
* Tika, and Autopsy file type definitions take precendence over Tika.
*
* @throws FileTypeDetectorInitException If an initialization error occurs,
* e.g., user-defined file type definitions exist but cannot be loaded.
* e.g., user-defined file type
* definitions exist but cannot be
* loaded.
*/
public FileTypeDetector() throws FileTypeDetectorInitException {
try {
@ -139,7 +141,7 @@ public class FileTypeDetector {
* user-defined MIME type by this detector.
*
* @param customTypes
* @param mimeType The MIME type name (e.g., "text/html").
* @param mimeType The MIME type name (e.g., "text/html").
*
* @return True or false.
*/
@ -170,7 +172,7 @@ public class FileTypeDetector {
* @param file The file to test.
*
* @return A MIME type name. If file type could not be detected, or results
* were uncertain, octet-stream is returned.
* were uncertain, octet-stream is returned.
*
*
*/
@ -223,7 +225,7 @@ public class FileTypeDetector {
ReadContentInputStream stream = new ReadContentInputStream(file);
try (TikaInputStream tikaInputStream = TikaInputStream.get(stream)) {
String tikaType = tika.detect(tikaInputStream, file.getName());
String tikaType = tika.detect(tikaInputStream);
/*
* Remove the Tika suffix from the MIME type name.
@ -234,6 +236,21 @@ public class FileTypeDetector {
*/
mimeType = removeOptionalParameter(mimeType);
/*
* If Tika recognizes the file signature, then use the file
* name to refine the type. In short, this is to exclude the
* mime types that are determined solely by file extension.
* More details in JIRA-4871.
*/
if (!mimeType.equals(MimeTypes.OCTET_STREAM)) {
ReadContentInputStream secondPassStream = new ReadContentInputStream(file);
try (TikaInputStream secondPassTikaStream = TikaInputStream.get(secondPassStream)) {
tikaType = tika.detect(secondPassTikaStream, file.getName());
mimeType = tikaType.replace("tika-", ""); //NON-NLS
mimeType = removeOptionalParameter(mimeType);
}
}
/**
* We cannot trust Tika's audio/mpeg mimetype. Lets verify the
* first two bytes and confirm it is not 0xffff. Details in
@ -275,6 +292,7 @@ public class FileTypeDetector {
* first 4 bits.
*
* @param x byte
*
* @return Flag indicating the byte if 0xFF
*/
private boolean byteIs0xFF(byte x) {
@ -284,9 +302,10 @@ public class FileTypeDetector {
/**
* Retrieves the first N bytes from a file.
*
* @param file Abstract file to read
* @param file Abstract file to read
* @param offset Offset to begin reading
* @param n Number of bytes to read
* @param n Number of bytes to read
*
* @return Byte array of size n
*
* @throws TskCoreException
@ -371,7 +390,7 @@ public class FileTypeDetector {
* Constructs an exception to throw if an initialization error occurs,
* e.g., user-defined file type definitions exist but cannot be loaded.
*
* @param message The exception message,
* @param message The exception message,
* @param throwable The underlying cause of the exception.
*/
FileTypeDetectorInitException(String message, Throwable throwable) {
@ -409,7 +428,7 @@ public class FileTypeDetector {
* @return A MIME type name.
*
* @throws TskCoreException if detection is required and there is a problem
* writing the result to the case database.
* writing the result to the case database.
* @deprecated Use getMIMEType instead, and call AbstractFile.setMIMEType
* and AbstractFile.save to save the result to the file object and the
* database.
@ -429,10 +448,10 @@ public class FileTypeDetector {
* @param file The file.
*
* @return A MIME type name. If file type could not be detected or results
* were uncertain, octet-stream is returned.
* were uncertain, octet-stream is returned.
*
* @throws TskCoreException if detection is required and there is a problem
* writing the result to the case database.
* writing the result to the case database.
*
* @deprecated Use getMIMEType instead, and call AbstractFile.setMIMEType
* and AbstractFile.save to save the result to the file object and the
@ -453,7 +472,7 @@ public class FileTypeDetector {
* @param file The file to test.
*
* @return A MIME type name. If file type could not be detected or results
* were uncertain, octet-stream is returned.
* were uncertain, octet-stream is returned.
*
* @throws TskCoreException
* @deprecated Use getMIMEType instead.

View File

@ -198,6 +198,7 @@ final class HtmlTextExtractor implements TextExtractor {
renderer.setIncludeHyperlinkURLs(false);
renderer.setDecorateFontStyles(false);
renderer.setIncludeAlternateText(false);
renderer.setMaxLineLength(0); // don't force wrapping
return new StringReader(renderer.toString());
} catch (IOException ex) {
logger.log(Level.WARNING, "Error extracting HTML from content.", ex);

View File

@ -46,6 +46,7 @@ import org.apache.tika.Tika;
import org.apache.tika.exception.TikaException;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.parser.AutoDetectParser;
import org.apache.tika.parser.EmptyParser;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.parser.Parser;
import org.apache.tika.parser.ParsingReader;
@ -125,6 +126,16 @@ final class TikaTextExtractor implements TextExtractor {
"application/x-z", //NON-NLS
"application/x-compress"); //NON-NLS
//Tika should ignore types with embedded files that can be handled by the unpacking modules
private static final List<String> EMBEDDED_FILE_MIME_TYPES
= ImmutableList.of("application/msword", //NON-NLS
"application/vnd.openxmlformats-officedocument.wordprocessingml.document", //NON-NLS
"application/vnd.ms-powerpoint", //NON-NLS
"application/vnd.openxmlformats-officedocument.presentationml.presentation", //NON-NLS
"application/vnd.ms-excel", //NON-NLS
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", //NON-NLS
"application/pdf"); //NON-NLS
private static final java.util.logging.Logger TIKA_LOGGER = java.util.logging.Logger.getLogger("Tika"); //NON-NLS
private static final Logger AUTOPSY_LOGGER = Logger.getLogger(TikaTextExtractor.class.getName());
@ -184,7 +195,14 @@ final class TikaTextExtractor implements TextExtractor {
InputStream stream = null;
ParseContext parseContext = new ParseContext();
parseContext.set(Parser.class, parser);
//Disable appending embedded file text to output for EFE supported types
//JIRA-4975
if(content instanceof AbstractFile && EMBEDDED_FILE_MIME_TYPES.contains(((AbstractFile)content).getMIMEType())) {
parseContext.set(Parser.class, new EmptyParser());
} else {
parseContext.set(Parser.class, parser);
}
if (ocrEnabled() && content instanceof AbstractFile) {
AbstractFile file = ((AbstractFile) content);
@ -516,11 +534,11 @@ final class TikaTextExtractor implements TextExtractor {
if (context != null) {
ImageConfig configInstance = context.lookup(ImageConfig.class);
if (configInstance != null) {
if(Objects.nonNull(configInstance.getOCREnabled())) {
if (Objects.nonNull(configInstance.getOCREnabled())) {
this.tesseractOCREnabled = configInstance.getOCREnabled();
}
if(Objects.nonNull(configInstance.getOCRLanguages())) {
if (Objects.nonNull(configInstance.getOCRLanguages())) {
this.languagePacks = formatLanguagePacks(configInstance.getOCRLanguages());
}
}

View File

@ -52,6 +52,8 @@ import org.sleuthkit.autopsy.texttranslation.NoServiceProviderException;
import org.sleuthkit.autopsy.texttranslation.TranslationException;
import org.sleuthkit.datamodel.Content;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
import org.sleuthkit.autopsy.texttranslation.ui.TranslationContentPanel.DisplayDropdownOptions;
@ -61,6 +63,8 @@ import org.sleuthkit.autopsy.texttranslation.ui.TranslationContentPanel.DisplayD
@ServiceProvider(service = TextViewer.class, position = 4)
public final class TranslatedTextViewer implements TextViewer {
private static final Logger logger = Logger.getLogger(TranslatedTextViewer.class.getName());
private static final boolean OCR_ENABLED = true;
private static final boolean OCR_DISABLED = false;
private static final int MAX_SIZE_1MB = 1024000;
@ -169,16 +173,20 @@ public final class TranslatedTextViewer implements TextViewer {
try {
return getFileText(node);
} catch (IOException ex) {
logger.log(Level.WARNING, "Error getting text", ex);
return Bundle.TranslatedContentViewer_errorMsg();
} catch (TextExtractor.InitReaderException ex) {
logger.log(Level.WARNING, "Error getting text", ex);
return Bundle.TranslatedContentViewer_errorExtractingText();
}
} else {
try {
return translate(getFileText(node));
} catch (IOException ex) {
logger.log(Level.WARNING, "Error translating text", ex);
return Bundle.TranslatedContentViewer_errorMsg();
} catch (TextExtractor.InitReaderException ex) {
logger.log(Level.WARNING, "Error translating text", ex);
return Bundle.TranslatedContentViewer_errorExtractingText();
}
}
@ -247,7 +255,8 @@ public final class TranslatedTextViewer implements TextViewer {
} catch (NoServiceProviderException ex) {
return Bundle.TranslatedContentViewer_noServiceProvider();
} catch (TranslationException ex) {
return Bundle.TranslatedContentViewer_translationException();
logger.log(Level.WARNING, "Error translating text", ex);
return Bundle.TranslatedContentViewer_translationException() + " (" + ex.getMessage() + ")";
}
}
@ -287,7 +296,7 @@ public final class TranslatedTextViewer implements TextViewer {
//Correct for UTF-8
byte[] resultInUTF8Bytes = result.getBytes("UTF8");
byte[] trimTo1MB = Arrays.copyOfRange(resultInUTF8Bytes, 0, MAX_SIZE_1MB / 1000);
byte[] trimTo1MB = Arrays.copyOfRange(resultInUTF8Bytes, 0, MAX_SIZE_1MB );
return new String(trimTo1MB, "UTF-8");
}
@ -333,7 +342,8 @@ public final class TranslatedTextViewer implements TextViewer {
bytesRead += read;
}
return textBuilder.toString();
// The trim is on here because HTML files were observed with nearly 1MB of white space at the end
return textBuilder.toString().trim();
}
/**