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 AddImageWizardIngestConfigPanel.name.text=Configure Ingest Modules
AddImageWizardSelectDspVisual.multiUserWarning.text=This type of Data Source Processor is not available in multi-user mode 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 # {0} - exception message
Case.closeException.couldNotCloseCase=Error closing case: {0} Case.closeException.couldNotCloseCase=Error closing case: {0}
Case.creationException.couldNotAcquireDirLock=Failed to get lock on case directory 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.l01FileOption.text=Logical evidence file (L01)
LogicalFilesDspPanel.subTypeComboBox.localFilesOption.text=Local files and folders LogicalFilesDspPanel.subTypeComboBox.localFilesOption.text=Local files and folders
LogicalImagerDSProcessor.dataSourceType=Autopsy Imager 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.title0=Hostname
LogicalImagerPanel.imageTable.columnModel.title1=Extracted Date LogicalImagerPanel.imageTable.columnModel.title1=Extracted Date
LogicalImagerPanel.messageLabel.clickScanOrBrowse=Click SCAN or BROWSE button to find images LogicalImagerPanel.messageLabel.clickScanOrBrowse=Click SCAN or BROWSE button to find images
# {0} - sparseImageDirectory
# {1} - image
LogicalImagerPanel.messageLabel.directoryDoesNotContainSparseImage=Directory {0} does not contain {1} 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.directoryFormatInvalid=Directory {0} does not match format Logical_Imager_HOSTNAME_yyyymmdd_HH_MM_SS
LogicalImagerPanel.messageLabel.driveHasNoImages=Drive has no images LogicalImagerPanel.messageLabel.driveHasNoImages=Drive has no images
LogicalImagerPanel.messageLabel.noExternalDriveFound=No drive found LogicalImagerPanel.messageLabel.noExternalDriveFound=No drive found

View File

@ -18,7 +18,13 @@
*/ */
package org.sleuthkit.autopsy.casemodule; package org.sleuthkit.autopsy.casemodule;
import java.io.File;
import java.nio.file.Path; 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 javax.swing.JPanel;
import org.openide.util.NbBundle.Messages; import org.openide.util.NbBundle.Messages;
import org.openide.util.lookup.ServiceProvider; 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.DataSourceProcessor;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor;
import org.sleuthkit.datamodel.Content;
/** /**
* A Logical Imager data source processor that implements the DataSourceProcessor service * 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 { 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 final LogicalImagerPanel configPanel;
private AddLogicalImageTask addLogicalImageTask;
/* /*
* Constructs a Logical Imager data source processor that implements the * 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 * @param callback Callback that will be used by the background task
* to return results. * 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 @Override
public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
configPanel.storeSettings(); configPanel.storeSettings();
Path dirPath = configPanel.getImageDirPath();
System.out.println("Choosen directory " + dirPath.toString()); Path imageDirPath = configPanel.getImageDirPath();
// TODO: process the data source in 5011 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 * @param chunkSize The maximum size of each chunk of the raw
* data source as it is divided up into virtual * data source as it is divided up into virtual
* unallocated space files. * 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 * @param progressMonitor Progress monitor for reporting progress
* during processing. * during processing.
* @param callback Callback to call when processing is done. * @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) { private void run(String deviceId, String imagePath, int sectorSize, String timeZone,
AddImageTask addImageTask = new AddImageTask(deviceId, imagePath, sectorSize, timeZone, ignoreFatOrphanFiles, md5, sha1, sha256, null, progressMonitor, callback); boolean ignoreFatOrphanFiles, String md5, String sha1, String sha256,
new Thread(addImageTask).start(); 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 @Override
public void cancel() { public void cancel() {
if (addLogicalImageTask != null) {
addLogicalImageTask.cancelTask();
}
} }
/** /**

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8" ?> <?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> <Properties>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> <Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[0, 65]"/> <Dimension value="[0, 65]"/>
@ -213,6 +213,9 @@
<Property name="columnModel" type="javax.swing.table.TableColumnModel" editor="org.netbeans.modules.form.editors2.TableColumnModelEditor"> <Property name="columnModel" type="javax.swing.table.TableColumnModel" editor="org.netbeans.modules.form.editors2.TableColumnModelEditor">
<TableColumnModel selectionModel="1"/> <TableColumnModel selectionModel="1"/>
</Property> </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="showHorizontalLines" type="boolean" value="false"/>
<Property name="showVerticalLines" 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"> <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; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor;
/** /**
* Panel for adding an logical image file from drive letters. Allows the user * Panel for adding an logical image file from drive letters. Allows the user to
* to select a file. * select a file.
*/ */
@Messages({ @Messages({
"LogicalImagerPanel.messageLabel.selectedImage=Selected folder", "LogicalImagerPanel.messageLabel.selectedImage=Selected folder",
"LogicalImagerPanel.messageLabel.noImageSelected=No image selected", "LogicalImagerPanel.messageLabel.noImageSelected=No image selected",
"LogicalImagerPanel.messageLabel.driveHasNoImages=Drive has no images", "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 @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
public class LogicalImagerPanel extends JPanel implements DocumentListener { public class LogicalImagerPanel extends JPanel implements DocumentListener {
@ -70,8 +69,8 @@ public class LogicalImagerPanel extends JPanel implements DocumentListener {
/** /**
* Creates new form LogicalImagerPanel * Creates new form LogicalImagerPanel
* *
* @param context A string context name used to read/store last * @param context A string context name used to read/store last used
* used settings. * settings.
*/ */
private LogicalImagerPanel(String context) { private LogicalImagerPanel(String context) {
this.contextName = context; this.contextName = context;
@ -82,8 +81,8 @@ public class LogicalImagerPanel extends JPanel implements DocumentListener {
/** /**
* Creates and returns an instance of a LogicalImagerPanel. * Creates and returns an instance of a LogicalImagerPanel.
* *
* @param context A string context name used to read/store last * @param context A string context name used to read/store last used
* used settings. * settings.
* *
* @return instance of the LogicalImagerPanel * @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.setAutoResizeMode(javax.swing.JTable.AUTO_RESIZE_OFF);
imageTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
imageTable.setShowHorizontalLines(false); imageTable.setShowHorizontalLines(false);
imageTable.setShowVerticalLines(false); imageTable.setShowVerticalLines(false);
imageTable.getTableHeader().setReorderingAllowed(false); imageTable.getTableHeader().setReorderingAllowed(false);
@ -264,7 +264,7 @@ public class LogicalImagerPanel extends JPanel implements DocumentListener {
return bytes + " B"; //NON-NLS return bytes + " B"; //NON-NLS
} }
int exp = (int) (Math.log(bytes) / Math.log(unit)); 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 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 }//GEN-LAST:event_scanButtonActionPerformed
@Messages({ @Messages({
"# {0} - sparseImageDirectory",
"# {1} - image",
"LogicalImagerPanel.messageLabel.directoryDoesNotContainSparseImage=Directory {0} does not contain {1}", "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.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 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()) { if (m.find()) {
Path vhdPath = Paths.get(path, SPARSE_IMAGE_VHD); Path vhdPath = Paths.get(path, SPARSE_IMAGE_VHD);
if (!vhdPath.toFile().exists()) { 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); firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), true, false);
return; return;
} }
@ -485,6 +488,10 @@ public class LogicalImagerPanel extends JPanel implements DocumentListener {
return choosenImageDirPath; return choosenImageDirPath;
} }
public void setMessageLabel(String message) {
messageLabel.setText(message);
}
@Override @Override
public void insertUpdate(DocumentEvent e) { public void insertUpdate(DocumentEvent e) {
} }
@ -501,6 +508,7 @@ public class LogicalImagerPanel extends JPanel implements DocumentListener {
} }
private class ImageTableModel extends AbstractTableModel { private class ImageTableModel extends AbstractTableModel {
private final List<String> hostnames = new ArrayList<>(); private final List<String> hostnames = new ArrayList<>();
private final List<String> extractDates = new ArrayList<>(); private final List<String> extractDates = new ArrayList<>();
private final List<String> imageDirPaths = 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 com.google.common.eventbus.Subscribe;
import java.awt.event.ItemListener; import java.awt.event.ItemListener;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.Instant;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId; import java.time.ZoneId;
import java.util.Collection; import java.util.Collection;
import java.util.EnumSet; 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.autopsy.ingest.ModuleDataEvent;
import org.sleuthkit.datamodel.Account; import org.sleuthkit.datamodel.Account;
import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.CaseDbAccessManager.CaseDbAccessQueryCallback;
import org.sleuthkit.datamodel.CommunicationsFilter; import org.sleuthkit.datamodel.CommunicationsFilter;
import org.sleuthkit.datamodel.CommunicationsFilter.AccountTypeFilter; import org.sleuthkit.datamodel.CommunicationsFilter.AccountTypeFilter;
import org.sleuthkit.datamodel.CommunicationsFilter.DateRangeFilter; import org.sleuthkit.datamodel.CommunicationsFilter.DateRangeFilter;
@ -157,6 +162,29 @@ final public class FiltersPanel extends JPanel {
applyFiltersButton.addActionListener(e -> applyFilters()); applyFiltersButton.addActionListener(e -> applyFilters());
refreshButton.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); 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> <Layout>
<DimensionLayout dim="0"> <DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0"> <Group type="103" groupAlignment="0" attributes="0">
<Component id="htmlScrollPane" pref="300" max="32767" attributes="0"/>
<Group type="102" attributes="0"> <Group type="102" attributes="0">
<Component id="showImagesToggleButton" min="-2" max="-2" 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> </Group>
<Component id="htmlJPanel" max="32767" attributes="0"/>
</Group> </Group>
</DimensionLayout> </DimensionLayout>
<DimensionLayout dim="1"> <DimensionLayout dim="1">
@ -28,29 +28,12 @@
<Group type="102" alignment="0" attributes="0"> <Group type="102" alignment="0" attributes="0">
<Component id="showImagesToggleButton" min="-2" max="-2" attributes="0"/> <Component id="showImagesToggleButton" min="-2" max="-2" attributes="0"/>
<EmptySpace 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>
</Group> </Group>
</DimensionLayout> </DimensionLayout>
</Layout> </Layout>
<SubComponents> <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"> <Component class="javax.swing.JToggleButton" name="showImagesToggleButton">
<Properties> <Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> <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"/> <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="showImagesToggleButtonActionPerformed"/>
</Events> </Events>
</Component> </Component>
<Container class="javax.swing.JPanel" name="htmlJPanel">
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
</Container>
</SubComponents> </SubComponents>
</Form> </Form>

View File

@ -18,9 +18,19 @@
*/ */
package org.sleuthkit.autopsy.contentviewers; 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.Jsoup;
import org.jsoup.nodes.Document; import org.jsoup.nodes.Node;
import org.openide.util.NbBundle.Messages; 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. * A file content viewer for HTML files.
@ -29,7 +39,9 @@ import org.openide.util.NbBundle.Messages;
final class HtmlPanel extends javax.swing.JPanel { final class HtmlPanel extends javax.swing.JPanel {
private static final long serialVersionUID = 1L; 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; private String htmlText;
/** /**
@ -37,8 +49,26 @@ final class HtmlPanel extends javax.swing.JPanel {
*/ */
HtmlPanel() { HtmlPanel() {
initComponents(); initComponents();
Platform.runLater(() -> {
Utilities.configureTextPaneAsHtml(htmlbodyTextPane); 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. * Clear the HTML in the text pane and disable the show/hide button.
*/ */
void reset() { void reset() {
htmlbodyTextPane.setText(""); Platform.runLater(() -> {
webView.getEngine().loadContent("", TEXT_TYPE);
});
showImagesToggleButton.setEnabled(false); 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 * Cleans out input HTML string
* *
@ -78,12 +99,11 @@ final class HtmlPanel extends javax.swing.JPanel {
* @return The cleansed HTML String * @return The cleansed HTML String
*/ */
private String cleanseHTML(String htmlInString) { private String cleanseHTML(String htmlInString) {
org.jsoup.nodes.Document doc = Jsoup.parse(htmlInString);
Document doc = Jsoup.parse(htmlInString); // remove all 'img' tags.
doc.select("img").stream().forEach(Node::remove);
// Update all 'img' tags. // remove all 'span' tags, these are often images which are ads
doc.select("img[src]").forEach(img -> img.attr("src", "")); doc.select("span").stream().forEach(Node::remove);
return doc.html(); return doc.html();
} }
@ -93,22 +113,26 @@ final class HtmlPanel extends javax.swing.JPanel {
@Messages({ @Messages({
"HtmlPanel_showImagesToggleButton_show=Show Images", "HtmlPanel_showImagesToggleButton_show=Show Images",
"HtmlPanel_showImagesToggleButton_hide=Hide 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() { private void refresh() {
if (false == htmlText.isEmpty()) { if (false == htmlText.isEmpty()) {
try { try {
if (showImagesToggleButton.isSelected()) { if (showImagesToggleButton.isSelected()) {
showImagesToggleButton.setText(Bundle.HtmlPanel_showImagesToggleButton_hide()); showImagesToggleButton.setText(Bundle.HtmlPanel_showImagesToggleButton_hide());
this.htmlbodyTextPane.setText(wrapInHtmlBody(htmlText)); Platform.runLater(() -> {
webView.getEngine().loadContent(htmlText);
});
} else { } else {
showImagesToggleButton.setText(Bundle.HtmlPanel_showImagesToggleButton_show()); showImagesToggleButton.setText(Bundle.HtmlPanel_showImagesToggleButton_show());
this.htmlbodyTextPane.setText(wrapInHtmlBody(cleanseHTML(htmlText))); Platform.runLater(() -> {
webView.getEngine().loadContent(cleanseHTML(htmlText));
});
} }
showImagesToggleButton.setEnabled(true); showImagesToggleButton.setEnabled(true);
htmlbodyTextPane.setCaretPosition(0); } catch (Exception ignored) {
} catch(Exception ex) { Platform.runLater(() -> {
this.htmlbodyTextPane.setText(wrapInHtmlBody(Bundle.Html_text_display_error())); 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 // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() { private void initComponents() {
htmlScrollPane = new javax.swing.JScrollPane();
htmlbodyTextPane = new javax.swing.JTextPane();
showImagesToggleButton = new javax.swing.JToggleButton(); showImagesToggleButton = new javax.swing.JToggleButton();
htmlJPanel = new javax.swing.JPanel();
htmlScrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
htmlbodyTextPane.setEditable(false);
htmlScrollPane.setViewportView(htmlbodyTextPane);
org.openide.awt.Mnemonics.setLocalizedText(showImagesToggleButton, org.openide.util.NbBundle.getMessage(HtmlPanel.class, "HtmlPanel.showImagesToggleButton.text")); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(showImagesToggleButton, org.openide.util.NbBundle.getMessage(HtmlPanel.class, "HtmlPanel.showImagesToggleButton.text")); // NOI18N
showImagesToggleButton.addActionListener(new java.awt.event.ActionListener() { 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); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout); this.setLayout(layout);
layout.setHorizontalGroup( layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(htmlScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 300, Short.MAX_VALUE)
.addGroup(layout.createSequentialGroup() .addGroup(layout.createSequentialGroup()
.addComponent(showImagesToggleButton) .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.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup() .addGroup(layout.createSequentialGroup()
.addComponent(showImagesToggleButton) .addComponent(showImagesToggleButton)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .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 }// </editor-fold>//GEN-END:initComponents
@ -160,10 +180,27 @@ final class HtmlPanel extends javax.swing.JPanel {
refresh(); refresh();
}//GEN-LAST:event_showImagesToggleButtonActionPerformed }//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 // Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JScrollPane htmlScrollPane; private javax.swing.JPanel htmlJPanel;
private javax.swing.JTextPane htmlbodyTextPane;
private javax.swing.JToggleButton showImagesToggleButton; private javax.swing.JToggleButton showImagesToggleButton;
// End of variables declaration//GEN-END:variables // 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 DISPLAY_TRANSLATED_NAMES = "DisplayTranslatedNames";
public static final String EXTERNAL_HEX_EDITOR_PATH = "ExternalHexEditorPath"; public static final String EXTERNAL_HEX_EDITOR_PATH = "ExternalHexEditorPath";
public static final String SOLR_MAX_JVM_SIZE = "SolrMaxJVMSize"; public static final String SOLR_MAX_JVM_SIZE = "SolrMaxJVMSize";
public static final String RESULTS_TABLE_PAGE_SIZE = "ResultsTablePageSize";
// Prevent instantiation. // Prevent instantiation.
private UserPreferences() { private UserPreferences() {
@ -500,6 +501,24 @@ public final class UserPreferences {
preferences.putInt(SOLR_MAX_JVM_SIZE, maxSize); 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. * 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.maxSolrMemoryLabel.text=Maximum Solr JVM Memory:
AutopsyOptionsPanel.maxMemoryUnitsLabel2.text=MB AutopsyOptionsPanel.maxMemoryUnitsLabel2.text=MB
AutopsyOptionsPanel.solrJVMHeapWarning.text=NOTE: Setting this too large may impact overall performance. 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.name=O
DataResultViewerTable.countRender.toolTip=O(ccurrences) indicates the number of data sources containing the item in the Central Repository DataResultViewerTable.countRender.toolTip=O(ccurrences) indicates the number of data sources containing the item in the Central Repository
DataResultViewerTable.firstColLbl=Name 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.name=S
DataResultViewerTable.scoreRender.toolTip=S(core) indicates whether the item is interesting or notable DataResultViewerTable.scoreRender.toolTip=S(core) indicates whether the item is interesting or notable
DataResultViewerTable.title=Table DataResultViewerTable.title=Table
@ -254,3 +260,13 @@ ExternalViewerGlobalSettingsPanel.jTable1.columnModel.title0_1=Mime type/Extensi
AutopsyOptionsPanel.maxSolrMemoryLabel.text=Maximum Solr JVM Memory: AutopsyOptionsPanel.maxSolrMemoryLabel.text=Maximum Solr JVM Memory:
AutopsyOptionsPanel.maxMemoryUnitsLabel2.text=MB AutopsyOptionsPanel.maxMemoryUnitsLabel2.text=MB
AutopsyOptionsPanel.solrJVMHeapWarning.text=NOTE: Setting this too large may impact overall performance. 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> <Layout>
<DimensionLayout dim="0"> <DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="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> </Group>
</DimensionLayout> </DimensionLayout>
<DimensionLayout dim="1"> <DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0"> <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> </Group>
</DimensionLayout> </DimensionLayout>
</Layout> </Layout>
<SubComponents> <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"> <Container class="org.openide.explorer.view.OutlineView" name="outlineView">
<AuxValues> <AuxValues>
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="new OutlineView(DataResultViewerTable.FIRST_COLUMN_LABEL);"/> <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"/> <Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
</Container> </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> </SubComponents>
</Form> </Form>

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2012-2018 Basis Technology Corp. * Copyright 2012-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -18,6 +18,7 @@
*/ */
package org.sleuthkit.autopsy.corecomponents; package org.sleuthkit.autopsy.corecomponents;
import com.google.common.eventbus.Subscribe;
import java.awt.Component; import java.awt.Component;
import java.awt.Cursor; import java.awt.Cursor;
import java.awt.FontMetrics; import java.awt.FontMetrics;
@ -26,6 +27,7 @@ import java.awt.dnd.DnDConstants;
import java.awt.event.MouseAdapter; import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.beans.FeatureDescriptor; import java.beans.FeatureDescriptor;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyVetoException; import java.beans.PropertyVetoException;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList; import java.util.ArrayList;
@ -35,9 +37,12 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.prefs.PreferenceChangeEvent;
import java.util.prefs.Preferences; import java.util.prefs.Preferences;
import javax.swing.ImageIcon; import javax.swing.ImageIcon;
import javax.swing.JOptionPane;
import javax.swing.JTable; import javax.swing.JTable;
import javax.swing.ListSelectionModel; import javax.swing.ListSelectionModel;
import static javax.swing.SwingConstants.CENTER; import static javax.swing.SwingConstants.CENTER;
@ -61,15 +66,24 @@ import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children; import org.openide.nodes.Children;
import org.openide.nodes.Node; import org.openide.nodes.Node;
import org.openide.nodes.Node.Property; 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.ImageUtilities;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.openide.util.NbPreferences; import org.openide.util.NbPreferences;
import org.openide.util.lookup.ServiceProvider; import org.openide.util.lookup.ServiceProvider;
import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.datamodel.NodeProperty; import org.sleuthkit.autopsy.datamodel.NodeProperty;
import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo; 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 * 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 final IconRendererTableListener iconRendererListener;
private Node rootNode; 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 * Constructs a tabular result viewer that displays the children of the
* given root node using an OutlineView. The viewer should have an ancestor * given root node using an OutlineView. The viewer should have an ancestor
@ -149,6 +175,8 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
*/ */
initComponents(); initComponents();
initializePagingSupport();
/* /*
* Configure the child OutlineView (explorer view) component. * Configure the child OutlineView (explorer view) component.
*/ */
@ -177,6 +205,32 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
outline.getTableHeader().addMouseListener(outlineViewListener); 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 * 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 * 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) { if (rootNode != null && rootNode.getChildren().getNodesCount() > 0) {
this.rootNode = rootNode; 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); this.getExplorerManager().setRootContext(this.rootNode);
setupTable(); setupTable();
} else { } 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 * Listener which sets the custom icon renderer on columns which contain
* icons instead of text when a column is added. * 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 // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void 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); 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); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout); this.setLayout(layout);
layout.setHorizontalGroup( layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) 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.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) 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 }// </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 // 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 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 // End of variables declaration//GEN-END:variables
} }

View File

@ -68,7 +68,7 @@
<Group type="103" groupAlignment="0" attributes="0"> <Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0"> <Group type="102" alignment="0" attributes="0">
<Component id="globalSettingsPanel" min="-2" max="-2" 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"/> <Component id="currentCaseSettingsPanel" min="-2" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/> <EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="currentSessionSettingsPanel" min="-2" max="-2" attributes="0"/> <Component id="currentSessionSettingsPanel" min="-2" max="-2" attributes="0"/>
@ -92,7 +92,7 @@
<Layout> <Layout>
<DimensionLayout dim="0"> <DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="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"/> <EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0"> <Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0"> <Group type="102" attributes="0">
@ -153,6 +153,11 @@
</Group> </Group>
</Group> </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> </Group>
<EmptySpace max="32767" attributes="0"/> <EmptySpace max="32767" attributes="0"/>
</Group> </Group>
@ -206,6 +211,11 @@
<Component id="fileNameTranslationColumnCheckbox" min="-2" max="-2" attributes="0"/> <Component id="fileNameTranslationColumnCheckbox" min="-2" max="-2" attributes="0"/>
</Group> </Group>
</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"/> <EmptySpace max="32767" attributes="0"/>
</Group> </Group>
</Group> </Group>
@ -407,6 +417,26 @@
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="fileNameTranslationColumnCheckboxActionPerformed"/> <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="fileNameTranslationColumnCheckboxActionPerformed"/>
</Events> </Events>
</Component> </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> </SubComponents>
</Container> </Container>
<Container class="javax.swing.JPanel" name="currentCaseSettingsPanel"> <Container class="javax.swing.JPanel" name="currentCaseSettingsPanel">

View File

@ -35,6 +35,7 @@ import org.sleuthkit.autopsy.texttranslation.TextTranslationService;
/** /**
* Panel for configuring view preferences. * Panel for configuring view preferences.
*/ */
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
public class ViewPreferencesPanel extends JPanel implements OptionsPanel { public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
private final boolean immediateUpdates; private final boolean immediateUpdates;
@ -85,6 +86,8 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
TextTranslationService tts = TextTranslationService.getInstance(); TextTranslationService tts = TextTranslationService.getInstance();
fileNameTranslationColumnCheckbox.setEnabled(tts.hasProvider()); fileNameTranslationColumnCheckbox.setEnabled(tts.hasProvider());
maxResultsSpinner.setValue(UserPreferences.getResultsTablePageSize());
// Current Case Settings // Current Case Settings
boolean caseIsOpen = Case.isCaseOpen(); boolean caseIsOpen = Case.isCaseOpen();
currentCaseSettingsPanel.setEnabled(caseIsOpen); currentCaseSettingsPanel.setEnabled(caseIsOpen);
@ -114,6 +117,7 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
UserPreferences.setShowOnlyCurrentUserTags(hideOtherUsersTagsCheckbox.isSelected()); UserPreferences.setShowOnlyCurrentUserTags(hideOtherUsersTagsCheckbox.isSelected());
UserPreferences.setHideCentralRepoCommentsAndOccurrences(commentsOccurencesColumnsCheckbox.isSelected()); UserPreferences.setHideCentralRepoCommentsAndOccurrences(commentsOccurencesColumnsCheckbox.isSelected());
UserPreferences.setDisplayTranslatedFileNames(fileNameTranslationColumnCheckbox.isSelected()); UserPreferences.setDisplayTranslatedFileNames(fileNameTranslationColumnCheckbox.isSelected());
UserPreferences.setResultsTablePageSize((int)maxResultsSpinner.getValue());
storeGroupItemsInTreeByDataSource(); storeGroupItemsInTreeByDataSource();
@ -167,6 +171,8 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
translateTextLabel = new javax.swing.JLabel(); translateTextLabel = new javax.swing.JLabel();
commentsOccurencesColumnWrapAroundText = new javax.swing.JLabel(); commentsOccurencesColumnWrapAroundText = new javax.swing.JLabel();
fileNameTranslationColumnCheckbox = new javax.swing.JCheckBox(); fileNameTranslationColumnCheckbox = new javax.swing.JCheckBox();
maxResultsLabel = new javax.swing.JLabel();
maxResultsSpinner = new javax.swing.JSpinner();
currentCaseSettingsPanel = new javax.swing.JPanel(); currentCaseSettingsPanel = new javax.swing.JPanel();
groupByDataSourceCheckbox = new javax.swing.JCheckBox(); groupByDataSourceCheckbox = new javax.swing.JCheckBox();
currentSessionSettingsPanel = new javax.swing.JPanel(); 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); javax.swing.GroupLayout globalSettingsPanelLayout = new javax.swing.GroupLayout(globalSettingsPanel);
globalSettingsPanel.setLayout(globalSettingsPanelLayout); globalSettingsPanel.setLayout(globalSettingsPanelLayout);
globalSettingsPanelLayout.setHorizontalGroup( globalSettingsPanelLayout.setHorizontalGroup(
@ -333,7 +349,11 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
.addComponent(keepCurrentViewerRadioButton) .addComponent(keepCurrentViewerRadioButton)
.addComponent(useBestViewerRadioButton) .addComponent(useBestViewerRadioButton)
.addComponent(useLocalTimeRadioButton) .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)) .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
); );
globalSettingsPanelLayout.setVerticalGroup( globalSettingsPanelLayout.setVerticalGroup(
@ -381,6 +401,10 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
.addComponent(translateTextLabel) .addComponent(translateTextLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(fileNameTranslationColumnCheckbox))) .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)) .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
); );
@ -593,6 +617,14 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
} }
}//GEN-LAST:event_fileNameTranslationColumnCheckboxActionPerformed }//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 // Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JLabel centralRepoLabel; private javax.swing.JLabel centralRepoLabel;
@ -613,6 +645,8 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
private javax.swing.JLabel hideSlackFilesLabel; private javax.swing.JLabel hideSlackFilesLabel;
private javax.swing.JScrollPane jScrollPane1; private javax.swing.JScrollPane jScrollPane1;
private javax.swing.JRadioButton keepCurrentViewerRadioButton; private javax.swing.JRadioButton keepCurrentViewerRadioButton;
private javax.swing.JLabel maxResultsLabel;
private javax.swing.JSpinner maxResultsSpinner;
private javax.swing.JLabel selectFileLabel; private javax.swing.JLabel selectFileLabel;
private javax.swing.JList<String> timeZoneList; private javax.swing.JList<String> timeZoneList;
private javax.swing.JLabel translateTextLabel; 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.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import org.openide.nodes.Children;
import org.openide.nodes.Sheet; import org.openide.nodes.Sheet;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.openide.util.WeakListeners; import org.openide.util.WeakListeners;
@ -55,6 +54,8 @@ import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable.Score;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import static org.sleuthkit.autopsy.datamodel.Bundle.*; import static org.sleuthkit.autopsy.datamodel.Bundle.*;
import static org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.AbstractFilePropertyType.*; 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.IngestManager;
import org.sleuthkit.autopsy.ingest.ModuleContentEvent; import org.sleuthkit.autopsy.ingest.ModuleContentEvent;
import org.sleuthkit.autopsy.texttranslation.NoServiceProviderException; 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 (getContent().getId() == newContent.getId()) {
// If so, refresh our children. // If so, refresh our children.
try { try {
Children parentsChildren = getParentNode().getChildren();
// We only want to refresh our parents children if we are in the // We only want to refresh our parents children if we are in the
// data sources branch of the tree. The parent nodes in other // data sources branch of the tree. The parent nodes in other
// branches of the tree (e.g. File Types and Deleted Files) do // branches of the tree (e.g. File Types and Deleted Files) do
// not need to be refreshed. // not need to be refreshed.
if (parentsChildren instanceof ContentChildren) { BaseChildFactory.post(getParentNode().getName(), new RefreshKeysEvent());
((ContentChildren) parentsChildren).refreshChildren();
parentsChildren.getNodesCount();
}
} catch (NullPointerException ex) { } catch (NullPointerException ex) {
// Skip // 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())) { } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2011-2016 Basis Technology Corp. * Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -18,228 +18,28 @@
*/ */
package org.sleuthkit.autopsy.datamodel; package org.sleuthkit.autopsy.datamodel;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children.Keys;
import org.openide.nodes.Node; 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.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.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. * 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 CreateSleuthkitNodeVisitor createSleuthkitNodeVisitor = new CreateSleuthkitNodeVisitor();
private final CreateAutopsyNodeVisitor createAutopsyNodeVisitor = new CreateAutopsyNodeVisitor();
/** AbstractContentChildren(String nodeName) {
* Uses lazy Content.Keys super(nodeName, new DataSourcesKnownAndSlackFilter<>());
*/
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
} }
@Override @Override
protected Node[] createNodes(T key) { protected Node createNodeForKey(T key) {
if (key instanceof SleuthkitVisitableItem) { if (key instanceof SleuthkitVisitableItem) {
return new Node[]{((SleuthkitVisitableItem) key).accept(createSleuthkitNodeVisitor)}; return ((SleuthkitVisitableItem) key).accept(createSleuthkitNodeVisitor);
} else { } else {
return new Node[]{((AutopsyVisitableItem) key).accept(createAutopsyNodeVisitor)}; return null;
}
}
/**
* 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();
} }
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2011-2018 Basis Technology Corp. * Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -22,6 +22,7 @@ import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.List; import java.util.List;
import java.util.logging.Level; import java.util.logging.Level;
import org.openide.nodes.Children;
import org.openide.util.lookup.Lookups; import org.openide.util.lookup.Lookups;
import org.openide.util.Lookup; 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. * @param lookup The Lookup object for the node.
*/ */
AbstractContentNode(T content, Lookup lookup) { AbstractContentNode(T content, Lookup lookup) {
//TODO consider child factory for the content children super(Children.create(new ContentChildren(content), true), lookup);
super(new ContentChildren(content), lookup);
this.content = content; this.content = content;
//super.setName(ContentUtils.getSystemName(content)); //super.setName(ContentUtils.getSystemName(content));
super.setName("content_" + Long.toString(content.getId())); //NON-NLS 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) { protected Node createNodeForKey(Object key) {
if (key instanceof SleuthkitVisitableItem) { if (key instanceof SleuthkitVisitableItem) {
return ((SleuthkitVisitableItem) key).accept(new RootContentChildren.CreateSleuthkitNodeVisitor()); return ((SleuthkitVisitableItem) key).accept(new CreateSleuthkitNodeVisitor());
} else if (key instanceof AutopsyVisitableItem) { } else if (key instanceof AutopsyVisitableItem) {
return ((AutopsyVisitableItem) key).accept(new RootContentChildren.CreateAutopsyNodeVisitor()); 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.attrsTableHeader.value=Value
ArtifactStringContent.failedToGetAttributes.message=Failed to get some or all attributes from case database 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 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.displayName=Result Details
BlackboardArtifactNode.createSheet.artifactDetails.name=Result Details BlackboardArtifactNode.createSheet.artifactDetails.name=Result Details
BlackboardArtifactNode.createSheet.artifactMD5.displayName=MD5 Hash BlackboardArtifactNode.createSheet.artifactMD5.displayName=MD5 Hash

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2011-2014 Basis Technology Corp. * Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * 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 * 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. * about the structure of the directory tree and what levels should be ignored.
* TODO consider a ContentChildren child factory
*/ */
class ContentChildren extends AbstractContentChildren<Content> { class ContentChildren extends AbstractContentChildren<Content> {
private static final Logger logger = Logger.getLogger(ContentChildren.class.getName()); private static final Logger logger = Logger.getLogger(ContentChildren.class.getName());
//private static final int MAX_CHILD_COUNT = 1000000;
private final Content parent; private final Content parent;
ContentChildren(Content parent) { ContentChildren(Content parent) {
super(); //initialize lazy behavior super("content_" + Long.toString(parent.getId()));
this.parent = parent; this.parent = parent;
} }
@ -90,7 +88,7 @@ class ContentChildren extends AbstractContentChildren<Content> {
children.add(c); children.add(c);
} }
} else if (c instanceof LocalDirectory) { } else if (c instanceof LocalDirectory) {
LocalDirectory localDir = (LocalDirectory)c; LocalDirectory localDir = (LocalDirectory) c;
if (localDir.isRoot()) { if (localDir.isRoot()) {
children.addAll(getDisplayChildren(localDir)); children.addAll(getDisplayChildren(localDir));
} else { } else {
@ -104,27 +102,13 @@ class ContentChildren extends AbstractContentChildren<Content> {
} }
@Override @Override
protected void addNotify() { protected List<Content> makeKeys() {
super.addNotify(); return getDisplayChildren(parent);
//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);
} }
@Override @Override
protected void removeNotify() { protected void onAdd() {
super.removeNotify(); // No-op
setKeys(new ArrayList<>());
} }
/** /**
@ -133,7 +117,11 @@ class ContentChildren extends AbstractContentChildren<Content> {
* them). * them).
*/ */
void refreshChildren() { void refreshChildren() {
List<Content> children = getDisplayChildren(parent); refresh(true);
setKeys(children); }
@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.EnumSet;
import java.util.List; import java.util.List;
import java.util.logging.Level; import java.util.logging.Level;
import org.openide.nodes.Children;
import org.openide.nodes.Sheet; import org.openide.nodes.Sheet;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.openide.util.lookup.Lookups; import org.openide.util.lookup.Lookups;
@ -56,7 +57,7 @@ public class DataSourcesNode extends DisplayableItemNode {
} }
public DataSourcesNode(long dsObjId) { 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; displayName = (dsObjId > 0) ? NbBundle.getMessage(DataSourcesNode.class, "DataSourcesNode.group_by_datasource.name") : NAME;
init(); init();
} }
@ -87,7 +88,7 @@ public class DataSourcesNode extends DisplayableItemNode {
} }
public DataSourcesNodeChildren(long dsObjId) { public DataSourcesNodeChildren(long dsObjId) {
super(); super("ds_" + Long.toString(dsObjId));
this.currentKeys = new ArrayList<>(); this.currentKeys = new ArrayList<>();
this.datasourceObjId = dsObjId; this.datasourceObjId = dsObjId;
} }
@ -97,25 +98,24 @@ public class DataSourcesNode extends DisplayableItemNode {
public void propertyChange(PropertyChangeEvent evt) { public void propertyChange(PropertyChangeEvent evt) {
String eventType = evt.getPropertyName(); String eventType = evt.getPropertyName();
if (eventType.equals(Case.Events.DATA_SOURCE_ADDED.toString())) { if (eventType.equals(Case.Events.DATA_SOURCE_ADDED.toString())) {
reloadKeys(); refresh(true);
} }
} }
}; };
@Override @Override
protected void addNotify() { protected void onAdd() {
Case.addEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED), pcl); Case.addEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED), pcl);
reloadKeys();
} }
@Override @Override
protected void removeNotify() { protected void onRemove() {
Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED), pcl); Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED), pcl);
currentKeys.clear(); currentKeys.clear();
setKeys(Collections.<Content>emptySet());
} }
private void reloadKeys() { @Override
protected List<Content> makeKeys() {
try { try {
if (datasourceObjId == 0) { if (datasourceObjId == 0) {
currentKeys = Case.getCurrentCaseThrows().getDataSources(); currentKeys = Case.getCurrentCaseThrows().getDataSources();
@ -135,20 +135,11 @@ public class DataSourcesNode extends DisplayableItemNode {
}); });
setKeys(currentKeys);
} catch (TskCoreException | NoCurrentCaseException | TskDataException ex) { } catch (TskCoreException | NoCurrentCaseException | TskDataException ex) {
logger.log(Level.SEVERE, "Error getting data sources: {0}", ex.getMessage()); // NON-NLS logger.log(Level.SEVERE, "Error getting data sources: {0}", ex.getMessage()); // NON-NLS
setKeys(Collections.<Content>emptySet());
}
} }
/** return currentKeys;
* Refresh all content keys This creates new nodes of keys have changed.
*/
public void refreshContentKeys() {
for (Content key : currentKeys) {
refreshKey(key);
}
} }
} }

View File

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

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2012-2018 Basis Technology Corp. * Copyright 2012-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * 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.Case;
import org.sleuthkit.autopsy.casemodule.CasePreferences; import org.sleuthkit.autopsy.casemodule.CasePreferences;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.autopsy.ingest.ModuleDataEvent; import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
@ -494,41 +493,52 @@ public class EmailExtracted implements AutopsyVisitableItem {
/** /**
* Node representing mail folder content (mail messages) * 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 accountName;
private final String folderName; private final String folderName;
private MessageFactory(String accountName, String folderName) { private MessageFactory(String accountName, String folderName) {
super(); super(accountName + "_" + folderName);
this.accountName = accountName; this.accountName = accountName;
this.folderName = folderName; this.folderName = folderName;
emailResults.addObserver(this); emailResults.addObserver(this);
} }
@Override @Override
protected boolean createKeys(List<Long> list) { protected Node createNodeForKey(BlackboardArtifact art) {
list.addAll(emailResults.getArtifactIds(accountName, folderName)); return new BlackboardArtifactNode(art);
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;
} }
@Override @Override
public void update(Observable o, Object arg) { public void update(Observable o, Object arg) {
refresh(true); 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 * Creates children for a given artifact type
*/ */
private class ArtifactFactory extends ChildFactory.Detachable<BlackboardArtifact> { private class ArtifactFactory extends BaseChildFactory<BlackboardArtifact> {
private BlackboardArtifact.Type type; private BlackboardArtifact.Type type;
public ArtifactFactory(BlackboardArtifact.Type type) { public ArtifactFactory(BlackboardArtifact.Type type) {
super(); super(type.getTypeName());
this.type = type; this.type = type;
} }
@ -481,36 +481,34 @@ public class ExtractedContent implements AutopsyVisitableItem {
}; };
@Override @Override
protected void addNotify() { protected void onAdd() {
IngestManager.getInstance().addIngestJobEventListener(pcl); IngestManager.getInstance().addIngestJobEventListener(pcl);
IngestManager.getInstance().addIngestModuleEventListener(pcl); IngestManager.getInstance().addIngestModuleEventListener(pcl);
} }
@Override @Override
protected void removeNotify() { protected void onRemove() {
IngestManager.getInstance().removeIngestJobEventListener(pcl); IngestManager.getInstance().removeIngestJobEventListener(pcl);
IngestManager.getInstance().removeIngestModuleEventListener(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 @Override
protected Node createNodeForKey(BlackboardArtifact key) { protected Node createNodeForKey(BlackboardArtifact key) {
return new BlackboardArtifactNode(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 * Autopsy Forensic Browser
* *
* Copyright 2013-2018 Basis Technology Corp. * Copyright 2013-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * 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 * 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 SleuthkitCase skCase;
private final FileSizeFilter filter; private final FileSizeFilter filter;
@ -377,6 +377,7 @@ public class FileSize implements AutopsyVisitableItem {
* added to case * added to case
*/ */
FileSizeChildren(FileSizeFilter filter, SleuthkitCase skCase, Observable o, long dsObjId) { FileSizeChildren(FileSizeFilter filter, SleuthkitCase skCase, Observable o, long dsObjId) {
super(filter.getName(), new ViewsKnownAndSlackFilter<>());
this.skCase = skCase; this.skCase = skCase;
this.filter = filter; this.filter = filter;
this.notifier = o; this.notifier = o;
@ -385,14 +386,14 @@ public class FileSize implements AutopsyVisitableItem {
} }
@Override @Override
protected void addNotify() { protected void onAdd() {
if (notifier != null) { if (notifier != null) {
notifier.addObserver(observer); notifier.addObserver(observer);
} }
} }
@Override @Override
protected void removeNotify() { protected void onRemove() {
if (notifier != null) { if (notifier != null) {
notifier.deleteObserver(observer); notifier.deleteObserver(observer);
} }
@ -400,6 +401,11 @@ public class FileSize implements AutopsyVisitableItem {
private final Observer observer = new FileSizeChildrenObserver(); private final Observer observer = new FileSizeChildrenObserver();
@Override
protected List<AbstractFile> makeKeys() {
return runFsQuery();
}
// Cause refresh of children if there are changes // Cause refresh of children if there are changes
private class FileSizeChildrenObserver implements Observer { 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) { private static String makeQuery(FileSizeFilter filter, long filteringDSObjId) {
String query; String query;
switch (filter) { switch (filter) {
@ -436,17 +436,6 @@ public class FileSize implements AutopsyVisitableItem {
// Ignore unallocated block files. // Ignore unallocated block files.
query = query + " AND (type != " + TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS.getFileType() + ")"; //NON-NLS 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 // filter by datasource if indicated in case preferences
if (Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true)) { if (Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true)) {
query += " AND data_source_obj_id = " + filteringDSObjId; query += " AND data_source_obj_id = " + filteringDSObjId;

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2011-2018 Basis Technology Corp. * Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * 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.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@ -292,7 +293,7 @@ public final class FileTypesByExtension implements AutopsyVisitableItem {
* should refresh * should refresh
*/ */
FileExtensionNode(FileTypesByExtension.SearchFilterInterface filter, SleuthkitCase skCase, FileTypesByExtObservable o) { 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())); Lookups.singleton(filter.getDisplayName()));
this.filter = filter; this.filter = filter;
super.setName(filter.getDisplayName()); 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. * 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 SleuthkitCase skCase;
private final FileTypesByExtension.SearchFilterInterface filter; 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 * @param o Observable that will notify when there could be new
* data to display * data to display
*/ */
private FileExtensionNodeChildren(FileTypesByExtension.SearchFilterInterface filter, SleuthkitCase skCase, Observable o) { private FileExtensionNodeChildren(FileTypesByExtension.SearchFilterInterface filter, SleuthkitCase skCase, Observable o, String nodeName) {
super(); super(nodeName, new ViewsKnownAndSlackFilter<>());
this.filter = filter; this.filter = filter;
this.skCase = skCase; this.skCase = skCase;
notifier = o; notifier = o;
} }
@Override @Override
protected void addNotify() { protected void onAdd() {
if (notifier != null) { if (notifier != null) {
notifier.addObserver(this); notifier.addObserver(this);
} }
} }
@Override @Override
protected void removeNotify() { protected void onRemove() {
if (notifier != null) { if (notifier != null) {
notifier.deleteObserver(this); notifier.deleteObserver(this);
} }
@ -417,19 +418,19 @@ public final class FileTypesByExtension implements AutopsyVisitableItem {
} }
@Override @Override
protected boolean createKeys(List<FileTypesKey> list) { protected Node createNodeForKey(FileTypesKey key) {
try { return key.accept(new FileTypes.FileNodeCreationVisitor());
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;
} }
@Override @Override
protected Node createNodeForKey(FileTypesKey key) { protected List<FileTypesKey> makeKeys() {
return key.accept(new FileTypes.FileNodeCreationVisitor()); 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 * Autopsy Forensic Browser
* *
* Copyright 2011-2018 Basis Technology Corp. * Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * 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 * files that match MimeType which is represented by this position in the
* tree. * tree.
*/ */
private class MediaSubTypeNodeChildren extends ChildFactory.Detachable<FileTypesKey> implements Observer { private class MediaSubTypeNodeChildren extends BaseChildFactory<FileTypesKey> implements Observer {
private final String mimeType; private final String mimeType;
private MediaSubTypeNodeChildren(String mimeType) { private MediaSubTypeNodeChildren(String mimeType) {
super(); super(mimeType, new ViewsKnownAndSlackFilter<>());
addObserver(this); addObserver(this);
this.mimeType = mimeType; 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 @Override
public void update(Observable o, Object arg) { public void update(Observable o, Object arg) {
refresh(true); refresh(true);
@ -475,5 +464,26 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi
protected Node createNodeForKey(FileTypesKey key) { protected Node createNodeForKey(FileTypesKey key) {
return key.accept(new FileTypes.FileNodeCreationVisitor()); 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 * Autopsy Forensic Browser
* *
* Copyright 2011-2018 Basis Technology Corp. * Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * 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.Case;
import org.sleuthkit.autopsy.casemodule.CasePreferences; import org.sleuthkit.autopsy.casemodule.CasePreferences;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.autopsy.ingest.ModuleDataEvent; import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
@ -378,33 +377,40 @@ public class HashsetHits implements AutopsyVisitableItem {
/** /**
* Creates the nodes for the hits in a given set. * 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 String hashsetName;
private Map<Long, BlackboardArtifact> artifactHits = new HashMap<>(); private Map<Long, BlackboardArtifact> artifactHits = new HashMap<>();
private HitFactory(String hashsetName) { private HitFactory(String hashsetName) {
super(); super(hashsetName);
this.hashsetName = hashsetName; this.hashsetName = hashsetName;
} }
@Override @Override
protected void addNotify() { protected void onAdd() {
hashsetResults.addObserver(this); hashsetResults.addObserver(this);
} }
@Override @Override
protected void removeNotify() { protected void onRemove() {
hashsetResults.deleteObserver(this); hashsetResults.deleteObserver(this);
} }
@Override @Override
protected boolean createKeys(List<Long> list) { protected Node createNodeForKey(BlackboardArtifact key) {
return new BlackboardArtifactNode(key);
if (skCase == null) {
return true;
} }
@Override
public void update(Observable o, Object arg) {
refresh(true);
}
@Override
protected List<BlackboardArtifact> makeKeys() {
if (skCase != null) {
hashsetResults.getArtifactIds(hashsetName).forEach((id) -> { hashsetResults.getArtifactIds(hashsetName).forEach((id) -> {
try { try {
if (!artifactHits.containsKey(id)) { if (!artifactHits.containsKey(id)) {
@ -415,23 +421,9 @@ public class HashsetHits implements AutopsyVisitableItem {
logger.log(Level.SEVERE, "TSK Exception occurred", ex); //NON-NLS logger.log(Level.SEVERE, "TSK Exception occurred", ex); //NON-NLS
} }
}); });
return new ArrayList<>(artifactHits.values());
// 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;
} }
return Collections.emptyList();
@Override
protected Node createNodeForKey(Long id) {
BlackboardArtifact art = artifactHits.get(id);
return (null == art) ? null : new BlackboardArtifactNode(art);
}
@Override
public void update(Observable o, Object arg) {
refresh(true);
} }
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2011-2018 Basis Technology Corp. * Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * 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.List;
import java.util.logging.Level; import java.util.logging.Level;
import javax.swing.Action; import javax.swing.Action;
import org.openide.nodes.Children;
import org.openide.nodes.Sheet; import org.openide.nodes.Sheet;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages; 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.SleuthkitCase.CaseDbQuery;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.VirtualDirectory; 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 * 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) { if (parent != null) {
// Is this a new carved file? // Is this a new carved file?
if (parent.getName().equals(VirtualDirectory.NAME_CARVED)) { if (parent.getName().equals(VirtualDirectory.NAME_CARVED)) {
// Was this new carved file produced from this image? // Is this new carved file for this data source?
if (parent.getParent().getId() == getContent().getId()) { if (newContent.getDataSource().getId() == getContent().getDataSource().getId()) {
Children children = getChildren(); // Find the image (if any) associated with the new content and
if (children != null) { // trigger a refresh if it matches the image wrapped by this node.
((ContentChildren) children).refreshChildren(); while ((parent = parent.getParent()) != null) {
children.getNodesCount(); if (parent.getId() == getContent().getId()) {
BaseChildFactory.post(getName(), new BaseChildFactory.RefreshKeysEvent());
break;
}
} }
} }
} }
} }
} catch (TskCoreException ex) { } catch (TskCoreException ex) {
// Do nothing. // 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())) { } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
if (evt.getNewValue() == null) { if (evt.getNewValue() == null) {

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2011-2018 Basis Technology Corp. * Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -449,26 +449,23 @@ 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 setName;
private final String typeName; private final String typeName;
private final Map<Long, BlackboardArtifact> artifactHits = new HashMap<>(); private final Map<Long, BlackboardArtifact> artifactHits = new HashMap<>();
private HitFactory(String setName, String typeName) { private HitFactory(String setName, String typeName) {
super(); super(typeName);
this.setName = setName; this.setName = setName;
this.typeName = typeName; this.typeName = typeName;
interestingResults.addObserver(this); interestingResults.addObserver(this);
} }
@Override @Override
protected boolean createKeys(List<Long> list) { protected List<BlackboardArtifact> makeKeys() {
if (skCase == null) {
return true;
}
if (skCase != null) {
interestingResults.getArtifactIds(setName, typeName).forEach((id) -> { interestingResults.getArtifactIds(setName, typeName).forEach((id) -> {
try { try {
if (!artifactHits.containsKey(id)) { if (!artifactHits.containsKey(id)) {
@ -480,20 +477,29 @@ public class InterestingHits implements AutopsyVisitableItem {
} }
}); });
list.addAll(artifactHits.keySet()); return new ArrayList<>(artifactHits.values());
}
return true; return Collections.emptyList();
} }
@Override @Override
protected Node createNodeForKey(Long l) { protected Node createNodeForKey(BlackboardArtifact art) {
BlackboardArtifact art = artifactHits.get(l); return new BlackboardArtifactNode(art);
return (null == art) ? null : new BlackboardArtifactNode(art);
} }
@Override @Override
public void update(Observable o, Object arg) { public void update(Observable o, Object arg) {
refresh(true); refresh(true);
} }
@Override
protected void onAdd() {
// No-op
}
@Override
protected void onRemove() {
// No-op
}
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2011-2018 Basis Technology Corp. * Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * 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.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
@ -34,7 +35,6 @@ import java.util.Observable;
import java.util.Observer; import java.util.Observer;
import java.util.Set; import java.util.Set;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.openide.nodes.ChildFactory; import org.openide.nodes.ChildFactory;
import org.openide.nodes.Children; 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.Case;
import org.sleuthkit.autopsy.casemodule.CasePreferences; import org.sleuthkit.autopsy.casemodule.CasePreferences;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import static org.sleuthkit.autopsy.datamodel.Bundle.*; import static org.sleuthkit.autopsy.datamodel.Bundle.*;
import org.sleuthkit.autopsy.ingest.IngestManager; 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"; private static final String DEFAULT_INSTANCE_NAME = "DEFAULT_INSTANCE_NAME";
/** /**
* query attributes table for the ones that we need for the tree * query attributes table for the ones that we need for the tree
*/ */
@ -510,9 +508,11 @@ public class KeywordHits implements AutopsyVisitableItem {
} }
private abstract class KWHitsNodeBase extends DisplayableItemNode implements Observer { 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); super(children, lookup);
this.displayName = displayName;
} }
private KWHitsNodeBase(Children children) { private KWHitsNodeBase(Children children) {
@ -530,7 +530,7 @@ public class KeywordHits implements AutopsyVisitableItem {
} }
final void updateDisplayName() { final void updateDisplayName() {
super.setDisplayName(getName() + " (" + countTotalDescendants() + ")"); super.setDisplayName(displayName + " (" + countTotalDescendants() + ")");
} }
abstract int countTotalDescendants(); abstract int countTotalDescendants();
@ -545,7 +545,7 @@ public class KeywordHits implements AutopsyVisitableItem {
private final String listName; private final String listName;
private ListNode(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); super.setName(listName);
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/keyword_hits.png"); //NON-NLS this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/keyword_hits.png"); //NON-NLS
this.listName = listName; 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 * Represents the search term or regexp that user searched for
*/ */
@ -640,8 +658,16 @@ public class KeywordHits implements AutopsyVisitableItem {
private final String keyword; private final String keyword;
private TermNode(String setName, String keyword) { private TermNode(String setName, String keyword) {
super(Children.create(new RegExpInstancesFactory(setName, keyword), true), Lookups.singleton(keyword)); super(Children.create(createChildFactory(setName, keyword), true), Lookups.singleton(keyword), keyword);
super.setName(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.setName = setName;
this.keyword = keyword; this.keyword = keyword;
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/keyword_hits.png"); //NON-NLS 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 * Creates the nodes for a given regexp that represent the specific terms
* that were found * that were found
*/ */
private class RegExpInstancesFactory extends DetachableObserverChildFactory<RegExpInstanceKey> { private class RegExpInstancesFactory extends DetachableObserverChildFactory<String> {
private final String keyword; private final String keyword;
private final String setName; private final String setName;
@ -744,35 +736,17 @@ public class KeywordHits implements AutopsyVisitableItem {
} }
@Override @Override
protected boolean createKeys(List<RegExpInstanceKey> list) { protected boolean createKeys(List<String> list) {
List<String> instances = keywordResults.getKeywordInstances(setName, keyword); list.addAll(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()));
}
return true; return true;
} }
@Override @Override
protected Node createNodeForKey(RegExpInstanceKey key) { protected Node createNodeForKey(String key) {
if (key.isRegExp()) { return new RegExpInstanceNode(setName, keyword, key);
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());
} }
} }
}
/** /**
* Represents a specific term that was found from a regexp * Represents a specific term that was found from a regexp
*/ */
@ -783,8 +757,16 @@ public class KeywordHits implements AutopsyVisitableItem {
private final String instance; private final String instance;
private RegExpInstanceNode(String setName, String keyword, String instance) { private RegExpInstanceNode(String setName, String keyword, String instance) {
super(Children.create(new HitsFactory(setName, keyword, instance), true), Lookups.singleton(instance)); super(Children.create(new HitsFactory(setName, keyword, instance), true), Lookups.singleton(instance), instance);
super.setName(instance); //the instance represents the name of the keyword hit at this point as the keyword is the regex
/**
* 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.setName = setName;
this.keyword = keyword; this.keyword = keyword;
this.instance = instance; this.instance = instance;
@ -837,7 +819,7 @@ public class KeywordHits implements AutopsyVisitableItem {
/** /**
* Create a blackboard node for the given Keyword Hit artifact * Create a blackboard node for the given Keyword Hit artifact
* *
* @param artifactId * @param art
* *
* @return Node or null on error * @return Node or null on error
*/ */
@ -850,14 +832,13 @@ public class KeywordHits implements AutopsyVisitableItem {
"KeywordHits.createNodeForKey.chgTime.name=ChangeTime", "KeywordHits.createNodeForKey.chgTime.name=ChangeTime",
"KeywordHits.createNodeForKey.chgTime.displayName=Change Time", "KeywordHits.createNodeForKey.chgTime.displayName=Change Time",
"KeywordHits.createNodeForKey.chgTime.desc=Change Time"}) "KeywordHits.createNodeForKey.chgTime.desc=Change Time"})
private BlackboardArtifactNode createBlackboardArtifactNode(Long artifactId) { private BlackboardArtifactNode createBlackboardArtifactNode(BlackboardArtifact art) {
if (skCase == null) { if (skCase == null) {
return null; return null;
} }
try { BlackboardArtifactNode n = new BlackboardArtifactNode(art); //NON-NLS
BlackboardArtifact art = skCase.getBlackboardArtifact(artifactId);
BlackboardArtifactNode n = new BlackboardArtifactNode(art);
// The associated file should be available through the Lookup that // The associated file should be available through the Lookup that
// gets created when the BlackboardArtifactNode is constructed. // gets created when the BlackboardArtifactNode is constructed.
AbstractFile file = n.getLookup().lookup(AbstractFile.class); AbstractFile file = n.getLookup().lookup(AbstractFile.class);
@ -870,14 +851,13 @@ public class KeywordHits implements AutopsyVisitableItem {
} }
} }
/* /*
* It is possible to get a keyword hit on artifacts generated for * It is possible to get a keyword hit on artifacts generated for the
* the underlying image in which case MAC times are not * underlying image in which case MAC times are not
* available/applicable/useful. * available/applicable/useful.
*/ */
if (file == null) { if (file == null) {
return n; return n;
} }
n.addNodeProperty(new NodeProperty<>( n.addNodeProperty(new NodeProperty<>(
KeywordHits_createNodeForKey_modTime_name(), KeywordHits_createNodeForKey_modTime_name(),
KeywordHits_createNodeForKey_modTime_displayName(), KeywordHits_createNodeForKey_modTime_displayName(),
@ -894,37 +874,68 @@ public class KeywordHits implements AutopsyVisitableItem {
KeywordHits_createNodeForKey_chgTime_desc(), KeywordHits_createNodeForKey_chgTime_desc(),
ContentUtils.getStringTime(file.getCtime(), file))); ContentUtils.getStringTime(file.getCtime(), file)));
return n; return n;
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "TSK Exception occurred", ex); //NON-NLS
}
return null;
} }
/** /**
* Creates nodes for individual files that had hits * 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 keyword;
private final String setName; private final String setName;
private final String instance; private final String instance;
private final Map<Long, BlackboardArtifact> artifactHits = new HashMap<>();
private HitsFactory(String setName, String keyword, String instance) { 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.setName = setName;
this.keyword = keyword; this.keyword = keyword;
this.instance = instance; this.instance = instance;
} }
@Override @Override
protected boolean createKeys(List<Long> list) { protected List<BlackboardArtifact> makeKeys() {
list.addAll(keywordResults.getArtifactIds(setName, keyword, instance)); if (skCase != null) {
return true; 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 @Override
protected Node createNodeForKey(Long artifactId) { protected Node createNodeForKey(BlackboardArtifact art) {
return createBlackboardArtifactNode(artifactId); 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 * Autopsy Forensic Browser
* *
* Copyright 2011-2017 Basis Technology Corp. * Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * 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.Collection;
import java.util.Collections; 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 * Children implementation for the root node of a ContentNode tree. Accepts a
* list of root Content objects for the tree. * 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 Collection<? extends Object> contentKeys;
private final CreateAutopsyNodeVisitor createAutopsyNodeVisitor = new CreateAutopsyNodeVisitor();
/** /**
* @param contentKeys root Content objects for the Node tree * @param contentKeys root Content objects for the Node tree
@ -56,4 +62,120 @@ public class RootContentChildren extends AbstractContentChildren<Object> {
public void refreshContentKeys() { public void refreshContentKeys() {
contentKeys.forEach(this::refreshKey); 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 * Autopsy Forensic Browser
* *
* Copyright 2011-2014 Basis Technology Corp. * Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * 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.ArrayList;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.List; import java.util.List;
import java.util.logging.Level;
import javax.swing.Action; import javax.swing.Action;
import org.openide.nodes.Children;
import org.openide.nodes.Sheet; import org.openide.nodes.Sheet;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case; 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.ExplorerNodeActionVisitor;
import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction;
import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.IngestManager;
@ -43,6 +45,7 @@ import org.sleuthkit.autopsy.directorytree.FileSystemDetailsAction;
* root directory of a file system * root directory of a file system
*/ */
public class VolumeNode extends AbstractContentNode<Volume> { 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 * 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) { if (parent != null) {
// Is this a new carved file? // Is this a new carved file?
if (parent.getName().equals(VirtualDirectory.NAME_CARVED)) { if (parent.getName().equals(VirtualDirectory.NAME_CARVED)) {
// Was this new carved file produced from this volume? // Is this new carved file for this data source?
if (parent.getParent().getId() == getContent().getId()) { if (newContent.getDataSource().getId() == getContent().getDataSource().getId()) {
Children children = getChildren(); // Find the volume (if any) associated with the new content and
if (children != null) { // trigger a refresh if it matches the volume wrapped by this node.
((ContentChildren) children).refreshChildren(); while ((parent = parent.getParent()) != null) {
children.getNodesCount(); if (parent.getId() == getContent().getId()) {
BaseChildFactory.post(getName(), new BaseChildFactory.RefreshKeysEvent());
break;
}
} }
} }
} }
} }
} catch (TskCoreException ex) { } catch (TskCoreException ex) {
// Do nothing. // Do nothing.
} catch (NoSuchEventBusException ex) {
logger.log(Level.WARNING, eventType, ex);
} }
} else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) { } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
if (evt.getNewValue() == null) { if (evt.getNewValue() == null) {

View File

@ -26,8 +26,6 @@ import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.prefs.PreferenceChangeEvent;
import java.util.prefs.PreferenceChangeListener;
import javax.swing.AbstractAction; import javax.swing.AbstractAction;
import javax.swing.Action; import javax.swing.Action;
import org.openide.explorer.ExplorerManager; 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.AddContentTagAction;
import org.sleuthkit.autopsy.actions.DeleteFileBlackboardArtifactTagAction; import org.sleuthkit.autopsy.actions.DeleteFileBlackboardArtifactTagAction;
import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction; import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction;
import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.AbstractFilePropertyType; 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.LocalFile;
import org.sleuthkit.datamodel.LocalDirectory; import org.sleuthkit.datamodel.LocalDirectory;
import org.sleuthkit.datamodel.SlackFile; import org.sleuthkit.datamodel.SlackFile;
import org.sleuthkit.datamodel.TskData;
import org.sleuthkit.datamodel.TskException; import org.sleuthkit.datamodel.TskException;
import org.sleuthkit.datamodel.VirtualDirectory; import org.sleuthkit.datamodel.VirtualDirectory;
import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; 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 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(); static private final DisplayableItemNodeVisitor<List<Action>> getActionsDIV = new GetPopupActionsDisplayableItemNodeVisitor();
private final DisplayableItemNodeVisitor<AbstractAction> getPreferredActionsDIV = new GetPreferredActionsDisplayableItemNodeVisitor(); private final DisplayableItemNodeVisitor<AbstractAction> getPreferredActionsDIV = new GetPreferredActionsDisplayableItemNodeVisitor();
@ -155,24 +124,6 @@ public class DataResultFilterNode extends FilterNode {
this.sourceEm = em; 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 * Right click action for the nodes that we want to pass to the directory
* table and the output view. * table and the output view.
@ -294,10 +245,7 @@ public class DataResultFilterNode extends FilterNode {
private static class DataResultFilterChildren extends FilterNode.Children { private static class DataResultFilterChildren extends FilterNode.Children {
private final ExplorerManager sourceEm; private final ExplorerManager sourceEm;
private final boolean filterArtifacts; // display message artifacts in the DataSource subtree
private boolean filterKnown;
private boolean filterSlack;
private boolean filterArtifacts; // display message artifacts in the DataSource subtree
/** /**
* the constructor * the constructor
@ -305,46 +253,13 @@ public class DataResultFilterNode extends FilterNode {
private DataResultFilterChildren(Node arg, ExplorerManager sourceEm) { private DataResultFilterChildren(Node arg, ExplorerManager sourceEm) {
super(arg); super(arg);
this.filterArtifacts = false; filterArtifacts = SelectionContext.getSelectionContext(arg).equals(SelectionContext.DATA_SOURCES);
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;
}
private DataResultFilterChildren(Node arg, ExplorerManager sourceEm, boolean filterKnown, boolean filterSlack) {
super(arg);
this.filterKnown = filterKnown;
this.filterSlack = filterSlack;
this.sourceEm = sourceEm; this.sourceEm = sourceEm;
} }
@Override @Override
protected Node[] createNodes(Node key) { 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 // filter out all non-message artifacts, if displaying the results from the Data Source tree
BlackboardArtifact art = key.getLookup().lookup(BlackboardArtifact.class); BlackboardArtifact art = key.getLookup().lookup(BlackboardArtifact.class);
if (art != null if (art != null
@ -354,9 +269,8 @@ public class DataResultFilterNode extends FilterNode {
return new Node[]{}; 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") @NbBundle.Messages("DataResultFilterNode.viewSourceArtifact.text=View Source Result")

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2011 Basis Technology Corp. * Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -22,7 +22,7 @@ import java.awt.event.ActionEvent;
import javax.swing.AbstractAction; import javax.swing.AbstractAction;
import org.sleuthkit.autopsy.corecomponents.DataContentTopComponent; import org.sleuthkit.autopsy.corecomponents.DataContentTopComponent;
import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode;
import org.sleuthkit.autopsy.datamodel.RootContentChildren; import org.sleuthkit.autopsy.datamodel.CreateSleuthkitNodeVisitor;
import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Content;
/** /**
@ -30,7 +30,7 @@ import org.sleuthkit.datamodel.Content;
*/ */
class ViewAssociatedContentAction extends AbstractAction { class ViewAssociatedContentAction extends AbstractAction {
private Content content; private final Content content;
public ViewAssociatedContentAction(String title, BlackboardArtifactNode node) { public ViewAssociatedContentAction(String title, BlackboardArtifactNode node) {
super(title); super(title);
@ -39,6 +39,6 @@ class ViewAssociatedContentAction extends AbstractAction {
@Override @Override
public void actionPerformed(ActionEvent e) { 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.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; 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.hwpf.usermodel.Picture;
import org.apache.poi.hslf.usermodel.HSLFPictureData; import org.apache.poi.hslf.usermodel.HSLFPictureData;
import org.apache.poi.hslf.usermodel.HSLFSlideShow; 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.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hwpf.HWPFDocument; import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.model.PicturesTable; import org.apache.poi.hwpf.model.PicturesTable;
import org.apache.poi.sl.usermodel.PictureData.PictureType; import org.apache.poi.sl.usermodel.PictureData.PictureType;
import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.util.RecordFormatException;
import org.apache.tika.config.TikaConfig; import org.apache.tika.config.TikaConfig;
import org.apache.tika.detect.Detector; import org.apache.tika.detect.Detector;
import org.apache.tika.exception.TikaException; 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 * 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 FileManager fileManager;
private final IngestServices services; 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 final IngestJobContext context;
private String parentFileName; private String parentFileName;
private final String UNKNOWN_IMAGE_NAME_PREFIX = "image_"; //NON-NLS private final String UNKNOWN_IMAGE_NAME_PREFIX = "image_"; //NON-NLS
@ -101,7 +101,8 @@ class MSOfficeEmbeddedContentExtractor {
PPT("application/vnd.ms-powerpoint"), //NON-NLS PPT("application/vnd.ms-powerpoint"), //NON-NLS
PPTX("application/vnd.openxmlformats-officedocument.presentationml.presentation"), //NON-NLS PPTX("application/vnd.openxmlformats-officedocument.presentationml.presentation"), //NON-NLS
XLS("application/vnd.ms-excel"), //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; private final String mimeType;
@ -116,7 +117,7 @@ class MSOfficeEmbeddedContentExtractor {
} }
private SupportedExtractionFormats abstractFileExtractionFormat; 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.fileManager = Case.getCurrentCaseThrows().getServices().getFileManager();
this.services = IngestServices.getInstance(); this.services = IngestServices.getInstance();
@ -190,6 +191,9 @@ class MSOfficeEmbeddedContentExtractor {
case XLS: case XLS:
listOfExtractedImages = extractImagesFromXls(abstractFile); listOfExtractedImages = extractImagesFromXls(abstractFile);
break; break;
case PDF:
listOfExtractedImages = extractEmbeddedContentFromPDF(abstractFile);
break;
default: default:
break; 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. * 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 //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 ConcurrentHashMap<Long, ConcurrentHashMap<Long, Archive>> mapOfDepthTrees = new ConcurrentHashMap<>();
private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter(); private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter();
private MSOfficeEmbeddedContentExtractor officeExtractor; private DocumentEmbeddedContentExtractor documentExtractor;
private SevenZipExtractor archiveExtractor; private SevenZipExtractor archiveExtractor;
private FileTypeDetector fileTypeDetector; private FileTypeDetector fileTypeDetector;
private long jobId; private long jobId;
@ -115,10 +115,10 @@ public final class EmbeddedFileExtractorIngestModule extends FileIngestModuleAda
} }
/* /*
* Construct an embedded content extractor for processing Microsoft * Construct an embedded content extractor for processing Microsoft
* Office documents. * Office documents and PDF documents.
*/ */
try { try {
this.officeExtractor = new MSOfficeEmbeddedContentExtractor(context, fileTypeDetector, moduleDirRelative, moduleDirAbsolute); this.documentExtractor = new DocumentEmbeddedContentExtractor(context, fileTypeDetector, moduleDirRelative, moduleDirAbsolute);
} catch (NoCurrentCaseException ex) { } catch (NoCurrentCaseException ex) {
throw new IngestModuleException(Bundle.EmbeddedFileExtractorIngestModule_UnableToGetMSOfficeExtractor_errMsg(), ex); throw new IngestModuleException(Bundle.EmbeddedFileExtractorIngestModule_UnableToGetMSOfficeExtractor_errMsg(), ex);
} }
@ -155,8 +155,8 @@ public final class EmbeddedFileExtractorIngestModule extends FileIngestModuleAda
*/ */
if (archiveExtractor.isSevenZipExtractionSupported(abstractFile)) { if (archiveExtractor.isSevenZipExtractionSupported(abstractFile)) {
archiveExtractor.unpack(abstractFile, mapOfDepthTrees.get(jobId)); archiveExtractor.unpack(abstractFile, mapOfDepthTrees.get(jobId));
} else if (officeExtractor.isContentExtractionSupported(abstractFile)) { } else if (documentExtractor.isContentExtractionSupported(abstractFile)) {
officeExtractor.extractEmbeddedContent(abstractFile); documentExtractor.extractEmbeddedContent(abstractFile);
} }
return ProcessResult.OK; 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

@ -109,7 +109,9 @@ public class FileTypeDetector {
* Tika, and Autopsy file type definitions take precendence over Tika. * Tika, and Autopsy file type definitions take precendence over Tika.
* *
* @throws FileTypeDetectorInitException If an initialization error occurs, * @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 { public FileTypeDetector() throws FileTypeDetectorInitException {
try { try {
@ -223,7 +225,7 @@ public class FileTypeDetector {
ReadContentInputStream stream = new ReadContentInputStream(file); ReadContentInputStream stream = new ReadContentInputStream(file);
try (TikaInputStream tikaInputStream = TikaInputStream.get(stream)) { 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. * Remove the Tika suffix from the MIME type name.
@ -234,6 +236,21 @@ public class FileTypeDetector {
*/ */
mimeType = removeOptionalParameter(mimeType); 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 * We cannot trust Tika's audio/mpeg mimetype. Lets verify the
* first two bytes and confirm it is not 0xffff. Details in * first two bytes and confirm it is not 0xffff. Details in
@ -275,6 +292,7 @@ public class FileTypeDetector {
* first 4 bits. * first 4 bits.
* *
* @param x byte * @param x byte
*
* @return Flag indicating the byte if 0xFF * @return Flag indicating the byte if 0xFF
*/ */
private boolean byteIs0xFF(byte x) { private boolean byteIs0xFF(byte x) {
@ -287,6 +305,7 @@ public class FileTypeDetector {
* @param file Abstract file to read * @param file Abstract file to read
* @param offset Offset to begin reading * @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 * @return Byte array of size n
* *
* @throws TskCoreException * @throws TskCoreException

View File

@ -198,6 +198,7 @@ final class HtmlTextExtractor implements TextExtractor {
renderer.setIncludeHyperlinkURLs(false); renderer.setIncludeHyperlinkURLs(false);
renderer.setDecorateFontStyles(false); renderer.setDecorateFontStyles(false);
renderer.setIncludeAlternateText(false); renderer.setIncludeAlternateText(false);
renderer.setMaxLineLength(0); // don't force wrapping
return new StringReader(renderer.toString()); return new StringReader(renderer.toString());
} catch (IOException ex) { } catch (IOException ex) {
logger.log(Level.WARNING, "Error extracting HTML from content.", 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.exception.TikaException;
import org.apache.tika.metadata.Metadata; import org.apache.tika.metadata.Metadata;
import org.apache.tika.parser.AutoDetectParser; import org.apache.tika.parser.AutoDetectParser;
import org.apache.tika.parser.EmptyParser;
import org.apache.tika.parser.ParseContext; import org.apache.tika.parser.ParseContext;
import org.apache.tika.parser.Parser; import org.apache.tika.parser.Parser;
import org.apache.tika.parser.ParsingReader; import org.apache.tika.parser.ParsingReader;
@ -125,6 +126,16 @@ final class TikaTextExtractor implements TextExtractor {
"application/x-z", //NON-NLS "application/x-z", //NON-NLS
"application/x-compress"); //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 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()); private static final Logger AUTOPSY_LOGGER = Logger.getLogger(TikaTextExtractor.class.getName());
@ -184,7 +195,14 @@ final class TikaTextExtractor implements TextExtractor {
InputStream stream = null; InputStream stream = null;
ParseContext parseContext = new ParseContext(); ParseContext parseContext = new ParseContext();
//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); parseContext.set(Parser.class, parser);
}
if (ocrEnabled() && content instanceof AbstractFile) { if (ocrEnabled() && content instanceof AbstractFile) {
AbstractFile file = ((AbstractFile) content); AbstractFile file = ((AbstractFile) content);
@ -516,11 +534,11 @@ final class TikaTextExtractor implements TextExtractor {
if (context != null) { if (context != null) {
ImageConfig configInstance = context.lookup(ImageConfig.class); ImageConfig configInstance = context.lookup(ImageConfig.class);
if (configInstance != null) { if (configInstance != null) {
if(Objects.nonNull(configInstance.getOCREnabled())) { if (Objects.nonNull(configInstance.getOCREnabled())) {
this.tesseractOCREnabled = configInstance.getOCREnabled(); this.tesseractOCREnabled = configInstance.getOCREnabled();
} }
if(Objects.nonNull(configInstance.getOCRLanguages())) { if (Objects.nonNull(configInstance.getOCRLanguages())) {
this.languagePacks = formatLanguagePacks(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.autopsy.texttranslation.TranslationException;
import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Content;
import java.util.List; import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.sleuthkit.autopsy.coreutils.PlatformUtil; import org.sleuthkit.autopsy.coreutils.PlatformUtil;
import org.sleuthkit.autopsy.texttranslation.ui.TranslationContentPanel.DisplayDropdownOptions; 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) @ServiceProvider(service = TextViewer.class, position = 4)
public final class TranslatedTextViewer implements TextViewer { 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_ENABLED = true;
private static final boolean OCR_DISABLED = false; private static final boolean OCR_DISABLED = false;
private static final int MAX_SIZE_1MB = 1024000; private static final int MAX_SIZE_1MB = 1024000;
@ -169,16 +173,20 @@ public final class TranslatedTextViewer implements TextViewer {
try { try {
return getFileText(node); return getFileText(node);
} catch (IOException ex) { } catch (IOException ex) {
logger.log(Level.WARNING, "Error getting text", ex);
return Bundle.TranslatedContentViewer_errorMsg(); return Bundle.TranslatedContentViewer_errorMsg();
} catch (TextExtractor.InitReaderException ex) { } catch (TextExtractor.InitReaderException ex) {
logger.log(Level.WARNING, "Error getting text", ex);
return Bundle.TranslatedContentViewer_errorExtractingText(); return Bundle.TranslatedContentViewer_errorExtractingText();
} }
} else { } else {
try { try {
return translate(getFileText(node)); return translate(getFileText(node));
} catch (IOException ex) { } catch (IOException ex) {
logger.log(Level.WARNING, "Error translating text", ex);
return Bundle.TranslatedContentViewer_errorMsg(); return Bundle.TranslatedContentViewer_errorMsg();
} catch (TextExtractor.InitReaderException ex) { } catch (TextExtractor.InitReaderException ex) {
logger.log(Level.WARNING, "Error translating text", ex);
return Bundle.TranslatedContentViewer_errorExtractingText(); return Bundle.TranslatedContentViewer_errorExtractingText();
} }
} }
@ -247,7 +255,8 @@ public final class TranslatedTextViewer implements TextViewer {
} catch (NoServiceProviderException ex) { } catch (NoServiceProviderException ex) {
return Bundle.TranslatedContentViewer_noServiceProvider(); return Bundle.TranslatedContentViewer_noServiceProvider();
} catch (TranslationException ex) { } 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 //Correct for UTF-8
byte[] resultInUTF8Bytes = result.getBytes("UTF8"); 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"); return new String(trimTo1MB, "UTF-8");
} }
@ -333,7 +342,8 @@ public final class TranslatedTextViewer implements TextViewer {
bytesRead += read; 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();
} }
/** /**