Merge remote-tracking branch 'upstream/develop' into 7357_imageGalleryDeadlock

This commit is contained in:
apriestman 2021-03-10 10:16:37 -05:00
commit a962ab34c5
87 changed files with 6246 additions and 2241 deletions

View File

@ -39,6 +39,7 @@ import java.sql.SQLException;
import java.sql.Statement;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
@ -81,8 +82,14 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
import org.sleuthkit.autopsy.casemodule.events.DataSourceAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.DataSourceDeletedEvent;
import org.sleuthkit.autopsy.casemodule.events.DataSourceNameChangedEvent;
import org.sleuthkit.autopsy.casemodule.events.HostsAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.HostsChangedEvent;
import org.sleuthkit.autopsy.casemodule.events.HostsRemovedEvent;
import org.sleuthkit.autopsy.casemodule.events.OsAccountAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.OsAccountChangedEvent;
import org.sleuthkit.autopsy.casemodule.events.PersonsAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.PersonsChangedEvent;
import org.sleuthkit.autopsy.casemodule.events.PersonsRemovedEvent;
import org.sleuthkit.autopsy.casemodule.events.ReportAddedEvent;
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData.CaseNodeDataException;
import org.sleuthkit.autopsy.casemodule.multiusercases.CoordinationServiceUtils;
@ -130,11 +137,19 @@ import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.FileSystem;
import org.sleuthkit.datamodel.Host;
import org.sleuthkit.datamodel.HostManager.HostsCreationEvent;
import org.sleuthkit.datamodel.HostManager.HostsUpdateEvent;
import org.sleuthkit.datamodel.HostManager.HostsDeletionEvent;
import org.sleuthkit.datamodel.Image;
import org.sleuthkit.datamodel.OsAccount;
import org.sleuthkit.datamodel.OsAccountManager;
import org.sleuthkit.datamodel.OsAccountManager.OsAccountsCreationEvent;
import org.sleuthkit.datamodel.OsAccountManager.OsAccountsUpdateEvent;
import org.sleuthkit.datamodel.Person;
import org.sleuthkit.datamodel.PersonManager.PersonsCreationEvent;
import org.sleuthkit.datamodel.PersonManager.PersonsUpdateEvent;
import org.sleuthkit.datamodel.PersonManager.PersonsDeletionEvent;
import org.sleuthkit.datamodel.Report;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TimelineManager;
@ -426,8 +441,38 @@ public class Case {
* OSAccount associated with the current case has changed.
* Call getOsAccount to get the changed account;
*/
OS_ACCOUNT_CHANGED;
OS_ACCOUNT_CHANGED,
/**
* Hosts associated with the current case added.
*/
HOSTS_ADDED,
/**
* Hosts associated with the current case has changed.
*/
HOSTS_CHANGED,
/**
* Hosts associated with the current case has been deleted.
*/
HOSTS_DELETED,
/**
* Persons associated with the current case added.
*/
PERSONS_ADDED,
/**
* Persons associated with the current case has changed.
*/
PERSONS_CHANGED,
/**
* Persons associated with the current case has been deleted.
*/
PERSONS_DELETED
;
};
/**
@ -474,6 +519,78 @@ public class Case {
eventPublisher.publish(new OsAccountChangedEvent(account));
}
}
/**
* Publishes an autopsy event from the sleuthkit HostCreationEvent
* indicating that hosts have been created.
*
* @param event The sleuthkit event for the creation of hosts.
*/
@Subscribe
public void publishHostsAddedEvent(HostsCreationEvent event) {
eventPublisher.publish(new HostsAddedEvent(
event == null ? Collections.emptyList() : event.getHosts()));
}
/**
* Publishes an autopsy event from the sleuthkit HostUpdateEvent
* indicating that hosts have been updated.
*
* @param event The sleuthkit event for the updating of hosts.
*/
@Subscribe
public void publishHostsChangedEvent(HostsUpdateEvent event) {
eventPublisher.publish(new HostsChangedEvent(
event == null ? Collections.emptyList() : event.getHosts()));
}
/**
* Publishes an autopsy event from the sleuthkit HostDeletedEvent
* indicating that hosts have been deleted.
*
* @param event The sleuthkit event for the deleting of hosts.
*/
@Subscribe
public void publishHostsDeletedEvent(HostsDeletionEvent event) {
eventPublisher.publish(new HostsRemovedEvent(
event == null ? Collections.emptyList() : event.getHosts()));
}
/**
* Publishes an autopsy event from the sleuthkit PersonCreationEvent
* indicating that persons have been created.
*
* @param event The sleuthkit event for the creation of persons.
*/
@Subscribe
public void publishPersonsAddedEvent(PersonsCreationEvent event) {
eventPublisher.publish(new PersonsAddedEvent(
event == null ? Collections.emptyList() : event.getPersons()));
}
/**
* Publishes an autopsy event from the sleuthkit PersonUpdateEvent
* indicating that persons have been updated.
*
* @param event The sleuthkit event for the updating of persons.
*/
@Subscribe
public void publishPersonsChangedEvent(PersonsUpdateEvent event) {
eventPublisher.publish(new PersonsChangedEvent(
event == null ? Collections.emptyList() : event.getPersons()));
}
/**
* Publishes an autopsy event from the sleuthkit PersonDeletedEvent
* indicating that persons have been deleted.
*
* @param event The sleuthkit event for the deleting of persons.
*/
@Subscribe
public void publishPersonsDeletedEvent(PersonsDeletionEvent event) {
eventPublisher.publish(new PersonsRemovedEvent(
event == null ? Collections.emptyList() : event.getPersons()));
}
}
/**
@ -1680,7 +1797,55 @@ public class Case {
public void notifyOsAccountChanged(OsAccount account) {
eventPublisher.publish(new OsAccountChangedEvent(account));
}
/**
* Notify via an autopsy event that a host has been added.
* @param host The host that has been added.
*/
public void notifyHostAdded(Host host) {
eventPublisher.publish(new HostsAddedEvent(Collections.singletonList(host)));
}
/**
* Notify via an autopsy event that a host has been changed.
* @param newValue The host that has been updated.
*/
public void notifyHostChanged(Host newValue) {
eventPublisher.publish(new HostsChangedEvent(Collections.singletonList(newValue)));
}
/**
* Notify via an autopsy event that a host has been deleted.
* @param host The host that has been deleted.
*/
public void notifyHostDeleted(Host host) {
eventPublisher.publish(new HostsRemovedEvent(Collections.singletonList(host)));
}
/**
* Notify via an autopsy event that a person has been added.
* @param person The person that has been added.
*/
public void notifyPersonAdded(Person person) {
eventPublisher.publish(new PersonsAddedEvent(Collections.singletonList(person)));
}
/**
* Notify via an autopsy event that a person has been changed.
* @param newValue The person that has been updated.
*/
public void notifyPersonChanged(Person newValue) {
eventPublisher.publish(new PersonsChangedEvent(Collections.singletonList(newValue)));
}
/**
* Notify via an autopsy event that a person has been deleted.
* @param person The person that has been deleted.
*/
public void notifyPersonDeleted(Person person) {
eventPublisher.publish(new PersonsRemovedEvent(Collections.singletonList(person)));
}
/**
* Adds a report to the case.
*

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013-2018 Basis Technology Corp.
* Copyright 2013-2021 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -51,8 +51,7 @@ import org.sleuthkit.datamodel.TskCoreException;
* independently of the wizard.
*/
@ServiceProviders(value = {
@ServiceProvider(service = DataSourceProcessor.class)
,
@ServiceProvider(service = DataSourceProcessor.class),
@ServiceProvider(service = AutoIngestDataSourceProcessor.class)}
)
public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSourceProcessor {
@ -83,7 +82,6 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour
private String sha1;
private String sha256;
private Host host = null;
private boolean setDataSourceOptionsCalled;
static {
filtersList.add(allFilter);
@ -184,8 +182,8 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour
@Override
public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
run(null, progressMonitor, callback);
}
}
/**
* Adds a data source to the case database using a background task in a
* separate thread and the settings provided by the selection and
@ -208,71 +206,71 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour
this.host = host;
try {
image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(),
new String[]{imagePath}, sectorSize, timeZone, md5, sha1, sha256, deviceId, this.host);
new String[]{imagePath}, sectorSize, timeZone, md5, sha1, sha256, deviceId, this.host);
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error adding data source with path " + imagePath + " to database", ex);
final List<String> errors = new ArrayList<>();
errors.add(ex.getMessage());
errors.add(ex.getMessage());
callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>());
return;
}
doAddImageProcess(deviceId, imagePath, sectorSize, timeZone, ignoreFatOrphanFiles, md5, sha1, sha256, progressMonitor, callback);
}
/**
* Adds a data source to the case database using a background task in a
* separate thread and the settings provided by the selection and
* configuration panel. Files found during ingest will be sent directly to the
* IngestStream provided. Returns as soon as the background task is started.
* The background task uses a callback object to signal task completion and
* return results.
* configuration panel. Files found during ingest will be sent directly to
* the IngestStream provided. Returns as soon as the background task is
* started. The background task uses a callback object to signal task
* completion and return results.
*
* This method should not be called unless isPanelValid returns true, and
* This method should not be called unless isPanelValid returns true, and
* should only be called for DSPs that support ingest streams.
*
* @param settings The ingest job settings.
* @param progress Progress monitor that will be used by the
* background task to report progress.
* @param callBack Callback that will be used by the background task
* to return results.
*
* @param settings The ingest job settings.
* @param progress Progress monitor that will be used by the background task
* to report progress.
* @param callBack Callback that will be used by the background task to
* return results.
*/
@Override
public void runWithIngestStream(IngestJobSettings settings, DataSourceProcessorProgressMonitor progress,
public void runWithIngestStream(IngestJobSettings settings, DataSourceProcessorProgressMonitor progress,
DataSourceProcessorCallback callBack) {
runWithIngestStream(null, settings, progress, callBack);
}
}
/**
* Adds a data source to the case database using a background task in a
* separate thread and the settings provided by the selection and
* configuration panel. Files found during ingest will be sent directly to the
* IngestStream provided. Returns as soon as the background task is started.
* The background task uses a callback object to signal task completion and
* return results.
* configuration panel. Files found during ingest will be sent directly to
* the IngestStream provided. Returns as soon as the background task is
* started. The background task uses a callback object to signal task
* completion and return results.
*
* This method should not be called unless isPanelValid returns true, and
* This method should not be called unless isPanelValid returns true, and
* should only be called for DSPs that support ingest streams.
*
* @param host The host for this data source.
* @param settings The ingest job settings.
* @param progress Progress monitor that will be used by the
* background task to report progress.
* @param callBack Callback that will be used by the background task
* to return results.
*
* @param host The host for this data source.
* @param settings The ingest job settings.
* @param progress Progress monitor that will be used by the background task
* to report progress.
* @param callBack Callback that will be used by the background task to
* return results.
*/
@Override
public void runWithIngestStream(Host host, IngestJobSettings settings, DataSourceProcessorProgressMonitor progress,
public void runWithIngestStream(Host host, IngestJobSettings settings, DataSourceProcessorProgressMonitor progress,
DataSourceProcessorCallback callBack) {
// Read the settings from the wizard
readConfigSettings();
this.host = host;
// Set up the data source before creating the ingest stream
try {
image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(),
new String[]{imagePath}, sectorSize, timeZone, md5, sha1, sha256, deviceId, this.host);
new String[]{imagePath}, sectorSize, timeZone, md5, sha1, sha256, deviceId, this.host);
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error adding data source with path " + imagePath + " to database", ex);
final List<String> errors = new ArrayList<>();
@ -291,46 +289,43 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour
callBack.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>());
return;
}
doAddImageProcess(deviceId, imagePath, sectorSize, timeZone, ignoreFatOrphanFiles, md5, sha1, sha256, progress, callBack);
}
/**
* Store the options from the config panel.
*/
private void readConfigSettings() {
if (!setDataSourceOptionsCalled) {
configPanel.storeSettings();
deviceId = UUID.randomUUID().toString();
imagePath = configPanel.getContentPaths();
sectorSize = configPanel.getSectorSize();
timeZone = configPanel.getTimeZone();
ignoreFatOrphanFiles = configPanel.getNoFatOrphans();
md5 = configPanel.getMd5();
if (md5.isEmpty()) {
md5 = null;
}
sha1 = configPanel.getSha1();
if (sha1.isEmpty()) {
sha1 = null;
}
sha256 = configPanel.getSha256();
if (sha256.isEmpty()) {
sha256 = null;
}
configPanel.storeSettings();
deviceId = UUID.randomUUID().toString();
imagePath = configPanel.getContentPaths();
sectorSize = configPanel.getSectorSize();
timeZone = configPanel.getTimeZone();
ignoreFatOrphanFiles = configPanel.getNoFatOrphans();
md5 = configPanel.getMd5();
if (md5.isEmpty()) {
md5 = null;
}
sha1 = configPanel.getSha1();
if (sha1.isEmpty()) {
sha1 = null;
}
sha256 = configPanel.getSha256();
if (sha256.isEmpty()) {
sha256 = null;
}
}
/**
* Check if this DSP supports ingest streams.
*
*
* @return True if this DSP supports an ingest stream, false otherwise.
*/
@Override
public boolean supportsIngestStream() {
return true;
}
}
/**
* Adds a data source to the case database using a background task in a
@ -357,11 +352,11 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour
ingestStream = new DefaultIngestStream();
try {
image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(),
new String[]{imagePath}, sectorSize, timeZone, "", "", "", deviceId);
new String[]{imagePath}, sectorSize, timeZone, "", "", "", deviceId);
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error adding data source with path " + imagePath + " to database", ex);
final List<String> errors = new ArrayList<>();
errors.add(ex.getMessage());
errors.add(ex.getMessage());
callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>());
return;
}
@ -375,10 +370,10 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour
* selection and configuration panel. Returns as soon as the background task
* is started and uses the callback object to signal task completion and
* return results.
*
* The image should be loaded in the database and stored in "image"
* before calling this method. Additionally, an ingest stream should be initialized
* and stored in "ingestStream".
*
* The image should be loaded in the database and stored in "image" before
* calling this method. Additionally, an ingest stream should be initialized
* and stored in "ingestStream".
*
* @param deviceId An ASCII-printable identifier for the device
* associated with the data source that is
@ -400,28 +395,28 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour
*/
private void doAddImageProcess(String deviceId, String imagePath, int sectorSize, String timeZone, boolean ignoreFatOrphanFiles, String md5, String sha1, String sha256, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
// If the data source or ingest stream haven't been initialized, stop processing
if (ingestStream == null) {
String message = "Ingest stream was not initialized before running the add image process on " + imagePath;
logger.log(Level.SEVERE, message);
final List<String> errors = new ArrayList<>();
errors.add(message);
callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>());
return;
}
if (image == null) {
String message = "Image was not added to database before running the add image process on " + imagePath;
logger.log(Level.SEVERE, message);
final List<String> errors = new ArrayList<>();
errors.add(message);
callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>());
return;
}
// If the data source or ingest stream haven't been initialized, stop processing
if (ingestStream == null) {
String message = "Ingest stream was not initialized before running the add image process on " + imagePath;
logger.log(Level.SEVERE, message);
final List<String> errors = new ArrayList<>();
errors.add(message);
callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>());
return;
}
if (image == null) {
String message = "Image was not added to database before running the add image process on " + imagePath;
logger.log(Level.SEVERE, message);
final List<String> errors = new ArrayList<>();
errors.add(message);
callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>());
return;
}
AddImageTask.ImageDetails imageDetails = new AddImageTask.ImageDetails(deviceId, image, sectorSize, timeZone, ignoreFatOrphanFiles, md5, sha1, sha256, null);
addImageTask = new AddImageTask(imageDetails,
progressMonitor,
new StreamingAddDataSourceCallbacks(ingestStream),
AddImageTask.ImageDetails imageDetails = new AddImageTask.ImageDetails(deviceId, image, sectorSize, timeZone, ignoreFatOrphanFiles, md5, sha1, sha256, null);
addImageTask = new AddImageTask(imageDetails,
progressMonitor,
new StreamingAddDataSourceCallbacks(ingestStream),
new StreamingAddImageTaskCallback(ingestStream, callback));
new Thread(addImageTask).start();
}
@ -455,7 +450,6 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour
ignoreFatOrphanFiles = false;
host = null;
configPanel.reset();
setDataSourceOptionsCalled = false;
}
private static boolean isAcceptedByFiler(File file, List<FileFilter> filters) {
@ -488,56 +482,56 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour
// able to process the data source
return 100;
}
@Override
public void process(String deviceId, Path dataSourcePath, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callBack) {
process(deviceId, dataSourcePath, null, progressMonitor, callBack);
}
@Override
public void process(String deviceId, Path dataSourcePath, Host host, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callBack) {
// this method does not use the config panel
this.deviceId = deviceId;
this.imagePath = dataSourcePath.toString();
this.sectorSize = 0;
this.timeZone = Calendar.getInstance().getTimeZone().getID();
this.host = host;
this.ignoreFatOrphanFiles = false;
setDataSourceOptionsCalled = true;
ingestStream = new DefaultIngestStream();
try {
image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(),
new String[]{imagePath}, sectorSize, timeZone, "", "", "", deviceId, host);
new String[]{imagePath}, sectorSize, timeZone, "", "", "", deviceId, host);
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error adding data source with path " + imagePath + " to database", ex);
final List<String> errors = new ArrayList<>();
errors.add(ex.getMessage());
errors.add(ex.getMessage());
callBack.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>());
return;
}
doAddImageProcess(deviceId, dataSourcePath.toString(), sectorSize, timeZone, ignoreFatOrphanFiles, null, null, null, progressMonitor, callBack);
}
@Override
public IngestStream processWithIngestStream(String deviceId, Path dataSourcePath, IngestJobSettings settings, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callBack) {
return processWithIngestStream(deviceId, dataSourcePath, null, settings, progressMonitor, callBack);
}
@Override
public IngestStream processWithIngestStream(String deviceId, Path dataSourcePath, Host host, IngestJobSettings settings, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callBack) {
// this method does not use the config panel
this.deviceId = deviceId;
this.imagePath = dataSourcePath.toString();
this.sectorSize = 0;
this.timeZone = Calendar.getInstance().getTimeZone().getID();
this.host = host;
this.ignoreFatOrphanFiles = false;
setDataSourceOptionsCalled = true;
// Set up the data source before creating the ingest stream
try {
image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(),
new String[]{imagePath}, sectorSize, timeZone, md5, sha1, sha256, deviceId, host);
new String[]{imagePath}, sectorSize, timeZone, md5, sha1, sha256, deviceId, host);
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error adding data source with path " + imagePath + " to database", ex);
final List<String> errors = new ArrayList<>();
@ -555,34 +549,10 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour
errors.add(ex.getMessage());
callBack.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>());
return null;
}
}
doAddImageProcess(deviceId, dataSourcePath.toString(), sectorSize, timeZone, ignoreFatOrphanFiles, null, null, null, progressMonitor, callBack);
return ingestStream;
}
/**
* Sets the configuration of the data source processor without using the
* selection and configuration panel.
*
* @param imagePath Path to the image file.
* @param timeZone The time zone to use when processing dates
* and times for the image, obtained from
* java.util.TimeZone.getID.
* @param ignoreFatOrphanFiles Whether to parse orphans if the image has a
* FAT filesystem.
*
* @deprecated Use the provided overload of the run method instead.
*/
@Deprecated
public void setDataSourceOptions(String imagePath, String timeZone, boolean ignoreFatOrphanFiles) {
this.deviceId = UUID.randomUUID().toString();
this.imagePath = imagePath;
this.sectorSize = 0;
this.timeZone = Calendar.getInstance().getTimeZone().getID();
this.ignoreFatOrphanFiles = ignoreFatOrphanFiles;
this.host = null;
setDataSourceOptionsCalled = true;
}
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013-2018 Basis Technology Corp.
* Copyright 2013-2021 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -19,7 +19,6 @@
package org.sleuthkit.autopsy.casemodule;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.UUID;
import java.util.logging.Level;
@ -60,7 +59,6 @@ public class LocalDiskDSProcessor implements DataSourceProcessor {
private Host host;
private ImageWriterSettings imageWriterSettings;
private boolean ignoreFatOrphanFiles;
private boolean setDataSourceOptionsCalled;
/**
* Constructs a local drive data source processor that implements the
@ -139,7 +137,7 @@ public class LocalDiskDSProcessor implements DataSourceProcessor {
public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
run(null, progressMonitor, callback);
}
/**
* Adds a data source to the case database using a background task in a
* separate thread and the settings provided by the selection and
@ -157,38 +155,36 @@ public class LocalDiskDSProcessor implements DataSourceProcessor {
*/
@Override
public void run(Host host, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
if (!setDataSourceOptionsCalled) {
deviceId = UUID.randomUUID().toString();
drivePath = configPanel.getContentPath();
sectorSize = configPanel.getSectorSize();
timeZone = configPanel.getTimeZone();
ignoreFatOrphanFiles = configPanel.getNoFatOrphans();
if (configPanel.getImageWriterEnabled()) {
imageWriterSettings = configPanel.getImageWriterSettings();
} else {
imageWriterSettings = null;
}
deviceId = UUID.randomUUID().toString();
drivePath = configPanel.getContentPath();
sectorSize = configPanel.getSectorSize();
timeZone = configPanel.getTimeZone();
ignoreFatOrphanFiles = configPanel.getNoFatOrphans();
if (configPanel.getImageWriterEnabled()) {
imageWriterSettings = configPanel.getImageWriterSettings();
} else {
imageWriterSettings = null;
}
this.host = host;
Image image;
try {
image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(),
new String[]{drivePath}, sectorSize,
timeZone, null, null, null, deviceId, this.host);
new String[]{drivePath}, sectorSize,
timeZone, null, null, null, deviceId, this.host);
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error adding local disk with path " + drivePath + " to database", ex);
final List<String> errors = new ArrayList<>();
errors.add(ex.getMessage());
callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>());
return;
}
}
addDiskTask = new AddImageTask(
new AddImageTask.ImageDetails(deviceId, image, sectorSize, timeZone, ignoreFatOrphanFiles, null, null, null, imageWriterSettings),
new AddImageTask.ImageDetails(deviceId, image, sectorSize, timeZone, ignoreFatOrphanFiles, null, null, null, imageWriterSettings),
progressMonitor,
new StreamingAddDataSourceCallbacks(new DefaultIngestStream()),
new StreamingAddDataSourceCallbacks(new DefaultIngestStream()),
new StreamingAddImageTaskCallback(new DefaultIngestStream(), callback));
new Thread(addDiskTask).start();
}
@ -244,19 +240,19 @@ public class LocalDiskDSProcessor implements DataSourceProcessor {
Image image;
try {
image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(),
new String[]{drivePath}, sectorSize,
timeZone, null, null, null, deviceId);
new String[]{drivePath}, sectorSize,
timeZone, null, null, null, deviceId);
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error adding local disk with path " + drivePath + " to database", ex);
final List<String> errors = new ArrayList<>();
errors.add(ex.getMessage());
callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>());
return;
}
addDiskTask = new AddImageTask(new AddImageTask.ImageDetails(deviceId, image, sectorSize, timeZone, ignoreFatOrphanFiles, null, null, null, imageWriterSettings),
progressMonitor,
new StreamingAddDataSourceCallbacks(new DefaultIngestStream()),
}
addDiskTask = new AddImageTask(new AddImageTask.ImageDetails(deviceId, image, sectorSize, timeZone, ignoreFatOrphanFiles, null, null, null, imageWriterSettings),
progressMonitor,
new StreamingAddDataSourceCallbacks(new DefaultIngestStream()),
new StreamingAddImageTaskCallback(new DefaultIngestStream(), callback));
new Thread(addDiskTask).start();
}
@ -285,30 +281,5 @@ public class LocalDiskDSProcessor implements DataSourceProcessor {
drivePath = null;
timeZone = null;
ignoreFatOrphanFiles = false;
setDataSourceOptionsCalled = false;
}
/**
* Sets the configuration of the data source processor without using the
* configuration panel.
*
* @param drivePath Path to the local drive.
* @param timeZone The time zone to use when processing dates
* and times for the local drive, obtained from
* java.util.TimeZone.getID.
* @param ignoreFatOrphanFiles Whether to parse orphans if the image has a
* FAT filesystem.
*
* @deprecated Use the provided overload of the run method instead.
*/
@Deprecated
public void setDataSourceOptions(String drivePath, String timeZone, boolean ignoreFatOrphanFiles) {
this.deviceId = UUID.randomUUID().toString();
this.drivePath = drivePath;
this.sectorSize = 0;
this.timeZone = Calendar.getInstance().getTimeZone().getID();
this.ignoreFatOrphanFiles = ignoreFatOrphanFiles;
setDataSourceOptionsCalled = true;
}
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2018 Basis Technology Corp.
* Copyright 2011-2021 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -78,7 +78,6 @@ public class LocalFilesDSProcessor implements DataSourceProcessor, AutoIngestDat
* when the deprecated method setDataSourceOptions is removed.
*/
private List<String> localFilePaths;
private boolean setDataSourceOptionsCalled;
/**
* Constructs a local/logical files and/or directories data source processor
@ -139,7 +138,7 @@ public class LocalFilesDSProcessor implements DataSourceProcessor, AutoIngestDat
return configPanel.validatePanel();
}
/**
/**
* Adds a data source to the case database using a background task in a
* separate thread and the settings provided by the selection and
* configuration panel. Returns as soon as the background task is started.
@ -156,8 +155,8 @@ public class LocalFilesDSProcessor implements DataSourceProcessor, AutoIngestDat
@Override
public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
run(null, progressMonitor, callback);
}
}
/**
* Adds a data source to the case database using a background task in a
* separate thread and the settings provided by the selection and
@ -175,23 +174,21 @@ public class LocalFilesDSProcessor implements DataSourceProcessor, AutoIngestDat
*/
@Override
public void run(Host host, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
if (!setDataSourceOptionsCalled) {
localFilePaths = configPanel.getContentPaths();
if (configPanel.subTypeIsLogicalEvidencePanel()) {
try {
//if the L01 option was chosen
localFilePaths = extractLogicalEvidenceFileContents(localFilePaths);
} catch (L01Exception ex) {
//contents of l01 could not be extracted don't add data source or run ingest
final List<String> errors = new ArrayList<>();
errors.add(ex.getMessage());
callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>());
return;
} catch (NoCurrentCaseException ex) {
logger.log(Level.WARNING, "Exception while getting open case.", ex);
return;
}
localFilePaths = configPanel.getContentPaths();
if (configPanel.subTypeIsLogicalEvidencePanel()) {
try {
//if the L01 option was chosen
localFilePaths = extractLogicalEvidenceFileContents(localFilePaths);
} catch (L01Exception ex) {
//contents of l01 could not be extracted don't add data source or run ingest
final List<String> errors = new ArrayList<>();
errors.add(ex.getMessage());
callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>());
return;
} catch (NoCurrentCaseException ex) {
logger.log(Level.WARNING, "Exception while getting open case.", ex);
return;
}
}
run(UUID.randomUUID().toString(), configPanel.getFileSetName(), localFilePaths, host, progressMonitor, callback);
@ -220,7 +217,7 @@ public class LocalFilesDSProcessor implements DataSourceProcessor, AutoIngestDat
command.add("-f");
command.add("files");
command.add("-t");
File l01Dir = new File(Case.getCurrentCaseThrows().getModuleDirectory(), L01_EXTRACTION_DIR);
File l01Dir = new File(Case.getCurrentCaseThrows().getModuleDirectory(), L01_EXTRACTION_DIR);
if (!l01Dir.exists()) {
l01Dir.mkdirs();
}
@ -307,7 +304,7 @@ public class LocalFilesDSProcessor implements DataSourceProcessor, AutoIngestDat
return executablePath;
}
/**
* Adds a data source to the case database using a background task in a
* separate thread and the given settings instead of those provided by the
@ -385,7 +382,6 @@ public class LocalFilesDSProcessor implements DataSourceProcessor, AutoIngestDat
public void reset() {
configPanel.select();
localFilePaths = null;
setDataSourceOptionsCalled = false;
}
@Override
@ -421,34 +417,13 @@ public class LocalFilesDSProcessor implements DataSourceProcessor, AutoIngestDat
public void process(String deviceId, Path dataSourcePath, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callBack) {
process(deviceId, dataSourcePath, null, progressMonitor, callBack);
}
@Override
public void process(String deviceId, Path dataSourcePath, Host host, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callBack) {
List<String> filePaths = Arrays.asList(new String[]{dataSourcePath.toString()});
run(deviceId, "", filePaths, host, progressMonitor, callBack);
}
/**
* Sets the configuration of the data source processor without using the
* configuration panel. The data source processor will assign a UUID to the
* data source and will use the time zone of the machine executing this code
* when when processing dates and times for the image.
*
* @param paths A list of local/logical file and/or directory
* localFilePaths.
*
* @deprecated Use the provided overload of the run method instead.
*/
@Deprecated
public void setDataSourceOptions(String paths) {
// The LocalFilesPanel used to separate file paths with a comma and pass
// them as a string, but because file names are allowed to contain
// commas, this approach was buggy and replaced. We now pass a list of
// String paths.
this.localFilePaths = Arrays.asList(paths.split(","));
setDataSourceOptionsCalled = true;
}
/**
* A custom exception for the L01 processing.
*/

View File

@ -0,0 +1,39 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2021 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.events;
import java.util.List;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.datamodel.Host;
/**
* Event fired when new hosts are added.
*/
public class HostsAddedEvent extends HostsEvent {
private static final long serialVersionUID = 1L;
/**
* Main constructor.
* @param dataModelObjects The hosts that have been added.
*/
public HostsAddedEvent(List<Host> dataModelObjects) {
super(Case.Events.HOSTS_ADDED.name(), dataModelObjects);
}
}

View File

@ -0,0 +1,41 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2021 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.events;
import java.util.List;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.datamodel.Host;
/**
* Event fired when hosts are changed.
*/
public class HostsChangedEvent extends HostsEvent {
private static final long serialVersionUID = 1L;
/**
* Main constructor.
*
* @param dataModelObjects The new values for the hosts that have been
* changed.
*/
public HostsChangedEvent(List<Host> dataModelObjects) {
super(Case.Events.HOSTS_CHANGED.name(), dataModelObjects);
}
}

View File

@ -0,0 +1,87 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2021 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.events;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.sleuthkit.datamodel.Host;
import org.sleuthkit.datamodel.HostManager;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Base event class for when something pertaining to hosts changes.
*/
public class HostsEvent extends TskDataModelChangeEvent<Host> {
/**
* Retrieves a list of ids from a list of hosts.
*
* @param hosts The hosts.
* @return The list of ids.
*/
private static List<Long> getIds(List<Host> hosts) {
return getSafeList(hosts).stream()
.filter(h -> h != null)
.map(h -> h.getId()).collect(Collectors.toList());
}
/**
* Returns the hosts or an empty list.
*
* @param hosts The host list.
* @return The host list or an empty list if the parameter is null.
*/
private static List<Host> getSafeList(List<Host> hosts) {
return hosts == null ? Collections.emptyList() : hosts;
}
/**
* Main constructor.
*
* @param eventName The name of the Case.Events enum value for the event
* type.
* @param dataModelObjects The list of hosts for the event.
*/
protected HostsEvent(String eventName, List<Host> dataModelObjects) {
super(eventName, getIds(dataModelObjects), new ArrayList<>(getSafeList(dataModelObjects)));
}
@Override
protected List<Host> getDataModelObjects(SleuthkitCase caseDb, List<Long> ids) throws TskCoreException {
HostManager hostManager = caseDb.getHostManager();
List<Host> toRet = new ArrayList<>();
if (ids != null) {
for (Long id : ids) {
if (id == null) {
continue;
}
Optional<Host> thisHostOpt = hostManager.getHost(id);
thisHostOpt.ifPresent((h) -> toRet.add(h));
}
}
return toRet;
}
}

View File

@ -0,0 +1,39 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2021 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.events;
import java.util.List;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.datamodel.Host;
/**
* Event fired when hosts are removed.
*/
public class HostsRemovedEvent extends HostsEvent {
private static final long serialVersionUID = 1L;
/**
* Main constructor.
* @param dataModelObjects The list of hosts that have been deleted.
*/
public HostsRemovedEvent(List<Host> dataModelObjects) {
super(Case.Events.HOSTS_DELETED.name(), dataModelObjects);
}
}

View File

@ -0,0 +1,39 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2021 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.events;
import java.util.List;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.datamodel.Person;
/**
* Event fired when new persons are added.
*/
public class PersonsAddedEvent extends PersonsEvent {
private static final long serialVersionUID = 1L;
/**
* Main constructor.
* @param dataModelObjects The persons that have been added.
*/
public PersonsAddedEvent(List<Person> dataModelObjects) {
super(Case.Events.PERSONS_ADDED.name(), dataModelObjects);
}
}

View File

@ -0,0 +1,41 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2021 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.events;
import java.util.List;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.datamodel.Person;
/**
* Event fired when persons are changed.
*/
public class PersonsChangedEvent extends PersonsEvent {
private static final long serialVersionUID = 1L;
/**
* Main constructor.
*
* @param dataModelObjects The new values for the persons that have been
* changed.
*/
public PersonsChangedEvent(List<Person> dataModelObjects) {
super(Case.Events.PERSONS_CHANGED.name(), dataModelObjects);
}
}

View File

@ -0,0 +1,87 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2021 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.events;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.sleuthkit.datamodel.Person;
import org.sleuthkit.datamodel.PersonManager;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Base event class for when something pertaining to persons changes.
*/
public class PersonsEvent extends TskDataModelChangeEvent<Person> {
/**
* Retrieves a list of ids from a list of persons.
*
* @param persons The persons.
* @return The list of ids.
*/
private static List<Long> getIds(List<Person> persons) {
return getSafeList(persons).stream()
.filter(h -> h != null)
.map(h -> h.getId()).collect(Collectors.toList());
}
/**
* Returns the persons or an empty list.
*
* @param persons The person list.
* @return The person list or an empty list if the parameter is null.
*/
private static List<Person> getSafeList(List<Person> persons) {
return persons == null ? Collections.emptyList() : persons;
}
/**
* Main constructor.
*
* @param eventName The name of the Case.Events enum value for the event
* type.
* @param dataModelObjects The list of persons for the event.
*/
protected PersonsEvent(String eventName, List<Person> dataModelObjects) {
super(eventName, getIds(dataModelObjects), new ArrayList<>(getSafeList(dataModelObjects)));
}
@Override
protected List<Person> getDataModelObjects(SleuthkitCase caseDb, List<Long> ids) throws TskCoreException {
PersonManager personManager = caseDb.getPersonManager();
List<Person> toRet = new ArrayList<>();
if (ids != null) {
for (Long id : ids) {
if (id == null) {
continue;
}
Optional<Person> thisPersonOpt = personManager.getPerson(id);
thisPersonOpt.ifPresent((h) -> toRet.add(h));
}
}
return toRet;
}
}

View File

@ -0,0 +1,39 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2021 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.events;
import java.util.List;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.datamodel.Person;
/**
* Event fired when persons are removed.
*/
public class PersonsRemovedEvent extends PersonsEvent {
private static final long serialVersionUID = 1L;
/**
* Main constructor.
* @param dataModelObjects The list of persons that have been deleted.
*/
public PersonsRemovedEvent(List<Person> dataModelObjects) {
super(Case.Events.PERSONS_DELETED.name(), dataModelObjects);
}
}

View File

@ -1,8 +1,10 @@
DataContentViewerOtherCases.showCaseDetailsMenuItem.text=Show Case Details
DataContentViewerOtherCases.table.toolTip.text=Click column name to sort. Right-click on the table for more options.
DataContentViewerOtherCases.exportToCSVMenuItem.text=Export all Other Occurrences to CSV
DataContentViewerOtherCases.showCommonalityMenuItem.text=Show Frequency
DataContentViewerOtherCases.earliestCaseDate.text=Earliest Case Date
DataContentViewerOtherCases.earliestCaseLabel.toolTipText=
DataContentViewerOtherCases.earliestCaseLabel.text=Central Repository Starting Date:
DataContentViewerOtherCases.foundInLabel.text=
OtherOccurrencesPanel.caseDatasourceFileSplitPane.toolTipText=
OtherOccurrencesPanel.earliestCaseLabel.toolTipText=
OtherOccurrencesPanel.earliestCaseLabel.text=Central Repository Starting Date:
OtherOccurrencesPanel.earliestCaseDate.text=Earliest Case Date
OtherOccurrencesPanel.foundInLabel.text=
OtherOccurrencesPanel.filesTable.toolTipText=Click column name to sort. Right-click on the table for more options.
OtherOccurrencesPanel.exportToCSVMenuItem.text=Export all Other Occurrences to CSV
OtherOccurrencesPanel.showCommonalityMenuItem.text=Show Frequency
OtherOccurrencesPanel.showCaseDetailsMenuItem.text=Show Case Details

View File

@ -1,38 +1,6 @@
DataContentViewerOtherCases.caseDetailsDialog.noCaseNameError=Error
DataContentViewerOtherCases.caseDetailsDialog.noDetails=No details for this case.
DataContentViewerOtherCases.caseDetailsDialog.noDetailsReference=No case details for Global reference properties.
DataContentViewerOtherCases.caseDetailsDialog.notSelected=No Row Selected
# {0} - commonality percentage
# {1} - correlation type
# {2} - correlation value
DataContentViewerOtherCases.correlatedArtifacts.byType={0}% of data sources have {2} (type: {1})\n
DataContentViewerOtherCases.correlatedArtifacts.failed=Failed to get frequency details.
DataContentViewerOtherCases.correlatedArtifacts.isEmpty=There are no files or artifacts to correlate.
DataContentViewerOtherCases.correlatedArtifacts.title=Attribute Frequency
DataContentViewerOtherCases.dataSources.header.text=Data Source Name
DataContentViewerOtherCases.earliestCaseNotAvailable=\ Not Enabled.
DataContentViewerOtherCases.foundIn.text=Found %d instances in %d cases and %d data sources.
DataContentViewerOtherCases.noOpenCase.errMsg=No open case available.
DataContentViewerOtherCases.showCaseDetailsMenuItem.text=Show Case Details
DataContentViewerOtherCases.table.noArtifacts=Item has no attributes with which to search.
DataContentViewerOtherCases.table.noResultsFound=No results found.
DataContentViewerOtherCases.table.toolTip.text=Click column name to sort. Right-click on the table for more options.
DataContentViewerOtherCases.exportToCSVMenuItem.text=Export all Other Occurrences to CSV
DataContentViewerOtherCases.showCommonalityMenuItem.text=Show Frequency
DataContentViewerOtherCases.earliestCaseDate.text=Earliest Case Date
DataContentViewerOtherCases.earliestCaseLabel.toolTipText=
DataContentViewerOtherCases.earliestCaseLabel.text=Central Repository Starting Date:
DataContentViewerOtherCases.foundInLabel.text=
DataContentViewerOtherCases.title=Other Occurrences
DataContentViewerOtherCases.toolTip=Displays instances of the selected file/artifact from other occurrences.
DataContentViewerOtherCasesModel.csvHeader.attribute=Matched Attribute
DataContentViewerOtherCasesModel.csvHeader.case=Case
DataContentViewerOtherCasesModel.csvHeader.comment=Comment
DataContentViewerOtherCasesModel.csvHeader.dataSource=Data Source
DataContentViewerOtherCasesModel.csvHeader.device=Device
DataContentViewerOtherCasesModel.csvHeader.known=Known
DataContentViewerOtherCasesModel.csvHeader.path=Path
DataContentViewerOtherCasesModel.csvHeader.value=Attribute Value
OccurrencePanel.caseCreatedDateLabel.text=Created Date:
OccurrencePanel.caseDetails.text=Case Details
OccurrencePanel.caseNameLabel.text=Name:
@ -51,3 +19,36 @@ OtherOccurrencesDataSourcesTableModel.dataSourceName=Data Source Name
OtherOccurrencesDataSourcesTableModel.noData=No Data.
OtherOccurrencesFilesTableModel.fileName=File Name
OtherOccurrencesFilesTableModel.noData=No Data.
OtherOccurrencesPanel.caseDatasourceFileSplitPane.toolTipText=
OtherOccurrencesPanel.caseDetailsDialog.noCaseNameError=Error
OtherOccurrencesPanel.caseDetailsDialog.noDetails=No details for this case.
OtherOccurrencesPanel.caseDetailsDialog.noDetailsReference=No case details for Global reference properties.
OtherOccurrencesPanel.caseDetailsDialog.notSelected=No Row Selected
# {0} - commonality percentage
# {1} - correlation type
# {2} - correlation value
OtherOccurrencesPanel.correlatedArtifacts.byType={0}% of data sources have {2} (type: {1})\n
OtherOccurrencesPanel.correlatedArtifacts.failed=Failed to get frequency details.
OtherOccurrencesPanel.correlatedArtifacts.isEmpty=There are no files or artifacts to correlate.
OtherOccurrencesPanel.correlatedArtifacts.title=Attribute Frequency
OtherOccurrencesPanel.csvHeader.attribute=Matched Attribute
OtherOccurrencesPanel.csvHeader.case=Case
OtherOccurrencesPanel.csvHeader.comment=Comment
OtherOccurrencesPanel.csvHeader.dataSource=Data Source
OtherOccurrencesPanel.csvHeader.device=Device
OtherOccurrencesPanel.csvHeader.known=Known
OtherOccurrencesPanel.csvHeader.path=Path
OtherOccurrencesPanel.csvHeader.value=Attribute Value
OtherOccurrencesPanel.earliestCaseLabel.toolTipText=
OtherOccurrencesPanel.earliestCaseLabel.text=Central Repository Starting Date:
OtherOccurrencesPanel.earliestCaseDate.text=Earliest Case Date
OtherOccurrencesPanel.earliestCaseNotAvailable=\ Not Enabled.
OtherOccurrencesPanel.foundIn.text=Found %d instances in %d cases and %d data sources.
OtherOccurrencesPanel.foundInLabel.text=
OtherOccurrencesPanel.filesTable.toolTipText=Click column name to sort. Right-click on the table for more options.
OtherOccurrencesPanel.exportToCSVMenuItem.text=Export all Other Occurrences to CSV
OtherOccurrencesPanel.noOpenCase.errMsg=No open case available.
OtherOccurrencesPanel.showCommonalityMenuItem.text=Show Frequency
OtherOccurrencesPanel.showCaseDetailsMenuItem.text=Show Case Details
OtherOccurrencesPanel.table.noArtifacts=Item has no attributes with which to search.
OtherOccurrencesPanel.table.noResultsFound=No results found.

View File

@ -13,16 +13,9 @@ DataContentViewerOtherCases.dataSources.header.text=\u30c7\u30fc\u30bf\u30bd\u30
DataContentViewerOtherCases.earliestCaseNotAvailable=\ \u6709\u52b9\u306b\u306a\u3063\u3066\u3044\u307e\u305b\u3093\u3002
DataContentViewerOtherCases.foundIn.text=%d \u306e\u30b1\u30fc\u30b9\u3068 %d \u306e\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u5185\u306b %d \u306e\u30a4\u30f3\u30b9\u30bf\u30f3\u30b9\u304c\u898b\u3064\u304b\u308a\u307e\u3057\u305f\u3002
DataContentViewerOtherCases.noOpenCase.errMsg=\u958b\u3044\u3066\u3044\u308b\u30b1\u30fc\u30b9\u306f\u3042\u308a\u307e\u305b\u3093\u3002
DataContentViewerOtherCases.showCaseDetailsMenuItem.text=\u30b1\u30fc\u30b9\u8a73\u7d30\u3092\u8868\u793a
DataContentViewerOtherCases.table.noArtifacts=\u9805\u76ee\u306b\u691c\u7d22\u306b\u5229\u7528\u3067\u304d\u308b\u5c5e\u6027\u306f\u3042\u308a\u307e\u305b\u3093\u3002
DataContentViewerOtherCases.table.noResultsFound=\u8a72\u5f53\u3059\u308b\u7d50\u679c\u304c\u3042\u308a\u307e\u305b\u3093\u3002
DataContentViewerOtherCases.table.toolTip.text=\u5217\u540d\u3092\u30af\u30ea\u30c3\u30af\u3057\u3066\u30bd\u30fc\u30c8\u3057\u307e\u3059\u3002\u30c6\u30fc\u30d6\u30eb\u3092\u53f3\u30af\u30ea\u30c3\u30af\u3057\u3066\u3055\u3089\u306a\u308b\u30aa\u30d7\u30b7\u30e7\u30f3\u3092\u8868\u793a\u3057\u307e\u3059\u3002
DataContentViewerOtherCases.exportToCSVMenuItem.text=\u305d\u306e\u4ed6\u3059\u3079\u3066\u306e\u767a\u751f\u3092CSV\u306b\u30a8\u30af\u30b9\u30dd\u30fc\u30c8
DataContentViewerOtherCases.showCommonalityMenuItem.text=\u983b\u5ea6\u3092\u8868\u793a
DataContentViewerOtherCases.earliestCaseDate.text=\u6700\u3082\u53e4\u3044\u30b1\u30fc\u30b9\u65e5\u4ed8
DataContentViewerOtherCases.earliestCaseLabel.toolTipText=
DataContentViewerOtherCases.earliestCaseLabel.text=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ec\u30dd\u30b8\u30c8\u30ea\u30fc\u958b\u59cb\u65e5:
DataContentViewerOtherCases.foundInLabel.text=
DataContentViewerOtherCases.title=\u305d\u306e\u4ed6\u306e\u767a\u751f
DataContentViewerOtherCases.toolTip=\u305d\u306e\u4ed6\u306e\u767a\u751f\u304b\u3089\u9078\u629e\u3057\u305f\u30d5\u30a1\u30a4\u30eb/\u904e\u53bb\u306e\u691c\u7d22\u7d50\u679c\u306e\u30a4\u30f3\u30b9\u30bf\u30f3\u30b9\u3092\u8868\u793a\u3057\u307e\u3059\u3002
DataContentViewerOtherCasesModel.csvHeader.attribute=\u4e00\u81f4\u3057\u305f\u5c5e\u6027
@ -51,3 +44,11 @@ OtherOccurrencesDataSourcesTableModel.dataSourceName=\u30c7\u30fc\u30bf\u30bd\u3
OtherOccurrencesDataSourcesTableModel.noData=\u30c7\u30fc\u30bf\u304c\u3042\u308a\u307e\u305b\u3093\u3002
OtherOccurrencesFilesTableModel.fileName=\u30d5\u30a1\u30a4\u30eb\u540d
OtherOccurrencesFilesTableModel.noData=\u30c7\u30fc\u30bf\u304c\u3042\u308a\u307e\u305b\u3093\u3002
OtherOccurrencesPanel.filesTable.toolTipText=\u5217\u540d\u3092\u30af\u30ea\u30c3\u30af\u3057\u3066\u30bd\u30fc\u30c8\u3057\u307e\u3059\u3002\u30c6\u30fc\u30d6\u30eb\u3092\u53f3\u30af\u30ea\u30c3\u30af\u3057\u3066\u3055\u3089\u306a\u308b\u30aa\u30d7\u30b7\u30e7\u30f3\u3092\u8868\u793a\u3057\u307e\u3059\u3002
OtherOccurrencesPanel.earliestCaseLabel.toolTipText=
OtherOccurrencesPanel.earliestCaseLabel.text=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ec\u30dd\u30b8\u30c8\u30ea\u30fc\u958b\u59cb\u65e5:
OtherOccurrencesPanel.earliestCaseDate.text=\u6700\u3082\u53e4\u3044\u30b1\u30fc\u30b9\u65e5\u4ed8
OtherOccurrencesPanel.foundInLabel.text=
OtherOccurrencesPanel.showCommonalityMenuItem.text=\u983b\u5ea6\u3092\u8868\u793a
OtherOccurrencesPanel.showCaseDetailsMenuItem.text=\u30b1\u30fc\u30b9\u8a73\u7d30\u3092\u8868\u793a
OtherOccurrencesPanel.exportToCSVMenuItem.text=\u305d\u306e\u4ed6\u3059\u3079\u3066\u306e\u767a\u751f\u3092CSV\u306b\u30a8\u30af\u30b9\u30dd\u30fc\u30c8

View File

@ -1,42 +1,6 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.6" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<NonVisualComponents>
<Container class="javax.swing.JPopupMenu" name="rightClickPopupMenu">
<Events>
<EventHandler event="popupMenuWillBecomeVisible" listener="javax.swing.event.PopupMenuListener" parameters="javax.swing.event.PopupMenuEvent" handler="rightClickPopupMenuPopupMenuWillBecomeVisible"/>
</Events>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignAbsoluteLayout">
<Property name="useNullLayout" type="boolean" value="true"/>
</Layout>
<SubComponents>
<MenuItem class="javax.swing.JMenuItem" name="exportToCSVMenuItem">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties" key="DataContentViewerOtherCases.exportToCSVMenuItem.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</MenuItem>
<MenuItem class="javax.swing.JMenuItem" name="showCaseDetailsMenuItem">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties" key="DataContentViewerOtherCases.showCaseDetailsMenuItem.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</MenuItem>
<MenuItem class="javax.swing.JMenuItem" name="showCommonalityMenuItem">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties" key="DataContentViewerOtherCases.showCommonalityMenuItem.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</MenuItem>
</SubComponents>
</Container>
<Component class="javax.swing.JFileChooser" name="CSVFileChooser">
</Component>
</NonVisualComponents>
<Properties>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[1000, 10]"/>
@ -58,280 +22,5 @@
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
</AuxValues>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="tableContainerPanel" alignment="0" pref="1000" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<Component id="tableContainerPanel" pref="78" max="32767" attributes="0"/>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Container class="javax.swing.JPanel" name="tableContainerPanel">
<Properties>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[600, 63]"/>
</Property>
<Property name="requestFocusEnabled" type="boolean" value="false"/>
</Properties>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="tablesViewerSplitPane" max="32767" attributes="0"/>
<Component id="jPanel1" alignment="1" max="32767" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<EmptySpace min="-2" pref="0" max="-2" attributes="0"/>
<Component id="tablesViewerSplitPane" pref="37" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="jPanel1" min="-2" max="-2" attributes="0"/>
<EmptySpace min="-2" pref="12" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Container class="javax.swing.JSplitPane" name="tablesViewerSplitPane">
<Properties>
<Property name="dividerLocation" type="int" value="450"/>
<Property name="resizeWeight" type="double" value="0.75"/>
</Properties>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout"/>
<SubComponents>
<Container class="javax.swing.JSplitPane" name="caseDatasourceFileSplitPane">
<Properties>
<Property name="dividerLocation" type="int" value="300"/>
<Property name="resizeWeight" type="double" value="0.66"/>
<Property name="toolTipText" type="java.lang.String" value=""/>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
<JSplitPaneConstraints position="left"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout"/>
<SubComponents>
<Container class="javax.swing.JSplitPane" name="caseDatasourceSplitPane">
<Properties>
<Property name="dividerLocation" type="int" value="150"/>
<Property name="resizeWeight" type="double" value="0.5"/>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
<JSplitPaneConstraints position="left"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout"/>
<SubComponents>
<Container class="javax.swing.JScrollPane" name="caseScrollPane">
<Properties>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[150, 30]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
<JSplitPaneConstraints position="left"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents>
<Component class="javax.swing.JTable" name="casesTable">
<Properties>
<Property name="autoCreateRowSorter" type="boolean" value="true"/>
<Property name="model" type="javax.swing.table.TableModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="casesTableModel" type="code"/>
</Property>
</Properties>
</Component>
</SubComponents>
</Container>
<Container class="javax.swing.JScrollPane" name="dataSourceScrollPane">
<Properties>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[150, 30]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
<JSplitPaneConstraints position="right"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents>
<Component class="javax.swing.JTable" name="dataSourcesTable">
<Properties>
<Property name="autoCreateRowSorter" type="boolean" value="true"/>
<Property name="model" type="javax.swing.table.TableModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="dataSourcesTableModel" type="code"/>
</Property>
</Properties>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Container>
<Container class="javax.swing.JScrollPane" name="filesTableScrollPane">
<Properties>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[150, 30]"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
<JSplitPaneConstraints position="right"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents>
<Component class="javax.swing.JTable" name="filesTable">
<Properties>
<Property name="autoCreateRowSorter" type="boolean" value="true"/>
<Property name="model" type="javax.swing.table.TableModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="filesTableModel" type="code"/>
</Property>
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties" key="DataContentViewerOtherCases.table.toolTip.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="columnModel" type="javax.swing.table.TableColumnModel" editor="org.netbeans.modules.form.editors2.TableColumnModelEditor">
<TableColumnModel selectionModel="0"/>
</Property>
<Property name="componentPopupMenu" type="javax.swing.JPopupMenu" editor="org.netbeans.modules.form.ComponentChooserEditor">
<ComponentRef name="rightClickPopupMenu"/>
</Property>
<Property name="selectionModel" type="javax.swing.ListSelectionModel" editor="org.netbeans.modules.form.editors2.JTableSelectionModelEditor">
<JTableSelectionModel selectionMode="0"/>
</Property>
<Property name="tableHeader" type="javax.swing.table.JTableHeader" editor="org.netbeans.modules.form.editors2.JTableHeaderEditor">
<TableHeader reorderingAllowed="true" resizingAllowed="true"/>
</Property>
</Properties>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Container>
<Container class="javax.swing.JScrollPane" name="detailsPanelScrollPane">
<Properties>
<Property name="horizontalScrollBarPolicy" type="int" value="31"/>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[300, 100]"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
<JSplitPaneConstraints position="right"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
</Container>
</SubComponents>
</Container>
<Container class="javax.swing.JPanel" name="jPanel1">
<Properties>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[576, 22]"/>
</Property>
</Properties>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
<SubComponents>
<Component class="javax.swing.JLabel" name="foundInLabel">
<Properties>
<Property name="horizontalAlignment" type="int" value="11"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties" key="DataContentViewerOtherCases.foundInLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[400, 16]"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="3" gridY="0" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="2" insetsLeft="2" insetsBottom="0" insetsRight="0" anchor="12" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="earliestCaseDate">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties" key="DataContentViewerOtherCases.earliestCaseDate.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[200, 16]"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="1" gridY="0" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="7" insetsBottom="0" insetsRight="0" anchor="18" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="earliestCaseLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties" key="DataContentViewerOtherCases.earliestCaseLabel.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/centralrepository/contentviewer/Bundle.properties" key="DataContentViewerOtherCases.earliestCaseLabel.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[260, 16]"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="0" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="18" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.Box$Filler" name="filler1">
<Properties>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[32767, 0]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="classDetails" type="java.lang.String" value="Box.Filler.HorizontalGlue"/>
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="2" gridY="0" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.1" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Container>
</SubComponents>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
</Form>

View File

@ -151,7 +151,7 @@ final class OtherOccurrencesDataSourcesTableModel extends AbstractTableModel {
//place holder value will be used since correlation attribute was unavailble
} catch (NoCurrentCaseException ex) {
logger.log(Level.WARNING, "Unable to get current case", ex);
caseUUID = DataContentViewerOtherCases.getPlaceholderUUID();
caseUUID = OtherOccurrencesPanel.getPlaceholderUUID();
}
}
dataSourceSet.add(new DataSourceColumnItem(nodeData.getCaseName(), nodeData.getDeviceID(), nodeData.getDataSourceName(), caseUUID));

View File

@ -130,7 +130,7 @@ public class OtherOccurrencesFilesTableModel extends AbstractTableModel {
//place holder value will be used since correlation attribute was unavailble
} catch (NoCurrentCaseException ex) {
logger.log(Level.WARNING, "Unable to get current case", ex);
caseUUID = DataContentViewerOtherCases.getPlaceholderUUID();
caseUUID = OtherOccurrencesPanel.getPlaceholderUUID();
}
}
return nodeData.getCaseName() + nodeData.getDataSourceName() + nodeData.getDeviceID() + nodeData.getFilePath() + caseUUID;

View File

@ -0,0 +1,321 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.6" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<NonVisualComponents>
<Container class="javax.swing.JPopupMenu" name="rightClickPopupMenu">
<Events>
<EventHandler event="popupMenuWillBecomeVisible" listener="javax.swing.event.PopupMenuListener" parameters="javax.swing.event.PopupMenuEvent" handler="rightClickPopupMenuPopupMenuWillBecomeVisible"/>
</Events>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignAbsoluteLayout">
<Property name="useNullLayout" type="boolean" value="true"/>
</Layout>
<SubComponents>
<MenuItem class="javax.swing.JMenuItem" name="exportToCSVMenuItem">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties" key="OtherOccurrencesPanel.exportToCSVMenuItem.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</MenuItem>
<MenuItem class="javax.swing.JMenuItem" name="showCaseDetailsMenuItem">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties" key="OtherOccurrencesPanel.showCaseDetailsMenuItem.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</MenuItem>
<MenuItem class="javax.swing.JMenuItem" name="showCommonalityMenuItem">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties" key="OtherOccurrencesPanel.showCommonalityMenuItem.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</MenuItem>
</SubComponents>
</Container>
<Component class="javax.swing.JFileChooser" name="CSVFileChooser">
</Component>
</NonVisualComponents>
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
</AuxValues>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="tableContainerPanel" alignment="0" pref="528" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="tableContainerPanel" alignment="0" pref="143" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Container class="javax.swing.JPanel" name="tableContainerPanel">
<Properties>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[600, 63]"/>
</Property>
<Property name="requestFocusEnabled" type="boolean" value="false"/>
</Properties>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="tablesViewerSplitPane" alignment="0" pref="0" max="32767" attributes="0"/>
<Component id="jPanel1" alignment="1" pref="516" max="32767" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="-2" pref="0" max="-2" attributes="0"/>
<Component id="tablesViewerSplitPane" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="jPanel1" min="-2" max="-2" attributes="0"/>
<EmptySpace min="-2" pref="12" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Container class="javax.swing.JSplitPane" name="tablesViewerSplitPane">
<Properties>
<Property name="dividerLocation" type="int" value="450"/>
<Property name="resizeWeight" type="double" value="0.75"/>
</Properties>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout"/>
<SubComponents>
<Container class="javax.swing.JSplitPane" name="caseDatasourceFileSplitPane">
<Properties>
<Property name="dividerLocation" type="int" value="300"/>
<Property name="resizeWeight" type="double" value="0.66"/>
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties" key="OtherOccurrencesPanel.caseDatasourceFileSplitPane.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
<JSplitPaneConstraints position="left"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout"/>
<SubComponents>
<Container class="javax.swing.JSplitPane" name="caseDatasourceSplitPane">
<Properties>
<Property name="dividerLocation" type="int" value="150"/>
<Property name="resizeWeight" type="double" value="0.5"/>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
<JSplitPaneConstraints position="left"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout"/>
<SubComponents>
<Container class="javax.swing.JScrollPane" name="caseScrollPane">
<Properties>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[150, 30]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
<JSplitPaneConstraints position="left"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents>
<Component class="javax.swing.JTable" name="casesTable">
<Properties>
<Property name="autoCreateRowSorter" type="boolean" value="true"/>
<Property name="model" type="javax.swing.table.TableModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="casesTableModel" type="code"/>
</Property>
</Properties>
</Component>
</SubComponents>
</Container>
<Container class="javax.swing.JScrollPane" name="dataSourceScrollPane">
<Properties>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[150, 30]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
<JSplitPaneConstraints position="right"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents>
<Component class="javax.swing.JTable" name="dataSourcesTable">
<Properties>
<Property name="autoCreateRowSorter" type="boolean" value="true"/>
<Property name="model" type="javax.swing.table.TableModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="dataSourcesTableModel" type="code"/>
</Property>
</Properties>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Container>
<Container class="javax.swing.JScrollPane" name="filesTableScrollPane">
<Properties>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[150, 30]"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
<JSplitPaneConstraints position="right"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents>
<Component class="javax.swing.JTable" name="filesTable">
<Properties>
<Property name="autoCreateRowSorter" type="boolean" value="true"/>
<Property name="model" type="javax.swing.table.TableModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="filesTableModel" type="code"/>
</Property>
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties" key="OtherOccurrencesPanel.filesTable.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="selectionModel" type="javax.swing.ListSelectionModel" editor="org.netbeans.modules.form.editors2.JTableSelectionModelEditor">
<JTableSelectionModel selectionMode="0"/>
</Property>
<Property name="tableHeader" type="javax.swing.table.JTableHeader" editor="org.netbeans.modules.form.editors2.JTableHeaderEditor">
<TableHeader reorderingAllowed="true" resizingAllowed="true"/>
</Property>
</Properties>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Container>
<Container class="javax.swing.JScrollPane" name="detailsPanelScrollPane">
<Properties>
<Property name="horizontalScrollBarPolicy" type="int" value="31"/>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[300, 100]"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
<JSplitPaneConstraints position="right"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
</Container>
</SubComponents>
</Container>
<Container class="javax.swing.JPanel" name="jPanel1">
<Properties>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[576, 22]"/>
</Property>
</Properties>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
<SubComponents>
<Component class="javax.swing.JLabel" name="foundInLabel">
<Properties>
<Property name="horizontalAlignment" type="int" value="11"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties" key="OtherOccurrencesPanel.foundInLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[400, 16]"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="3" gridY="0" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="2" insetsLeft="2" insetsBottom="0" insetsRight="0" anchor="12" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="earliestCaseDate">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties" key="OtherOccurrencesPanel.earliestCaseDate.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[200, 16]"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="1" gridY="0" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="7" insetsBottom="0" insetsRight="0" anchor="18" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="earliestCaseLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties" key="OtherOccurrencesPanel.earliestCaseLabel.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/centralrepository/contentviewer/Bundle.properties" key="OtherOccurrencesPanel.earliestCaseLabel.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[260, 16]"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="0" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="18" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.Box$Filler" name="filler1">
<Properties>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[32767, 0]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="classDetails" type="java.lang.String" value="Box.Filler.HorizontalGlue"/>
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="2" gridY="0" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.1" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Container>
</SubComponents>
</Form>

View File

@ -0,0 +1,117 @@
/*
* Central Repository
*
* Copyright 2017-2021 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.centralrepository.contentviewer;
import java.util.Objects;
import java.util.logging.Level;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException;
import org.sleuthkit.autopsy.coreutils.Logger;
/**
* Used as a key to ensure we eliminate duplicates from the result set by not
* overwriting CR correlation instances.
*/
final class UniquePathKey {
private static final Logger logger = Logger.getLogger(UniquePathKey.class.getName());
private final String dataSourceID;
private final String filePath;
private final String type;
private final String caseUUID;
UniquePathKey(OtherOccurrenceNodeInstanceData nodeData) {
super();
dataSourceID = nodeData.getDeviceID();
if (nodeData.getFilePath() != null) {
filePath = nodeData.getFilePath().toLowerCase();
} else {
filePath = null;
}
type = nodeData.getType();
String tempCaseUUID;
try {
tempCaseUUID = nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID();
} catch (CentralRepoException ignored) {
//non central repo nodeData won't have a correlation case
try {
tempCaseUUID = Case.getCurrentCaseThrows().getName();
//place holder value will be used since correlation attribute was unavailble
} catch (NoCurrentCaseException ex) {
logger.log(Level.WARNING, "Unable to get current case", ex);
tempCaseUUID = OtherOccurrencesPanel.getPlaceholderUUID();
}
}
caseUUID = tempCaseUUID;
}
@Override
public boolean equals(Object other) {
if (other instanceof UniquePathKey) {
UniquePathKey otherKey = (UniquePathKey) (other);
return (Objects.equals(otherKey.getDataSourceID(), this.getDataSourceID())
&& Objects.equals(otherKey.getFilePath(), this.getFilePath())
&& Objects.equals(otherKey.getType(), this.getType())
&& Objects.equals(otherKey.getCaseUUID(), this.getCaseUUID()));
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(getDataSourceID(), getFilePath(), getType(), getCaseUUID());
}
/**
* Get the type of this UniquePathKey.
*
* @return the type
*/
String getType() {
return type;
}
/**
* Get the file path for the UniquePathKey.
*
* @return the filePath
*/
String getFilePath() {
return filePath;
}
/**
* Get the data source id for the UniquePathKey.
*
* @return the dataSourceID
*/
String getDataSourceID() {
return dataSourceID;
}
/**
* Get the case uuid for the UniquePathKey
*
* @return the case UUID
*/
String getCaseUUID() {
return caseUUID;
}
}

View File

@ -0,0 +1 @@
TableDataPanel.titleLabel.text=Title

View File

@ -0,0 +1,15 @@
OsAccountDataPanel_basic_address=Address
OsAccountDataPanel_basic_admin=Administrator
OsAccountDataPanel_basic_creationDate=Creation Date
OsAccountDataPanel_basic_fullname=Full Name
OsAccountDataPanel_basic_login=Login
OsAccountDataPanel_basic_title=Basic Properties
OsAccountDataPanel_basic_type=Type
OsAccountDataPanel_realm_address=Address
OsAccountDataPanel_realm_confidence=Confidence
OsAccountDataPanel_realm_name=Name
OsAccountDataPanel_realm_title=Realm Properties
OsAccountDataPanel_realm_unknown=Unknown
OsAccountViewer_title=Os Account
OsAccountViewer_tooltip=Viewer for OS accounts related to the selected node.
TableDataPanel.titleLabel.text=Title

View File

@ -0,0 +1,294 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2021 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.contentviewers.osaccount;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import static java.util.Locale.US;
import java.util.Optional;
import javax.swing.Box;
import javax.swing.JLabel;
import javax.swing.JPanel;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.contentviewers.osaccount.SectionData.RowData;
import org.sleuthkit.datamodel.OsAccount;
import org.sleuthkit.datamodel.OsAccountRealm;
/**
* Panel for displaying the properties of an OsAccount.
*/
public class OsAccountDataPanel extends JPanel {
private static final long serialVersionUID = 1L;
private static final int KEY_COLUMN = 0;
private static final int VALUE_COLUMN = 1;
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("MMM dd yyyy", US);
// Panel constructor.
OsAccountDataPanel() {
initialize();
}
/**
* Initializes the panel layout.
*/
private void initialize() {
this.setLayout(new GridBagLayout());
}
/**
* Set the OsAccount to display in this panel.
*
* @param account OsAccount to display, if null is passed the panel will
* appear blank.
*/
void setOsAccount(OsAccount account) {
removeAll();
if (account != null) {
List<SectionData> data = new ArrayList<>();
data.add(buildBasicProperties(account));
OsAccountRealm realm = account.getRealm();
if (realm != null) {
data.add(buildRealmProperties(realm));
}
addDataComponents(data);
}
revalidate();
}
/**
* Give all of the data to display, create the swing components and add to
* the panel.
*
* @param panelData Data to be displayed.
*/
private void addDataComponents(List<SectionData> panelData) {
int rowCnt = 0;
for (SectionData section : panelData) {
addTitle(section.getTitle(), rowCnt++);
for (RowData<String, String> rowData : section) {
String key = rowData.getKey();
String value = rowData.getValue();
addPropertyName(key, rowCnt);
addPropertyValue(value, rowCnt++);
}
}
// Generate the constraints for a Vertical Glue to fill the space, if
// any at the bottom of the panel.
GridBagConstraints constraints = new GridBagConstraints();
constraints.gridx = 0;
constraints.gridy = rowCnt;
constraints.gridwidth = 2;
constraints.fill = GridBagConstraints.BOTH;
constraints.weightx = 1;
constraints.weighty = 1;
add(Box.createVerticalGlue(), constraints);
}
@Messages({
"OsAccountDataPanel_basic_title=Basic Properties",
"OsAccountDataPanel_basic_login=Login",
"OsAccountDataPanel_basic_fullname=Full Name",
"OsAccountDataPanel_basic_address=Address",
"OsAccountDataPanel_basic_admin=Administrator",
"OsAccountDataPanel_basic_type=Type",
"OsAccountDataPanel_basic_creationDate=Creation Date",
})
/**
* Returns the data for the Basic Properties section of the panel.
*
* @param account Selected account
*
* @return The basic properties data for the given account.
*/
private SectionData buildBasicProperties(OsAccount account) {
SectionData data = new SectionData(Bundle.OsAccountDataPanel_basic_title());
Optional<String> optional = account.getLoginName();
data.addData(Bundle.OsAccountDataPanel_basic_login(),
optional.isPresent() ? optional.get() : "");
optional = account.getFullName();
data.addData(Bundle.OsAccountDataPanel_basic_fullname(),
optional.isPresent() ? optional.get() : "");
data.addData(Bundle.OsAccountDataPanel_basic_address(),
account.getName() == null || account.getName().isEmpty() ? "" : account.getName());
data.addData(Bundle.OsAccountDataPanel_basic_type(), account.getOsAccountType().getName());
Optional<Long> crTime = account.getCreationTime();
if(crTime.isPresent()) {
data.addData(Bundle.OsAccountDataPanel_basic_creationDate(), DATE_FORMAT.format(new Date(crTime.get() * 1000)));
}
else {
data.addData(Bundle.OsAccountDataPanel_basic_creationDate(), "");
}
return data;
}
@Messages({
"OsAccountDataPanel_realm_title=Realm Properties",
"OsAccountDataPanel_realm_name=Name",
"OsAccountDataPanel_realm_address=Address",
"OsAccountDataPanel_realm_confidence=Confidence",
"OsAccountDataPanel_realm_unknown=Unknown",
"OsAccountDataPanel_realm_scope=Scope",})
/**
* Builds the Realm Properties.
*
* @param realm A valid OsAccountRealm.
*
* @return Data to be displayed for the given realm.
*/
private SectionData buildRealmProperties(OsAccountRealm realm) {
SectionData data = new SectionData(Bundle.OsAccountDataPanel_realm_title());
Optional<String> optional = realm.getRealmName();
data.addData(Bundle.OsAccountDataPanel_realm_name(),
optional.isPresent() ? optional.get() : Bundle.OsAccountDataPanel_realm_unknown());
optional = realm.getRealmAddr();
data.addData(Bundle.OsAccountDataPanel_realm_address(),
optional.isPresent() ? optional.get() : "");
data.addData(Bundle.OsAccountDataPanel_realm_scope(),
realm.getScope().getName());
data.addData(Bundle.OsAccountDataPanel_realm_confidence(),
realm.getScopeConfidence().getName());
return data;
}
/**
* Add a section title to the panel with the given title and location.
*
* @param title Section title.
* @param row Row in the layout the title will appear.
*/
private void addTitle(String title, int row) {
JLabel label = new JLabel(title);
// Make the title bold.
label.setFont(label.getFont().deriveFont(Font.BOLD));
add(label, getTitleContraints(row));
}
/**
* Add the property name at the given row in the layout.
*
* @param key The property name.
* @param row The row in the layout.
*/
private void addPropertyName(String key, int row) {
JLabel label = new JLabel(key);
add(label, getPropertyNameContraints(row));
}
/**
* Add the property value at the given row in the layout.
*
* @param value The value to display.
* @param row The row in the layout.
*/
private void addPropertyValue(String value, int row) {
JLabel label = new JLabel(value);
add(label, getPropertyValueContraints(row));
}
/**
* Generate the constraints for a title at the given row.
*
* @param row The row to generate the title constraints for.
*
* @return Constraints for a title row.
*/
private GridBagConstraints getTitleContraints(int row) {
GridBagConstraints constraints = new GridBagConstraints();
constraints.gridx = 0;
constraints.gridy = row;
constraints.gridwidth = 2; // The title goes across the other columns
constraints.gridheight = 1;
constraints.anchor = GridBagConstraints.NORTHWEST;
constraints.fill = GridBagConstraints.HORIZONTAL;
constraints.weightx = 1;
constraints.insets = new Insets(5, 5, 5, 9);
return constraints;
}
/**
* Generate the constraints for a property name at the given row.
*
* @param row
*
* @return Constraints for the property name label.
*/
private GridBagConstraints getPropertyNameContraints(int row) {
GridBagConstraints constraints = new GridBagConstraints();
constraints.gridx = KEY_COLUMN;
constraints.gridy = row;
constraints.gridwidth = 1; // The title goes across the other columns
constraints.gridheight = 1;
constraints.anchor = GridBagConstraints.WEST;
constraints.insets = new Insets(0, 13, 5, 5);
return constraints;
}
/**
* Generate the constraints for a property value at the given row.
*
* @param row Row in the layout.
*
* @return The constraints for the property label.
*/
private GridBagConstraints getPropertyValueContraints(int row) {
GridBagConstraints constraints = new GridBagConstraints();
constraints.gridx = VALUE_COLUMN;
constraints.gridy = row;
constraints.gridwidth = 1; // The title goes across the other columns
constraints.gridheight = 1;
constraints.fill = GridBagConstraints.HORIZONTAL;
constraints.weightx = 1;
constraints.insets = new Insets(0, 5, 5, 5);
return constraints;
}
}

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
<SubComponents>
<Container class="javax.swing.JScrollPane" name="mainScrollPane">
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
<BorderConstraints direction="Center"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
</Container>
</SubComponents>
</Form>

View File

@ -0,0 +1,162 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2021 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.contentviewers.osaccount;
import java.awt.Component;
import java.util.Optional;
import java.util.logging.Level;
import org.openide.nodes.Node;
import org.openide.util.NbBundle.Messages;
import org.openide.util.lookup.ServiceProvider;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.DataArtifact;
import org.sleuthkit.datamodel.OsAccount;
import org.sleuthkit.datamodel.TskCoreException;
/**
* DataContentViewer for OsAccounts.
*/
@ServiceProvider(service = DataContentViewer.class, position = 12)
public class OsAccountViewer extends javax.swing.JPanel implements DataContentViewer {
private static final long serialVersionUID = 1L;
private final static Logger logger = Logger.getLogger(OsAccountViewer.class.getName());
private final OsAccountDataPanel dataPanel = new OsAccountDataPanel();
/**
* Creates new form OsAccountViewer
*/
public OsAccountViewer() {
initComponents();
mainScrollPane.setViewportView(dataPanel);
}
@Override
public void setNode(Node node) {
OsAccount osAccount = null;
try {
osAccount = node.getLookup().lookup(OsAccount.class);
if (osAccount == null) {
Optional<OsAccount> optional;
AbstractFile file = node.getLookup().lookup(AbstractFile.class);
if (file != null) {
optional = file.getOsAccount();
if (optional.isPresent()) {
osAccount = optional.get();
}
}
}
if (osAccount == null) {
DataArtifact dataArtifact = node.getLookup().lookup(DataArtifact.class);
if (dataArtifact != null) {
Optional<OsAccount> optional = dataArtifact.getOsAccount();
if (optional.isPresent()) {
osAccount = optional.get();
}
}
}
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, String.format("Failed to get OsAccount for node %s", node.getDisplayName()), ex);
}
if (osAccount != null) {
dataPanel.setOsAccount(osAccount);
}
}
@Messages({
"OsAccountViewer_title=Os Account"
})
@Override
public String getTitle() {
return Bundle.OsAccountViewer_title();
}
@Messages({
"OsAccountViewer_tooltip=Viewer for OS accounts related to the selected node."
})
@Override
public String getToolTip() {
return Bundle.OsAccountViewer_tooltip();
}
@Override
public DataContentViewer createInstance() {
return new OsAccountViewer();
}
@Override
public Component getComponent() {
return this;
}
@Override
public void resetComponent() {
dataPanel.setOsAccount(null);
}
@Override
public boolean isSupported(Node node) {
AbstractFile file = node.getLookup().lookup(AbstractFile.class);
OsAccount osAccount = node.getLookup().lookup(OsAccount.class);
DataArtifact dataArtifact = node.getLookup().lookup(DataArtifact.class);
try {
return osAccount != null
|| (file != null && file.getOsAccount().isPresent())
|| (dataArtifact != null && dataArtifact.getOsAccount().isPresent());
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, String.format("Failed to determine if node %s is Supported for OsAccountViewer", node.getDisplayName()), ex);
return false;
}
}
@Override
public int isPreferred(Node node) {
return 5;
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
java.awt.GridBagConstraints gridBagConstraints;
mainScrollPane = new javax.swing.JScrollPane();
setLayout(new java.awt.BorderLayout());
add(mainScrollPane, java.awt.BorderLayout.CENTER);
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JScrollPane mainScrollPane;
// End of variables declaration//GEN-END:variables
}

View File

@ -0,0 +1,108 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2021 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.contentviewers.osaccount;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* Data model for OsAccount panels, but could be reused in other places. The
* model represents a titled section of key\value pairs.
*/
final class SectionData implements Iterable<SectionData.RowData<String, String>> {
private final String title;
private final List<RowData<String, String>> data;
/**
* Construct a new SectionData object.
*
* @param title
*/
SectionData(String title) {
this.title = title;
this.data = new ArrayList<>();
}
/**
* Returns the title for this section.
*
* @return The section title.
*/
String getTitle() {
return title;
}
/**
* Add a new property name\property value pair.
*
* @param key The property display name.
* @param value The property value.
*/
void addData(String properytName, String propertyValue) {
data.add(new RowData<>(properytName, propertyValue));
}
@Override
public Iterator<RowData<String, String>> iterator() {
return data.iterator();
}
/**
* Represents a row of data. In this case it is just a key value pair.
*
* @param <K> Property Name.
* @param <V> Property Value.
*/
static class RowData<K, V> {
private final K key;
private final V value;
/**
* Construct a new row of data for the model.
*
* @param key
* @param value
*/
RowData(K key, V value) {
this.key = key;
this.value = value;
}
/**
* Returns the key.
*
* @return The key value.
*/
K getKey() {
return key;
}
/**
* Returns the value.
*
* @return The value.
*/
V getValue() {
return value;
}
}
}

View File

@ -23,11 +23,11 @@ import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.logging.Level;
import java.util.stream.Collectors;
import org.apache.commons.collections.CollectionUtils;
import org.openide.nodes.ChildFactory;
import org.openide.nodes.Node;
@ -47,6 +47,19 @@ import org.sleuthkit.datamodel.TskCoreException;
*/
public final class AutopsyTreeChildFactory extends ChildFactory.Detachable<Object> {
private static final Set<Case.Events> LISTENING_EVENTS = EnumSet.of(
Case.Events.DATA_SOURCE_ADDED,
Case.Events.HOSTS_ADDED,
Case.Events.HOSTS_DELETED,
Case.Events.PERSONS_ADDED,
Case.Events.PERSONS_DELETED,
Case.Events.PERSONS_CHANGED
);
private static final Set<String> LISTENING_EVENT_NAMES = LISTENING_EVENTS.stream()
.map(evt -> evt.name())
.collect(Collectors.toSet());
private static final Logger logger = Logger.getLogger(AutopsyTreeChildFactory.class.getName());
/**
@ -56,7 +69,7 @@ public final class AutopsyTreeChildFactory extends ChildFactory.Detachable<Objec
@Override
public void propertyChange(PropertyChangeEvent evt) {
String eventType = evt.getPropertyName();
if (eventType.equals(Case.Events.DATA_SOURCE_ADDED.toString())
if (LISTENING_EVENT_NAMES.contains(eventType)
&& Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true)) {
refreshChildren();
}
@ -66,13 +79,13 @@ public final class AutopsyTreeChildFactory extends ChildFactory.Detachable<Objec
@Override
protected void addNotify() {
super.addNotify();
Case.addEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED), pcl);
Case.addEventTypeSubscriber(LISTENING_EVENTS, pcl);
}
@Override
protected void removeNotify() {
super.removeNotify();
Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED), pcl);
Case.removeEventTypeSubscriber(LISTENING_EVENTS, pcl);
}
/**
@ -94,11 +107,11 @@ public final class AutopsyTreeChildFactory extends ChildFactory.Detachable<Objec
.map(PersonGrouping::new)
.sorted()
.forEach(list::add);
if (CollectionUtils.isNotEmpty(personManager.getHostsForPerson(null))) {
list.add(new PersonGrouping(null));
}
return true;
} else {
// otherwise, just show host level

View File

@ -50,7 +50,7 @@ public class ContentNodeSelectionInfo implements NodeSelectionInfo {
@Override
public boolean matches(Node candidateNode) {
Content content = candidateNode.getLookup().lookup(Content.class);
return content.getId() == contentId;
return (content != null && content.getId() == contentId);
}
}

View File

@ -33,7 +33,6 @@ import org.openide.util.lookup.Lookups;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.Host.HostStatus;
import org.sleuthkit.datamodel.TskCoreException;
/**
@ -95,6 +94,13 @@ public class DataSourcesByTypeNode extends DisplayableItemNode {
private static final String NAME = Bundle.DataSourcesHostsNode_name();
/**
* @return The name used to identify the node of this type with a lookup.
*/
public static String getNameIdentifier() {
return NAME;
}
/**
* Main constructor.
*/

View File

@ -43,7 +43,15 @@ import org.sleuthkit.datamodel.TskDataException;
*/
public class DataSourcesNode extends DisplayableItemNode {
public static final String NAME = NbBundle.getMessage(DataSourcesNode.class, "DataSourcesNode.name");
private static final String NAME = NbBundle.getMessage(DataSourcesNode.class, "DataSourcesNode.name");
/**
* @return The name used to identify the node of this type with a lookup.
*/
public static String getNameIdentifier() {
return NAME;
}
private final String displayName;
// NOTE: The images passed in via argument will be ignored.

View File

@ -33,9 +33,11 @@ import org.openide.nodes.Node;
import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages;
import org.openide.util.WeakListeners;
import org.openide.util.lookup.Lookups;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.events.HostsChangedEvent;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.hosts.AssociatePersonsMenuAction;
import org.sleuthkit.autopsy.datamodel.hosts.RemoveParentPersonAction;
@ -76,7 +78,7 @@ public class HostNode extends DisplayableItemNode {
/**
* Listener for handling DATA_SOURCE_ADDED events.
*/
private final PropertyChangeListener pcl = new PropertyChangeListener() {
private final PropertyChangeListener dataSourceAddedPcl = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
String eventType = evt.getPropertyName();
@ -88,12 +90,12 @@ public class HostNode extends DisplayableItemNode {
@Override
protected void addNotify() {
Case.addEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED), pcl);
Case.addEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED), dataSourceAddedPcl);
}
@Override
protected void removeNotify() {
Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED), pcl);
Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED), dataSourceAddedPcl);
}
@Override
@ -163,7 +165,39 @@ public class HostNode extends DisplayableItemNode {
return new DataSourceGroupingNode(key.getDataSource());
};
/**
* Listener for handling host change events.
*/
private final PropertyChangeListener hostChangePcl = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
String eventType = evt.getPropertyName();
if (hostId != null && eventType.equals(Case.Events.HOSTS_CHANGED.toString()) && evt instanceof HostsChangedEvent) {
((HostsChangedEvent) evt).getNewValue().stream()
.filter(h -> h != null && h.getId() == hostId)
.findFirst()
.ifPresent((newHost) -> {
setName(newHost.getName());
setDisplayName(newHost.getName());
});
}
}
};
/*
* Get the host name or 'unknown host' if null.
*
* @param host The host.
* @return The display name.
*/
private static String getHostName(Host host) {
return (host == null || host.getName() == null)
? Bundle.HostGroupingNode_unknownHostNode_title()
: host.getName();
}
private final Host host;
private final Long hostId;
/**
* Main constructor for HostDataSources key where data source children
@ -192,14 +226,25 @@ public class HostNode extends DisplayableItemNode {
* @param host The host.
*/
private HostNode(Children children, Host host) {
super(children, host == null ? null : Lookups.singleton(host));
this(children, host, getHostName(host));
}
String safeName = (host == null || host.getName() == null)
? Bundle.HostGroupingNode_unknownHostNode_title()
: host.getName();
super.setName(safeName);
super.setDisplayName(safeName);
/**
* Constructor.
*
* @param children The children for this host node.
* @param host The host.
* @param displayName The displayName.
*/
private HostNode(Children children, Host host, String displayName) {
super(children,
host == null ? Lookups.fixed(displayName) : Lookups.fixed(host, displayName));
hostId = host == null ? null : host.getId();
Case.addEventTypeSubscriber(EnumSet.of(Case.Events.HOSTS_CHANGED),
WeakListeners.propertyChange(hostChangePcl, this));
super.setName(displayName);
super.setDisplayName(displayName);
this.setIconBaseWithExtension(ICON_PATH);
this.host = host;
}

View File

@ -23,16 +23,20 @@ import java.beans.PropertyChangeListener;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.stream.Collectors;
import javax.swing.Action;
import org.openide.nodes.ChildFactory;
import org.openide.nodes.Children;
import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages;
import org.openide.util.WeakListeners;
import org.openide.util.lookup.Lookups;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.events.PersonsChangedEvent;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.persons.DeletePersonAction;
import org.sleuthkit.autopsy.datamodel.persons.EditPersonAction;
@ -48,6 +52,14 @@ import org.sleuthkit.datamodel.TskCoreException;
public class PersonGroupingNode extends DisplayableItemNode {
private static final String ICON_PATH = "org/sleuthkit/autopsy/images/person.png";
/**
* Returns the id of an unknown persons node. This can be used with a node lookup.
* @return The id of an unknown persons node.
*/
public static String getUnknownPersonId() {
return Bundle.PersonNode_unknownPersonNode_title();
}
/**
* Responsible for creating the host children of this person.
@ -56,6 +68,15 @@ public class PersonGroupingNode extends DisplayableItemNode {
private static final Logger logger = Logger.getLogger(PersonChildren.class.getName());
private static final Set<Case.Events> CHILD_EVENTS = EnumSet.of(
Case.Events.HOSTS_ADDED,
Case.Events.HOSTS_DELETED,
Case.Events.PERSONS_CHANGED);
private static final Set<String> CHILD_EVENTS_STR = CHILD_EVENTS.stream()
.map(ev -> ev.name())
.collect(Collectors.toSet());
private final Person person;
/**
@ -68,15 +89,13 @@ public class PersonGroupingNode extends DisplayableItemNode {
}
/**
* Listener for handling DATA_SOURCE_ADDED and DATA_SOURCE_DELETED
* events.
* Listener for handling adding and removing host events.
*/
private final PropertyChangeListener pcl = new PropertyChangeListener() {
private final PropertyChangeListener hostAddedDeletedPcl = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
String eventType = evt.getPropertyName();
if (eventType.equals(Case.Events.DATA_SOURCE_ADDED.toString())
|| eventType.equals(Case.Events.DATA_SOURCE_DELETED.toString())) {
if (eventType != null && CHILD_EVENTS_STR.contains(eventType)) {
refresh(true);
}
}
@ -84,12 +103,12 @@ public class PersonGroupingNode extends DisplayableItemNode {
@Override
protected void addNotify() {
Case.addEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED), pcl);
Case.addEventTypeSubscriber(CHILD_EVENTS, hostAddedDeletedPcl);
}
@Override
protected void removeNotify() {
Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED), pcl);
Case.removeEventTypeSubscriber(CHILD_EVENTS, hostAddedDeletedPcl);
}
@Override
@ -117,6 +136,38 @@ public class PersonGroupingNode extends DisplayableItemNode {
}
private final Person person;
private final Long personId;
/**
* Listener for handling person change events.
*/
private final PropertyChangeListener personChangePcl = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
String eventType = evt.getPropertyName();
if (personId != null && eventType.equals(Case.Events.PERSONS_CHANGED.toString()) && evt instanceof PersonsChangedEvent) {
((PersonsChangedEvent) evt).getNewValue().stream()
.filter(p -> p != null && p.getId() == personId)
.findFirst()
.ifPresent((newPerson) -> {
setName(newPerson.getName());
setDisplayName(newPerson.getName());
});
}
}
};
/**
* Gets the display name for this person or "Unknown Persons".
*
* @param person The person.
* @return The non-empty string for the display name.
*/
private static String getDisplayName(Person person) {
return (person == null || person.getName() == null)
? getUnknownPersonId()
: person.getName();
}
/**
* Main constructor.
@ -124,16 +175,25 @@ public class PersonGroupingNode extends DisplayableItemNode {
* @param person The person record to be represented.
*/
PersonGroupingNode(Person person) {
super(Children.create(new PersonChildren(person), false), person == null ? null : Lookups.singleton(person));
this(person, getDisplayName(person));
}
String safeName = (person == null || person.getName() == null)
? Bundle.PersonNode_unknownPersonNode_title()
: person.getName();
super.setName(safeName);
super.setDisplayName(safeName);
/**
* Constructor.
*
* @param person The person.
* @param displayName The display name for the person.
*/
private PersonGroupingNode(Person person, String displayName) {
super(Children.create(new PersonChildren(person), false),
person == null ? Lookups.fixed(displayName) : Lookups.fixed(person, displayName));
super.setName(displayName);
super.setDisplayName(displayName);
this.setIconBaseWithExtension(ICON_PATH);
this.person = person;
this.personId = person == null ? null : person.getId();
Case.addEventTypeSubscriber(EnumSet.of(Case.Events.PERSONS_CHANGED),
WeakListeners.propertyChange(personChangePcl, this));
}
@Override

View File

@ -31,8 +31,12 @@ import org.sleuthkit.datamodel.SleuthkitCase;
public class ResultsNode extends DisplayableItemNode {
@NbBundle.Messages("ResultsNode.name.text=Results")
public static final String NAME = Bundle.ResultsNode_name_text();
private static final String NAME = Bundle.ResultsNode_name_text();
public static String getNameIdentifier() {
return NAME;
}
public ResultsNode(SleuthkitCase sleuthkitCase) {
this(sleuthkitCase, 0);
}

View File

@ -29,7 +29,6 @@ import javax.swing.filechooser.FileFilter;
import org.openide.util.NbBundle.Messages;
import org.openide.util.lookup.ServiceProvider;
import org.openide.util.lookup.ServiceProviders;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.GeneralFilter;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback;

View File

@ -46,13 +46,7 @@ public class AnalysisSummary implements DefaultArtifactUpdateGovernor {
private static final BlackboardAttribute.Type TYPE_SET_NAME = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_SET_NAME);
private static final Set<String> EXCLUDED_KEYWORD_SEARCH_ITEMS = new HashSet<>(Arrays.asList(
"PHONE NUMBERS",
"IP ADDRESSES",
"EMAIL ADDRESSES",
"URLS",
"CREDIT CARD NUMBERS"
));
private static final Set<String> EXCLUDED_KEYWORD_SEARCH_ITEMS = new HashSet<>();
private static final Set<Integer> ARTIFACT_UPDATE_TYPE_IDS = new HashSet<>(Arrays.asList(
ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID(),

View File

@ -353,7 +353,7 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
*
* @return Seconds from java epoch.
*/
Long getDateAsLong() {
public Long getDateAsLong() {
return date;
}

View File

@ -21,14 +21,18 @@ package org.sleuthkit.autopsy.datasourcesummary.ui;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.lang3.tuple.Pair;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.AnalysisSummary;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer.DefaultCellModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ColumnModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetcher;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DefaultCellModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport.ExcelSheetExport;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel.ColumnModel;
import org.sleuthkit.datamodel.DataSource;
/**
@ -38,30 +42,35 @@ import org.sleuthkit.datamodel.DataSource;
@Messages({
"AnalysisPanel_keyColumn_title=Name",
"AnalysisPanel_countColumn_title=Count",
"AnalysisPanel_keywordSearchModuleName=Keyword Search"
})
"AnalysisPanel_keywordSearchModuleName=Keyword Search",
"AnalysisPanel_hashsetHits_tabName=Hashset Hits",
"AnalysisPanel_keywordHits_tabName=Keyword Hits",
"AnalysisPanel_interestingItemHits_tabName=Interesting Item Hits",})
public class AnalysisPanel extends BaseDataSourceSummaryPanel {
private static final long serialVersionUID = 1L;
/**
* Default Column definitions for each table
*/
private static final List<ColumnModel<Pair<String, Long>>> DEFAULT_COLUMNS = Arrays.asList(
// Default Column definitions for each table
private static final List<ColumnModel<Pair<String, Long>, DefaultCellModel<?>>> DEFAULT_COLUMNS = Arrays.asList(
new ColumnModel<>(
Bundle.AnalysisPanel_keyColumn_title(),
(pair) -> new DefaultCellModel(pair.getKey()),
(pair) -> new DefaultCellModel<>(pair.getKey()),
300
),
new ColumnModel<>(
Bundle.AnalysisPanel_countColumn_title(),
(pair) -> new DefaultCellModel(String.valueOf(pair.getValue())),
(pair) -> new DefaultCellModel<>(pair.getValue()),
100
)
);
// Identifies the key in the records for the tables.
private static final Function<Pair<String, Long>, String> DEFAULT_KEY_PROVIDER = (pair) -> pair.getKey();
private final DataFetcher<DataSource, List<Pair<String, Long>>> hashsetsFetcher;
private final DataFetcher<DataSource, List<Pair<String, Long>>> keywordsFetcher;
private final DataFetcher<DataSource, List<Pair<String, Long>>> interestingItemsFetcher;
private final JTablePanel<Pair<String, Long>> hashsetHitsTable
= JTablePanel.getJTablePanel(DEFAULT_COLUMNS)
.setKeyFunction(DEFAULT_KEY_PROVIDER);
@ -98,19 +107,23 @@ public class AnalysisPanel extends BaseDataSourceSummaryPanel {
public AnalysisPanel(AnalysisSummary analysisData) {
super(analysisData);
hashsetsFetcher = (dataSource) -> analysisData.getHashsetCounts(dataSource);
keywordsFetcher = (dataSource) -> analysisData.getKeywordCounts(dataSource);
interestingItemsFetcher = (dataSource) -> analysisData.getInterestingItemCounts(dataSource);
// set up data acquisition methods
dataFetchComponents = Arrays.asList(
// hashset hits loading components
new DataFetchWorker.DataFetchComponents<>(
(dataSource) -> analysisData.getHashsetCounts(dataSource),
hashsetsFetcher,
(result) -> hashsetHitsTable.showDataFetchResult(result)),
// keyword hits loading components
new DataFetchWorker.DataFetchComponents<>(
(dataSource) -> analysisData.getKeywordCounts(dataSource),
keywordsFetcher,
(result) -> keywordHitsTable.showDataFetchResult(result)),
// interesting item hits loading components
new DataFetchWorker.DataFetchComponents<>(
(dataSource) -> analysisData.getInterestingItemCounts(dataSource),
interestingItemsFetcher,
(result) -> interestingItemsTable.showDataFetchResult(result))
);
@ -216,6 +229,16 @@ public class AnalysisPanel extends BaseDataSourceSummaryPanel {
);
}// </editor-fold>//GEN-END:initComponents
@Override
List<ExcelSheetExport> getExports(DataSource dataSource) {
return Stream.of(
getTableExport(hashsetsFetcher, DEFAULT_COLUMNS, Bundle.AnalysisPanel_hashsetHits_tabName(), dataSource),
getTableExport(keywordsFetcher, DEFAULT_COLUMNS, Bundle.AnalysisPanel_keywordHits_tabName(), dataSource),
getTableExport(interestingItemsFetcher, DEFAULT_COLUMNS, Bundle.AnalysisPanel_interestingItemHits_tabName(), dataSource))
.filter(sheet -> sheet != null)
.collect(Collectors.toList());
}
// Variables declaration - do not modify//GEN-BEGIN:variables
// End of variables declaration//GEN-END:variables

View File

@ -38,11 +38,18 @@ import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ColumnModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker.DataFetchComponents;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetcher;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.EventUpdateHandler;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport.ExcelExportException;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport.ExcelSheetExport;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelTableExport;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelTableExport.ExcelCellModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.GuiCellModel.DefaultMenuItem;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.GuiCellModel.MenuItem;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.LoadableComponent;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.SwingWorkerSequentialExecutor;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.UpdateGovernor;
@ -244,12 +251,12 @@ abstract class BaseDataSourceSummaryPanel extends JPanel {
* @param artifact The artifact.
* @return The menu item for a go to artifact menu item.
*/
protected CellModelTableCellRenderer.MenuItem getArtifactNavigateItem(BlackboardArtifact artifact) {
protected MenuItem getArtifactNavigateItem(BlackboardArtifact artifact) {
if (artifact == null) {
return null;
}
return new CellModelTableCellRenderer.DefaultMenuItem(
return new DefaultMenuItem(
Bundle.BaseDataSourceSummaryPanel_goToArtifact(),
() -> {
final DirectoryTreeTopComponent dtc = DirectoryTreeTopComponent.findInstance();
@ -292,7 +299,7 @@ abstract class BaseDataSourceSummaryPanel extends JPanel {
* @param path The path to the file.
* @return The menu item or null if file cannot be found in data source.
*/
protected CellModelTableCellRenderer.MenuItem getFileNavigateItem(String path) {
protected MenuItem getFileNavigateItem(String path) {
if (StringUtils.isNotBlank(path)) {
Path p = Paths.get(path);
String fileName = normalizePath(p.getFileName().toString());
@ -320,12 +327,12 @@ abstract class BaseDataSourceSummaryPanel extends JPanel {
* @param file The file.
* @return The menu item list for a go to artifact menu item.
*/
protected CellModelTableCellRenderer.MenuItem getFileNavigateItem(AbstractFile file) {
protected MenuItem getFileNavigateItem(AbstractFile file) {
if (file == null) {
return null;
}
return new CellModelTableCellRenderer.DefaultMenuItem(
return new DefaultMenuItem(
Bundle.BaseDataSourceSummaryPanel_goToFile(),
() -> {
new ViewContextAction(Bundle.BaseDataSourceSummaryPanel_goToFile(), file)
@ -440,6 +447,132 @@ abstract class BaseDataSourceSummaryPanel extends JPanel {
*/
protected abstract void onNewDataSource(DataSource dataSource);
/**
* Returns all the excel exportable items associated with the tab.
*
* @param dataSource The data source that results should be filtered.
* @return The excel exportable objects.
*/
abstract List<ExcelSheetExport> getExports(DataSource dataSource);
/**
* Runs a data fetcher and returns the result handling any possible errors
* with a log message.
*
* @param dataFetcher The means of fetching the data.
* @param sheetName The name of the sheet.
* @param ds The data source.
* @return The fetched data.
*/
protected static <T> T getFetchResult(
DataFetcher<DataSource, T> dataFetcher,
String sheetName, DataSource ds) {
try {
return dataFetcher.runQuery(ds);
} catch (Exception ex) {
logger.log(Level.WARNING,
String.format("There was an error while acquiring data for exporting worksheet(s): '%s' for dataSource: %s",
sheetName == null ? "<null>" : sheetName,
ds == null || ds.getName() == null ? "<null>" : ds.getName()), ex);
return null;
}
}
/**
* Function that converts data into a excel sheet data.
*/
protected interface ExcelExportFunction<T> {
/**
* Function that converts data into an excel sheet.
*
* @param data The data.
* @return The excel sheet export.
* @throws ExcelExportException
*/
ExcelSheetExport convert(T data) throws ExcelExportException;
}
/**
* Helper method that converts data into an excel sheet export handling
* possible excel exceptions.
*
* @param excelConverter Function to convert data to an excel sheet export.
* @param data The data. If data is null, null will be returned.
* @param sheetName The name(s) of the sheet (to be used in the error
* message).
* @return The excel sheet export.
*/
protected static <T> ExcelSheetExport convertToExcel(ExcelExportFunction<T> excelConverter, T data, String sheetName) {
if (data == null) {
return null;
}
try {
return excelConverter.convert(data);
} catch (ExcelExportException ex) {
logger.log(Level.WARNING,
String.format("There was an error while preparing export of worksheet(s): '%s'",
sheetName == null ? "<null>" : sheetName), ex);
return null;
}
}
/**
* Returns an excel sheet export given the fetching of data or null if no
* export created.
*
* @param dataFetcher The means of fetching data.
* @param excelConverter The means of converting data to excel.
* @param sheetName The name of the sheet (for error handling reporting).
* @param ds The data source to use for fetching data.
* @return The excel sheet export or null if no export could be generated.
*/
protected static <T> ExcelSheetExport getExport(
DataFetcher<DataSource, T> dataFetcher, ExcelExportFunction<T> excelConverter,
String sheetName, DataSource ds) {
T data = getFetchResult(dataFetcher, sheetName, ds);
return convertToExcel(excelConverter, data, sheetName);
}
/**
* Returns an excel table export of the data or null if no export created.
*
* @param columnsModel The model for the columns.
* @param sheetName The name for the sheet.
* @param data The data to be exported.
* @return The excel table export or null if no export could be generated.
*/
protected static <T, C extends ExcelCellModel> ExcelSheetExport getTableExport(List<ColumnModel<T, C>> columnsModel,
String sheetName, List<T> data) {
return convertToExcel((dataList) -> new ExcelTableExport<T, C>(sheetName, columnsModel, dataList),
data,
sheetName);
}
/**
* Returns an excel table export of the data or null if no export created.
*
* @param dataFetcher The means of fetching data for the data source and the
* export.
* @param columnsModel The model for the columns.
* @param sheetName The name for the sheet.
* @param ds The data source.
* @return The excel export or null if no export created.
*/
protected static <T, C extends ExcelCellModel> ExcelSheetExport getTableExport(
DataFetcher<DataSource, List<T>> dataFetcher, List<ColumnModel<T, C>> columnsModel,
String sheetName, DataSource ds) {
return getExport(dataFetcher,
(dataList) -> new ExcelTableExport<T, C>(sheetName, columnsModel, dataList),
sheetName,
ds);
}
/**
* Utility method that shows a loading screen with loadable components,
* create swing workers from the datafetch components and data source

View File

@ -58,3 +58,7 @@ UserActivityPanel.rightClickForMoreOptions3.text=Right click on row for more opt
UserActivityPanel.rightClickForMoreOptions4.text=Right click on row for more options
UserActivityPanel.rightClickForMoreOptions5.text=Right click on row for more options
TimelinePanel.viewInTimelineBtn.text=View in Timeline
ExportPanel.xlsxExportMessage.text=Export Data from Data Source Summary to an Excel Document.
ExportPanel.xlsxExportButton.text=Export Summary Data
ExcelExportDialog.titleLabel.text=Data Source Summary has been exported to:
ExcelExportDialog.okButton.text=OK

View File

@ -1,5 +1,8 @@
AnalysisPanel_countColumn_title=Count
AnalysisPanel_hashsetHits_tabName=Hashset Hits
AnalysisPanel_interestingItemHits_tabName=Interesting Item Hits
AnalysisPanel_keyColumn_title=Name
AnalysisPanel_keywordHits_tabName=Keyword Hits
AnalysisPanel_keywordSearchModuleName=Keyword Search
BaseDataSourceSummaryPanel_goToArtifact=View Source Result
BaseDataSourceSummaryPanel_goToFile=View Source File in Directory
@ -45,6 +48,7 @@ DataSourceSummaryNode.column.type.header=Type
DataSourceSummaryNode.viewDataSourceAction.text=Go to Data Source
DataSourceSummaryTabbedPane_analysisTab_title=Analysis
DataSourceSummaryTabbedPane_detailsTab_title=Container
DataSourceSummaryTabbedPane_exportTab_title=Export
DataSourceSummaryTabbedPane_geolocationTab_title=Geolocation
DataSourceSummaryTabbedPane_ingestHistoryTab_title=Ingest History
DataSourceSummaryTabbedPane_pastCasesTab_title=Past Cases
@ -52,19 +56,37 @@ DataSourceSummaryTabbedPane_recentFileTab_title=Recent Files
DataSourceSummaryTabbedPane_timelineTab_title=Timeline
DataSourceSummaryTabbedPane_typesTab_title=Types
DataSourceSummaryTabbedPane_userActivityTab_title=User Activity
ExcelExportAction_exportToXLSX_beginExport=Beginning Export...
# {0} - tabName
ExcelExportAction_exportToXLSX_gatheringTabData=Fetching Data for {0} Tab...
ExcelExportAction_exportToXLSX_writingToFile=Writing to File...
ExcelExportAction_getXLSXPath_directory=DataSourceSummary
ExcelExportAction_moduleName=Data Source Summary
ExcelExportAction_runXLSXExport_progressCancelActionTitle=Cancelling...
ExcelExportAction_runXLSXExport_progressCancelTitle=Cancel
# {0} - dataSource
ExcelExportAction_runXLSXExport_progressTitle=Exporting {0} to XLSX
ExcelExportDialog_title=Data Source Summary Exported
GeolocationPanel_cityColumn_title=Closest City
GeolocationPanel_countColumn_title=Count
GeolocationPanel_mostCommon_tabName=Most Common Cities
GeolocationPanel_mostRecent_tabName=Most Recent Cities
GeolocationPanel_onNoCrIngest_message=No results will be shown because the GPX Parser was not run.
GeolocationPanel_unknownRow_title=Unknown
PastCasesPanel_caseColumn_title=Case
PastCasesPanel_countColumn_title=Count
PastCasesPanel_notableFileTable_tabName=Cases with Common Notable
PastCasesPanel_onNoCrIngest_message=No results will be shown because the Central Repository module was not run.
PastCasesPanel_sameIdsTable_tabName=Past Cases with the Same Devices
RecentFilePanel_col_header_domain=Domain
RecentFilePanel_col_header_path=Path
RecentFilePanel_col_header_sender=Sender
RecentFilePanel_emailParserModuleName=Email Parser
RecentFilePanel_no_open_documents=No recently open documents found.
RecentFilesPanel_attachmentsTable_tabName=Recent Attachments
RecentFilesPanel_col_head_date=Date
RecentFilesPanel_docsTable_tabName=Recently Opened Documents
RecentFilesPanel_downloadsTable_tabName=Recently Downloads
SizeRepresentationUtil_units_bytes=\ bytes
SizeRepresentationUtil_units_gigabytes=\ GB
SizeRepresentationUtil_units_kilobytes=\ kB
@ -123,21 +145,30 @@ UserActivityPanel.rightClickForMoreOptions3.text=Right click on row for more opt
UserActivityPanel.rightClickForMoreOptions4.text=Right click on row for more options
UserActivityPanel.rightClickForMoreOptions5.text=Right click on row for more options
TimelinePanel.viewInTimelineBtn.text=View in Timeline
ExportPanel.xlsxExportMessage.text=Export Data from Data Source Summary to an Excel Document.
ExportPanel.xlsxExportButton.text=Export Summary Data
ExcelExportDialog.titleLabel.text=Data Source Summary has been exported to:
ExcelExportDialog.okButton.text=OK
UserActivityPanel_noDataExists=No communication data exists
UserActivityPanel_tab_title=User Activity
UserActivityPanel_TopAccountTableModel_accountType_header=Account Type
UserActivityPanel_TopAccountTableModel_lastAccess_header=Last Accessed
UserActivityPanel_TopAccountTableModel_tabName=Recent Account Types Used
UserActivityPanel_TopDeviceAttachedTableModel_dateAccessed_header=Last Accessed
UserActivityPanel_TopDeviceAttachedTableModel_deviceId_header=Device Id
UserActivityPanel_TopDeviceAttachedTableModel_makeModel_header=Make and Model
UserActivityPanel_TopDeviceAttachedTableModel_tabName=Recent Devices Attached
UserActivityPanel_TopDomainsTableModel_count_header=Visits
UserActivityPanel_TopDomainsTableModel_domain_header=Domain
UserActivityPanel_TopDomainsTableModel_lastAccess_header=Last Accessed
UserActivityPanel_TopDomainsTableModel_tabName=Recent Domains
UserActivityPanel_TopProgramsTableModel_count_header=Run Times
UserActivityPanel_TopProgramsTableModel_folder_header=Folder
UserActivityPanel_TopProgramsTableModel_lastrun_header=Last Run
UserActivityPanel_TopProgramsTableModel_name_header=Program
UserActivityPanel_TopProgramsTableModel_tabName=Recent Programs
UserActivityPanel_TopWebSearchTableModel_dateAccessed_header=Date Accessed
UserActivityPanel_TopWebSearchTableModel_searchString_header=Search String
UserActivityPanel_TopWebSearchTableModel_tabName=Recent Web Searches
UserActivityPanel_TopWebSearchTableModel_translatedResult_header=Translated
ViewSummaryInformationAction.name.text=View Summary Information

View File

@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.datasourcesummary.ui;
import java.beans.PropertyChangeEvent;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@ -32,6 +33,7 @@ import org.sleuthkit.autopsy.datasourcesummary.datamodel.ContainerSummary;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult.ResultType;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker.DataFetchComponents;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DefaultUpdateGovernor;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.UpdateGovernor;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.Image;
@ -275,6 +277,11 @@ class ContainerPanel extends BaseDataSourceSummaryPanel {
((DefaultTableModel) filePathsTable.getModel()).setRowCount(0);
}
@Override
List<ExcelExport.ExcelSheetExport> getExports(DataSource ds) {
return Collections.emptyList();
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always

View File

@ -16,15 +16,6 @@
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignCardLayout"/>
<SubComponents>
<Container class="javax.swing.JTabbedPane" name="tabbedPane">
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignCardLayout" value="org.netbeans.modules.form.compat2.layouts.DesignCardLayout$CardConstraintsDescription">
<CardConstraints cardName="tabbedPane"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout"/>
</Container>
<Container class="javax.swing.JPanel" name="noDataSourcePane">
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
@ -57,5 +48,29 @@
</Component>
</SubComponents>
</Container>
<Container class="javax.swing.JPanel" name="tabContentPane">
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignCardLayout" value="org.netbeans.modules.form.compat2.layouts.DesignCardLayout$CardConstraintsDescription">
<CardConstraints cardName="tabbedPane"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
<SubComponents>
<Container class="javax.swing.JTabbedPane" name="tabbedPane">
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
<BorderConstraints direction="Center"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout"/>
</Container>
</SubComponents>
</Container>
</SubComponents>
</Form>

View File

@ -25,9 +25,12 @@ import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.IngestJobInfoPanel;
import org.sleuthkit.autopsy.datasourcesummary.ui.ExcelExportAction.ExportableTab;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport.ExcelSheetExport;
import org.sleuthkit.datamodel.DataSource;
/**
@ -43,7 +46,8 @@ import org.sleuthkit.datamodel.DataSource;
"DataSourceSummaryTabbedPane_pastCasesTab_title=Past Cases",
"DataSourceSummaryTabbedPane_analysisTab_title=Analysis",
"DataSourceSummaryTabbedPane_geolocationTab_title=Geolocation",
"DataSourceSummaryTabbedPane_timelineTab_title=Timeline"
"DataSourceSummaryTabbedPane_timelineTab_title=Timeline",
"DataSourceSummaryTabbedPane_exportTab_title=Export"
})
public class DataSourceSummaryTabbedPane extends javax.swing.JPanel {
@ -51,28 +55,14 @@ public class DataSourceSummaryTabbedPane extends javax.swing.JPanel {
* Records of tab information (i.e. title, component, function to call on
* new data source).
*/
private class DataSourceTab {
private class DataSourceTab implements ExportableTab {
private final String tabTitle;
private final Component component;
private final Consumer<DataSource> onDataSource;
private final Function<DataSource, List<ExcelSheetExport>> excelExporter;
private final Runnable onClose;
/**
* Main constructor.
*
* @param tabTitle The title of the tab.
* @param component The component to be displayed.
* @param onDataSource The function to be called on a new data source.
* @param onClose Called to cleanup resources when closing tabs.
*/
DataSourceTab(String tabTitle, Component component, Consumer<DataSource> onDataSource, Runnable onClose) {
this.tabTitle = tabTitle;
this.component = component;
this.onDataSource = onDataSource;
this.onClose = onClose;
}
/**
* Main constructor.
*
@ -81,19 +71,35 @@ public class DataSourceSummaryTabbedPane extends javax.swing.JPanel {
* @param notifyParentClose Notifies parent to trigger a close.
*/
DataSourceTab(String tabTitle, BaseDataSourceSummaryPanel panel) {
this(tabTitle, panel, panel::setDataSource, panel::getExports, panel::close);
panel.setParentCloseListener(() -> notifyParentClose());
}
/**
* Main constructor.
*
* @param tabTitle The title of the tab.
* @param component The component to be displayed.
* @param onDataSource The function to be called on a new data source.
* @param excelExporter The function that creates excel exports for a
* particular data source for this tab. Can be null for no exports.
* @param onClose Called to cleanup resources when closing tabs. Can be
* null for no-op.
*/
DataSourceTab(String tabTitle, Component component, Consumer<DataSource> onDataSource,
Function<DataSource, List<ExcelSheetExport>> excelExporter, Runnable onClose) {
this.tabTitle = tabTitle;
this.component = panel;
this.onDataSource = panel::setDataSource;
this.onClose = panel::close;
this.component = component;
this.onDataSource = onDataSource;
this.excelExporter = excelExporter;
this.onClose = onClose;
}
/**
* @return The title for the tab.
*/
String getTabTitle() {
@Override
public String getTabTitle() {
return tabTitle;
}
@ -111,6 +117,11 @@ public class DataSourceSummaryTabbedPane extends javax.swing.JPanel {
return onDataSource;
}
@Override
public List<ExcelSheetExport> getExcelExports(DataSource dataSource) {
return excelExporter == null ? null : excelExporter.apply(dataSource);
}
/**
* @return The action for closing resources in the tab.
*/
@ -120,13 +131,17 @@ public class DataSourceSummaryTabbedPane extends javax.swing.JPanel {
}
private static final long serialVersionUID = 1L;
// needs to match value provided for card layout in designed
private static final String TABBED_PANE = "tabbedPane";
private static final String NO_DATASOURCE_PANE = "noDataSourcePane";
private Runnable notifyParentClose = null;
private final IngestJobInfoPanel ingestHistoryPanel = new IngestJobInfoPanel();
// create an export panel whose button triggers the export to XLSX action
private final ExportPanel exportPanel = new ExportPanel();
private final List<DataSourceTab> tabs = Arrays.asList(
new DataSourceTab(Bundle.DataSourceSummaryTabbedPane_typesTab_title(), new TypesPanel()),
new DataSourceTab(Bundle.DataSourceSummaryTabbedPane_userActivityTab_title(), new UserActivityPanel()),
@ -136,11 +151,24 @@ public class DataSourceSummaryTabbedPane extends javax.swing.JPanel {
new DataSourceTab(Bundle.DataSourceSummaryTabbedPane_geolocationTab_title(), new GeolocationPanel()),
new DataSourceTab(Bundle.DataSourceSummaryTabbedPane_timelineTab_title(), new TimelinePanel()),
// do nothing on closing
new DataSourceTab(Bundle.DataSourceSummaryTabbedPane_ingestHistoryTab_title(), ingestHistoryPanel, ingestHistoryPanel::setDataSource, () -> {
}),
new DataSourceTab(Bundle.DataSourceSummaryTabbedPane_detailsTab_title(), new ContainerPanel())
new DataSourceTab(
Bundle.DataSourceSummaryTabbedPane_ingestHistoryTab_title(),
ingestHistoryPanel,
ingestHistoryPanel::setDataSource,
null,
null),
new DataSourceTab(Bundle.DataSourceSummaryTabbedPane_detailsTab_title(), new ContainerPanel()),
new DataSourceTab(
Bundle.DataSourceSummaryTabbedPane_exportTab_title(),
exportPanel,
null,
null,
null)
);
// the action that does the export
private final ExcelExportAction exportAction = new ExcelExportAction(tabs);
private DataSource dataSource = null;
private CardLayout cardLayout;
@ -194,6 +222,9 @@ public class DataSourceSummaryTabbedPane extends javax.swing.JPanel {
// set this to no datasource initially
cardLayout.show(this, NO_DATASOURCE_PANE);
// set action for when user requests xlsx export
exportPanel.setXlsxExportAction(() -> exportAction.accept(getDataSource()));
}
/**
@ -212,11 +243,13 @@ public class DataSourceSummaryTabbedPane extends javax.swing.JPanel {
*/
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
for (DataSourceTab tab : tabs) {
tab.getOnDataSource().accept(dataSource);
if (tab.getOnDataSource() != null) {
tab.getOnDataSource().accept(dataSource);
}
}
if (this.dataSource == null) {
cardLayout.show(this, NO_DATASOURCE_PANE);
} else {
@ -229,12 +262,15 @@ public class DataSourceSummaryTabbedPane extends javax.swing.JPanel {
*/
public void close() {
for (DataSourceTab tab : tabs) {
tab.getOnClose().run();
if (tab.getOnClose() != null) {
tab.getOnClose().run();
}
}
Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), caseEventsListener);
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
@ -244,12 +280,12 @@ public class DataSourceSummaryTabbedPane extends javax.swing.JPanel {
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
tabbedPane = new javax.swing.JTabbedPane();
javax.swing.JPanel noDataSourcePane = new javax.swing.JPanel();
javax.swing.JLabel noDataSourceLabel = new javax.swing.JLabel();
javax.swing.JPanel tabContentPane = new javax.swing.JPanel();
tabbedPane = new javax.swing.JTabbedPane();
setLayout(new java.awt.CardLayout());
add(tabbedPane, "tabbedPane");
noDataSourcePane.setLayout(new java.awt.BorderLayout());
@ -258,6 +294,11 @@ public class DataSourceSummaryTabbedPane extends javax.swing.JPanel {
noDataSourcePane.add(noDataSourceLabel, java.awt.BorderLayout.CENTER);
add(noDataSourcePane, "noDataSourcePane");
tabContentPane.setLayout(new java.awt.BorderLayout());
tabContentPane.add(tabbedPane, java.awt.BorderLayout.CENTER);
add(tabContentPane, "tabbedPane");
}// </editor-fold>//GEN-END:initComponents

View File

@ -0,0 +1,295 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2021 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.datasourcesummary.ui;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import java.util.logging.Level;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages;
import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.coreutils.FileUtil;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport.ExcelExportException;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport.ExcelSheetExport;
import org.sleuthkit.autopsy.progress.ModalDialogProgressIndicator;
import org.sleuthkit.autopsy.progress.ProgressIndicator;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Action that exports tab data to an excel workbook.
*/
@Messages({
"ExcelExportAction_moduleName=Data Source Summary",})
class ExcelExportAction implements Consumer<DataSource> {
private static final Logger logger = Logger.getLogger(ExcelExportAction.class.getName());
/**
* A tab that can be exported.
*/
interface ExportableTab {
/**
* Returns the name of the tab.
*
* @return The tab name.
*/
String getTabTitle();
/**
* Given the data source, provides the excel exports for this tab.
*
* @param dataSource The data source.
* @return The excel exports or null.
*/
List<ExcelSheetExport> getExcelExports(DataSource dataSource);
}
private final ExcelExport excelExport = ExcelExport.getInstance();
private final List<? extends ExportableTab> tabExports;
/**
* Main constructor.
*
* @param tabExports The different tabs that may have excel exports.
*/
ExcelExportAction(List<? extends ExportableTab> tabExports) {
this.tabExports = Collections.unmodifiableList(new ArrayList<>(tabExports));
}
/**
* Accepts the data source for which this export pertains, prompts user for
* output location, and exports the data.
*
* @param ds The data source.
*/
@Override
public void accept(DataSource ds) {
if (ds == null) {
return;
}
File outputLoc = getXLSXPath(ds.getName());
if (outputLoc == null) {
return;
}
runXLSXExport(ds, outputLoc);
}
/**
* Generates an xlsx path for the data source summary export.
*
* @param dataSourceName The name of the data source.
* @return The file to which the excel document should be written or null if
* file already exists or cancellation.
*/
@NbBundle.Messages({
"ExcelExportAction_getXLSXPath_directory=DataSourceSummary",})
private File getXLSXPath(String dataSourceName) {
// set initial path to reports directory with filename that is
// a combination of the data source name and time stamp
DateFormat dateFormat = new SimpleDateFormat("MM-dd-yyyy-HH-mm-ss");
String fileName = String.format("%s-%s.xlsx", dataSourceName == null ? "" : FileUtil.escapeFileName(dataSourceName), dateFormat.format(new Date()));
try {
String reportsDir = Case.getCurrentCaseThrows().getReportDirectory();
File reportsDirFile = Paths.get(reportsDir, Bundle.ExcelExportAction_getXLSXPath_directory()).toFile();
if (!reportsDirFile.exists()) {
reportsDirFile.mkdirs();
}
return Paths.get(reportsDirFile.getAbsolutePath(), fileName).toFile();
} catch (NoCurrentCaseException ex) {
logger.log(Level.WARNING, "Unable to find reports directory.", ex);
}
return null;
}
/**
* An action listener that handles cancellation of the export process.
*/
private class CancelExportListener implements ActionListener {
private SwingWorker<Boolean, Void> worker = null;
@Override
public void actionPerformed(ActionEvent e) {
if (worker != null && !worker.isCancelled() && !worker.isDone()) {
worker.cancel(true);
}
}
/**
* Returns the swing worker that could be cancelled.
*
* @return The swing worker that could be cancelled.
*/
SwingWorker<Boolean, Void> getWorker() {
return worker;
}
/**
* Sets the swing worker that could be cancelled.
*
* @param worker The swing worker that could be cancelled.
*/
void setWorker(SwingWorker<Boolean, Void> worker) {
this.worker = worker;
}
}
/**
* Handles managing the gui and exporting data from the tabs into an excel
* document.
*
* @param dataSource The data source.
* @param path The output path.
*/
@NbBundle.Messages({
"# {0} - dataSource",
"ExcelExportAction_runXLSXExport_progressTitle=Exporting {0} to XLSX",
"ExcelExportAction_runXLSXExport_progressCancelTitle=Cancel",
"ExcelExportAction_runXLSXExport_progressCancelActionTitle=Cancelling..."
})
private void runXLSXExport(DataSource dataSource, File path) {
CancelExportListener cancelButtonListener = new CancelExportListener();
ProgressIndicator progressIndicator = new ModalDialogProgressIndicator(
WindowManager.getDefault().getMainWindow(),
Bundle.ExcelExportAction_runXLSXExport_progressTitle(dataSource.getName()),
new String[]{Bundle.ExcelExportAction_runXLSXExport_progressCancelTitle()},
Bundle.ExcelExportAction_runXLSXExport_progressCancelTitle(),
cancelButtonListener
);
SwingWorker<Boolean, Void> worker = new SwingWorker<Boolean, Void>() {
@Override
protected Boolean doInBackground() throws Exception {
exportToXLSX(progressIndicator, dataSource, path);
return true;
}
@Override
protected void done() {
try {
get();
} catch (ExecutionException ex) {
logger.log(Level.WARNING, "Error while trying to export data source summary to xlsx.", ex);
} catch (InterruptedException | CancellationException ex) {
// no op on cancellation
} finally {
progressIndicator.finish();
}
}
};
cancelButtonListener.setWorker(worker);
worker.execute();
}
/**
* Action that handles updating progress and exporting data from the tabs.
*
* @param progressIndicator The progress indicator.
* @param dataSource The data source to be exported.
* @param path The path of the excel export.
* @throws InterruptedException
* @throws IOException
* @throws ExcelExportException
*/
@NbBundle.Messages({
"ExcelExportAction_exportToXLSX_beginExport=Beginning Export...",
"# {0} - tabName",
"ExcelExportAction_exportToXLSX_gatheringTabData=Fetching Data for {0} Tab...",
"ExcelExportAction_exportToXLSX_writingToFile=Writing to File...",})
private void exportToXLSX(ProgressIndicator progressIndicator, DataSource dataSource, File path)
throws InterruptedException, IOException, ExcelExport.ExcelExportException {
int exportWeight = 3;
int totalWeight = tabExports.size() + exportWeight;
progressIndicator.start(Bundle.ExcelExportAction_exportToXLSX_beginExport(), totalWeight);
List<ExcelExport.ExcelSheetExport> sheetExports = new ArrayList<>();
for (int i = 0; i < tabExports.size(); i++) {
if (Thread.interrupted()) {
throw new InterruptedException("Export has been cancelled.");
}
ExportableTab tab = tabExports.get(i);
progressIndicator.progress(Bundle.ExcelExportAction_exportToXLSX_gatheringTabData(tab == null ? "" : tab.getTabTitle()), i);
List<ExcelExport.ExcelSheetExport> exports = tab.getExcelExports(dataSource);
if (exports != null) {
sheetExports.addAll(exports);
}
}
if (Thread.interrupted()) {
throw new InterruptedException("Export has been cancelled.");
}
progressIndicator.progress(Bundle.ExcelExportAction_exportToXLSX_writingToFile(), tabExports.size());
excelExport.writeExcel(sheetExports, path);
progressIndicator.finish();
try {
// add to reports
Case curCase = Case.getCurrentCaseThrows();
curCase.addReport(path.getParent(),
Bundle.ExcelExportAction_moduleName(),
path.getName(),
dataSource);
// and show finished dialog
SwingUtilities.invokeLater(() -> {
ExcelExportDialog dialog = new ExcelExportDialog(WindowManager.getDefault().getMainWindow(), path);
dialog.setResizable(false);
dialog.setLocationRelativeTo(WindowManager.getDefault().getMainWindow());
dialog.setVisible(true);
dialog.toFront();
});
} catch (NoCurrentCaseException | TskCoreException ex) {
logger.log(Level.WARNING, "There was an error attaching report to case.", ex);
}
}
}

View File

@ -0,0 +1,114 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JDialogFormInfo">
<Properties>
<Property name="defaultCloseOperation" type="int" value="2"/>
</Properties>
<SyntheticProperties>
<SyntheticProperty name="formSizePolicy" type="int" value="1"/>
<SyntheticProperty name="generateCenter" type="boolean" value="false"/>
</SyntheticProperties>
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
</AuxValues>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="1" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="1" attributes="0">
<Component id="linkTextScrollPane" max="32767" attributes="0"/>
<Group type="102" alignment="1" attributes="0">
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
<Component id="okButton" min="-2" max="-2" attributes="0"/>
</Group>
<Group type="102" alignment="0" attributes="0">
<Component id="titleLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace min="0" pref="116" max="32767" attributes="0"/>
</Group>
</Group>
<EmptySpace max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Component id="titleLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="linkTextScrollPane" min="-2" pref="39" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="okButton" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Component class="javax.swing.JLabel" name="titleLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties" key="ExcelExportDialog.titleLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
</Component>
<Component class="javax.swing.JButton" name="okButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties" key="ExcelExportDialog.okButton.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="okButtonActionPerformed"/>
</Events>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
</Component>
<Container class="javax.swing.JScrollPane" name="linkTextScrollPane">
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
<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.JTextArea" name="linkText">
<Properties>
<Property name="editable" type="boolean" value="false"/>
<Property name="background" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
<Color type="null"/>
</Property>
<Property name="columns" type="int" value="20"/>
<Property name="foreground" type="java.awt.Color" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="java.awt.Color.BLUE" type="code"/>
</Property>
<Property name="lineWrap" type="boolean" value="true"/>
<Property name="rows" type="int" value="1"/>
<Property name="wrapStyleWord" type="boolean" value="true"/>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="null"/>
</Property>
<Property name="opaque" type="boolean" value="false"/>
</Properties>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Form>

View File

@ -0,0 +1,143 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2021 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.datasourcesummary.ui;
import java.awt.Cursor;
import java.awt.Desktop;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import javax.swing.SwingUtilities;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.coreutils.Logger;
/**
* Dialog showing where the data source summary excel export can be located.
*/
@Messages({
"ExcelExportDialog_title=Data Source Summary Exported"
})
public class ExcelExportDialog extends javax.swing.JDialog {
private static final Logger logger = Logger.getLogger(ExcelExportDialog.class.getName());
/**
* Creates new form ExcelExportDialog
*/
public ExcelExportDialog(java.awt.Frame parent, File filePath) {
super(parent, true);
initComponents();
setTitle(Bundle.ExcelExportDialog_title());
this.linkText.setText(filePath.getAbsolutePath());
this.linkText.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
SwingUtilities.invokeLater(() -> {
try {
Desktop.getDesktop().open(filePath);
} catch (IOException ex) {
logger.log(Level.WARNING, "Unable to open: " + filePath.getAbsolutePath(), ex);
}
});
}
});
this.linkText.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
javax.swing.JLabel titleLabel = new javax.swing.JLabel();
javax.swing.JButton okButton = new javax.swing.JButton();
javax.swing.JScrollPane linkTextScrollPane = new javax.swing.JScrollPane();
linkText = new javax.swing.JTextArea();
setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
org.openide.awt.Mnemonics.setLocalizedText(titleLabel, org.openide.util.NbBundle.getMessage(ExcelExportDialog.class, "ExcelExportDialog.titleLabel.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(okButton, org.openide.util.NbBundle.getMessage(ExcelExportDialog.class, "ExcelExportDialog.okButton.text")); // NOI18N
okButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
okButtonActionPerformed(evt);
}
});
linkText.setEditable(false);
linkText.setBackground(null);
linkText.setColumns(20);
linkText.setForeground(java.awt.Color.BLUE);
linkText.setLineWrap(true);
linkText.setRows(1);
linkText.setWrapStyleWord(true);
linkText.setBorder(null);
linkText.setOpaque(false);
linkTextScrollPane.setViewportView(linkText);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
.addComponent(linkTextScrollPane)
.addGroup(layout.createSequentialGroup()
.addGap(0, 0, Short.MAX_VALUE)
.addComponent(okButton))
.addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
.addComponent(titleLabel)
.addGap(0, 116, Short.MAX_VALUE)))
.addContainerGap())
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addComponent(titleLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(linkTextScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 39, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(okButton)
.addContainerGap())
);
pack();
}// </editor-fold>//GEN-END:initComponents
private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed
dispose();
}//GEN-LAST:event_okButtonActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JTextArea linkText;
// End of variables declaration//GEN-END:variables
}

View File

@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
</AuxValues>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="xlsxExportMessage" min="-2" max="-2" attributes="0"/>
<Component id="xlsxExportButton" alignment="0" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace pref="62" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Component id="xlsxExportMessage" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="xlsxExportButton" min="-2" max="-2" attributes="0"/>
<EmptySpace pref="250" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Component class="javax.swing.JButton" name="xlsxExportButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties" key="ExportPanel.xlsxExportButton.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="xlsxExportButtonActionPerformed"/>
</Events>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
</Component>
<Component class="javax.swing.JLabel" name="xlsxExportMessage">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties" key="ExportPanel.xlsxExportMessage.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
</Component>
</SubComponents>
</Form>

View File

@ -0,0 +1,105 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2021 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.datasourcesummary.ui;
/**
* The panel that provides options for exporting data source summary data.
*/
public class ExportPanel extends javax.swing.JPanel {
private Runnable xlsxExportAction;
/**
* Creates new form ExportPanel
*/
public ExportPanel() {
initComponents();
}
/**
* Returns the action that handles exporting to excel.
*
* @return The action that handles exporting to excel.
*/
public Runnable getXlsxExportAction() {
return xlsxExportAction;
}
/**
* Sets the action that handles exporting to excel.
*
* @param onXlsxExport The action that handles exporting to excel.
*/
public void setXlsxExportAction(Runnable onXlsxExport) {
this.xlsxExportAction = onXlsxExport;
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
javax.swing.JButton xlsxExportButton = new javax.swing.JButton();
javax.swing.JLabel xlsxExportMessage = new javax.swing.JLabel();
org.openide.awt.Mnemonics.setLocalizedText(xlsxExportButton, org.openide.util.NbBundle.getMessage(ExportPanel.class, "ExportPanel.xlsxExportButton.text")); // NOI18N
xlsxExportButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
xlsxExportButtonActionPerformed(evt);
}
});
org.openide.awt.Mnemonics.setLocalizedText(xlsxExportMessage, org.openide.util.NbBundle.getMessage(ExportPanel.class, "ExportPanel.xlsxExportMessage.text")); // NOI18N
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(xlsxExportMessage)
.addComponent(xlsxExportButton))
.addContainerGap(62, Short.MAX_VALUE))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addComponent(xlsxExportMessage)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(xlsxExportButton)
.addContainerGap(250, Short.MAX_VALUE))
);
}// </editor-fold>//GEN-END:initComponents
private void xlsxExportButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_xlsxExportButtonActionPerformed
if (this.xlsxExportAction != null) {
xlsxExportAction.run();
}
}//GEN-LAST:event_xlsxExportButtonActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables
// End of variables declaration//GEN-END:variables
}

View File

@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.datasourcesummary.ui;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -38,13 +39,15 @@ import org.sleuthkit.autopsy.datasourcesummary.datamodel.GeolocationSummary.City
import org.sleuthkit.autopsy.datasourcesummary.datamodel.GeolocationSummary.CityData;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.GeolocationSummary.CityRecordCount;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.CityRecord;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer.DefaultCellModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ColumnModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker.DataFetchComponents;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetcher;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DefaultCellModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel.ColumnModel;
import org.sleuthkit.autopsy.geolocation.GeoFilter;
import org.sleuthkit.autopsy.geolocation.GeoLocationUIException;
import org.sleuthkit.autopsy.geolocation.GeolocationTopComponent;
@ -59,32 +62,79 @@ import org.sleuthkit.datamodel.DataSource;
"GeolocationPanel_cityColumn_title=Closest City",
"GeolocationPanel_countColumn_title=Count",
"GeolocationPanel_onNoCrIngest_message=No results will be shown because the GPX Parser was not run.",
"GeolocationPanel_unknownRow_title=Unknown",})
"GeolocationPanel_unknownRow_title=Unknown",
"GeolocationPanel_mostCommon_tabName=Most Common Cities",
"GeolocationPanel_mostRecent_tabName=Most Recent Cities",})
public class GeolocationPanel extends BaseDataSourceSummaryPanel {
/**
* Object encapsulating the view model of this panel.
*/
private static class GeolocationViewModel {
private final List<Pair<String, Integer>> mostRecentData;
private final List<Pair<String, Integer>> mostCommonData;
/**
* Main constructor.
*
* @param mostRecentData The data to be displayed in the most recent
* table.
* @param mostCommonData The data to be displayed in the most common
* table.
*/
GeolocationViewModel(List<Pair<String, Integer>> mostRecentData, List<Pair<String, Integer>> mostCommonData) {
this.mostRecentData = mostRecentData;
this.mostCommonData = mostCommonData;
}
/**
* Returns the data to be displayed in the most recent table.
*
* @return The data to be displayed in the most recent table.
*/
List<Pair<String, Integer>> getMostRecentData() {
return mostRecentData;
}
/**
* Returns the data to be displayed in the most common table.
*
* @return The data to be displayed in the most common table.
*/
List<Pair<String, Integer>> getMostCommonData() {
return mostCommonData;
}
}
private static final long serialVersionUID = 1L;
private static final int DAYS_COUNT = 30;
private static final int MAX_COUNT = 10;
// The column indicating the city
private static final ColumnModel<Pair<String, Integer>> CITY_COL = new ColumnModel<>(
private static final ColumnModel<Pair<String, Integer>, DefaultCellModel<?>> CITY_COL = new ColumnModel<>(
Bundle.GeolocationPanel_cityColumn_title(),
(pair) -> new DefaultCellModel(pair.getLeft()),
(pair) -> new DefaultCellModel<>(pair.getLeft()),
300
);
// The column indicating the count of points seen close to that city
private static final ColumnModel<Pair<String, Integer>> COUNT_COL = new ColumnModel<>(
private static final ColumnModel<Pair<String, Integer>, DefaultCellModel<?>> COUNT_COL = new ColumnModel<>(
Bundle.GeolocationPanel_countColumn_title(),
(pair) -> new DefaultCellModel(Integer.toString(pair.getRight())),
(pair) -> new DefaultCellModel<>(pair.getRight()),
100
);
private static final List<ColumnModel<Pair<String, Integer>, DefaultCellModel<?>>> DEFAULT_TEMPLATE = Arrays.asList(
CITY_COL,
COUNT_COL
);
// tables displaying city and number of hits for that city
private final JTablePanel<Pair<String, Integer>> mostCommonTable = JTablePanel.getJTablePanel(Arrays.asList(CITY_COL, COUNT_COL))
private final JTablePanel<Pair<String, Integer>> mostCommonTable = JTablePanel.getJTablePanel(DEFAULT_TEMPLATE)
.setKeyFunction((pair) -> pair.getLeft());
private final JTablePanel<Pair<String, Integer>> mostRecentTable = JTablePanel.getJTablePanel(Arrays.asList(CITY_COL, COUNT_COL))
private final JTablePanel<Pair<String, Integer>> mostRecentTable = JTablePanel.getJTablePanel(DEFAULT_TEMPLATE)
.setKeyFunction((pair) -> pair.getLeft());
// loadable components on this tab
@ -99,6 +149,8 @@ public class GeolocationPanel extends BaseDataSourceSummaryPanel {
private final GeolocationSummary whereUsedData;
private final DataFetcher<DataSource, GeolocationViewModel> geolocationFetcher;
/**
* Main constructor.
*/
@ -115,10 +167,13 @@ public class GeolocationPanel extends BaseDataSourceSummaryPanel {
super(whereUsedData);
this.whereUsedData = whereUsedData;
this.geolocationFetcher = (dataSource) -> convertToViewModel(whereUsedData.getCityCounts(dataSource, DAYS_COUNT, MAX_COUNT));
// set up data acquisition methods
dataFetchComponents = Arrays.asList(
new DataFetchWorker.DataFetchComponents<>(
(dataSource) -> whereUsedData.getCityCounts(dataSource, DAYS_COUNT, MAX_COUNT),
geolocationFetcher,
(result) -> handleData(result)));
initComponents();
@ -127,11 +182,12 @@ public class GeolocationPanel extends BaseDataSourceSummaryPanel {
/**
* Means of rendering data to be shown in the tables.
*
* @param result The result of fetching data for a data source.
* @param result The result of fetching data for a data source and
* processing into view model data.
*/
private void handleData(DataFetchResult<CityData> result) {
showCityContent(DataFetchResult.getSubResult(result, (dr) -> dr.getMostCommon()), mostCommonTable, commonViewInGeolocationBtn);
showCityContent(DataFetchResult.getSubResult(result, (dr) -> dr.getMostRecent()), mostRecentTable, recentViewInGeolocationBtn);
private void handleData(DataFetchResult<GeolocationViewModel> result) {
showCityContent(DataFetchResult.getSubResult(result, (dr) -> dr.getMostCommonData()), mostCommonTable, commonViewInGeolocationBtn);
showCityContent(DataFetchResult.getSubResult(result, (dr) -> dr.getMostRecentData()), mostRecentTable, recentViewInGeolocationBtn);
}
/**
@ -156,7 +212,7 @@ public class GeolocationPanel extends BaseDataSourceSummaryPanel {
} else if (cityIdentifiers.size() >= 3) {
return String.format("%s, %s; %s", cityIdentifiers.get(0), cityIdentifiers.get(1), cityIdentifiers.get(2));
}
return null;
}
@ -188,7 +244,7 @@ public class GeolocationPanel extends BaseDataSourceSummaryPanel {
*/
private List<Pair<String, Integer>> formatList(CityCountsList countsList) {
if (countsList == null) {
return null;
return Collections.emptyList();
}
Stream<CityRecordCount> countsStream = ((countsList.getCounts() == null)
@ -206,6 +262,21 @@ public class GeolocationPanel extends BaseDataSourceSummaryPanel {
.collect(Collectors.toList());
}
/**
* Converts CityData from GeolocationSummary into data that can be directly
* put into table in this panel.
*
* @param cityData The city data.
* @return The view model data.
*/
private GeolocationViewModel convertToViewModel(CityData cityData) {
if (cityData == null) {
return new GeolocationViewModel(Collections.emptyList(), Collections.emptyList());
} else {
return new GeolocationViewModel(formatList(cityData.getMostRecent()), formatList(cityData.getMostCommon()));
}
}
/**
* Shows data in a particular table.
*
@ -213,13 +284,12 @@ public class GeolocationPanel extends BaseDataSourceSummaryPanel {
* @param table The table where the data will be displayed.
* @param goToGeolocation The corresponding geolocation navigation button.
*/
private void showCityContent(DataFetchResult<CityCountsList> result, JTablePanel<Pair<String, Integer>> table, JButton goToGeolocation) {
DataFetchResult<List<Pair<String, Integer>>> convertedData = DataFetchResult.getSubResult(result, (countsList) -> formatList(countsList));
if (convertedData != null && convertedData.getResultType() == DataFetchResult.ResultType.SUCCESS && CollectionUtils.isNotEmpty(convertedData.getData())) {
private void showCityContent(DataFetchResult<List<Pair<String, Integer>>> result, JTablePanel<Pair<String, Integer>> table, JButton goToGeolocation) {
if (result != null && result.getResultType() == DataFetchResult.ResultType.SUCCESS && CollectionUtils.isNotEmpty(result.getData())) {
goToGeolocation.setEnabled(true);
}
table.showDataFetchResult(convertedData);
table.showDataFetchResult(result);
}
/**
@ -279,6 +349,19 @@ public class GeolocationPanel extends BaseDataSourceSummaryPanel {
onNewDataSource(dataFetchComponents, tables, dataSource);
}
@Override
List<ExcelExport.ExcelSheetExport> getExports(DataSource dataSource) {
GeolocationViewModel model = getFetchResult(geolocationFetcher, "Geolocation sheets", dataSource);
if (model == null) {
return Collections.emptyList();
}
return Arrays.asList(
getTableExport(DEFAULT_TEMPLATE, Bundle.GeolocationPanel_mostRecent_tabName(), model.getMostRecentData()),
getTableExport(DEFAULT_TEMPLATE, Bundle.GeolocationPanel_mostCommon_tabName(), model.getMostCommonData())
);
}
@Override
public void close() {
ingestRunningLabel.unregister();

View File

@ -19,18 +19,23 @@
package org.sleuthkit.autopsy.datasourcesummary.ui;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.apache.commons.lang3.tuple.Pair;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.PastCasesSummary;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.PastCasesSummary.PastCasesResult;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer.DefaultCellModel;
import static org.sleuthkit.autopsy.datasourcesummary.ui.BaseDataSourceSummaryPanel.getFetchResult;
import static org.sleuthkit.autopsy.datasourcesummary.ui.BaseDataSourceSummaryPanel.getTableExport;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ColumnModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker.DataFetchComponents;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetcher;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DefaultCellModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel.ColumnModel;
import org.sleuthkit.datamodel.DataSource;
/**
@ -40,29 +45,34 @@ import org.sleuthkit.datamodel.DataSource;
@Messages({
"PastCasesPanel_caseColumn_title=Case",
"PastCasesPanel_countColumn_title=Count",
"PastCasesPanel_onNoCrIngest_message=No results will be shown because the Central Repository module was not run."
})
"PastCasesPanel_onNoCrIngest_message=No results will be shown because the Central Repository module was not run.",
"PastCasesPanel_notableFileTable_tabName=Cases with Common Notable",
"PastCasesPanel_sameIdsTable_tabName=Past Cases with the Same Devices",})
public class PastCasesPanel extends BaseDataSourceSummaryPanel {
private static final long serialVersionUID = 1L;
private static final ColumnModel<Pair<String, Long>> CASE_COL = new ColumnModel<>(
// model for column indicating the case
private static final ColumnModel<Pair<String, Long>, DefaultCellModel<?>> CASE_COL = new ColumnModel<>(
Bundle.PastCasesPanel_caseColumn_title(),
(pair) -> new DefaultCellModel(pair.getKey()),
(pair) -> new DefaultCellModel<>(pair.getKey()),
300
);
private static final ColumnModel<Pair<String, Long>> COUNT_COL = new ColumnModel<>(
// model for column indicating the count
private static final ColumnModel<Pair<String, Long>, DefaultCellModel<?>> COUNT_COL = new ColumnModel<>(
Bundle.PastCasesPanel_countColumn_title(),
(pair) -> new DefaultCellModel(String.valueOf(pair.getValue())),
(pair) -> new DefaultCellModel<>(pair.getValue()),
100
);
private static final List<ColumnModel<Pair<String, Long>>> DEFAULT_COLUMNS = Arrays.asList(CASE_COL, COUNT_COL);
// the template for columns in both tables in this tab
private static List<ColumnModel<Pair<String, Long>, DefaultCellModel<?>>> DEFAULT_TEMPLATE
= Arrays.asList(CASE_COL, COUNT_COL);
private final JTablePanel<Pair<String, Long>> notableFileTable = JTablePanel.getJTablePanel(DEFAULT_COLUMNS);
private final JTablePanel<Pair<String, Long>> notableFileTable = JTablePanel.getJTablePanel(DEFAULT_TEMPLATE);
private final JTablePanel<Pair<String, Long>> sameIdTable = JTablePanel.getJTablePanel(DEFAULT_COLUMNS);
private final JTablePanel<Pair<String, Long>> sameIdTable = JTablePanel.getJTablePanel(DEFAULT_TEMPLATE);
private final List<JTablePanel<?>> tables = Arrays.asList(
notableFileTable,
@ -73,6 +83,8 @@ public class PastCasesPanel extends BaseDataSourceSummaryPanel {
private final IngestRunningLabel ingestRunningLabel = new IngestRunningLabel();
private final DataFetcher<DataSource, PastCasesResult> pastCasesFetcher;
public PastCasesPanel() {
this(new PastCasesSummary());
}
@ -83,10 +95,12 @@ public class PastCasesPanel extends BaseDataSourceSummaryPanel {
public PastCasesPanel(PastCasesSummary pastCaseData) {
super(pastCaseData);
this.pastCasesFetcher = (dataSource) -> pastCaseData.getPastCasesData(dataSource);
// set up data acquisition methods
dataFetchComponents = Arrays.asList(
new DataFetchWorker.DataFetchComponents<>(
(dataSource) -> pastCaseData.getPastCasesData(dataSource),
pastCasesFetcher,
(result) -> handleResult(result))
);
@ -114,6 +128,19 @@ public class PastCasesPanel extends BaseDataSourceSummaryPanel {
onNewDataSource(dataFetchComponents, tables, dataSource);
}
@Override
List<ExcelExport.ExcelSheetExport> getExports(DataSource dataSource) {
PastCasesResult result = getFetchResult(pastCasesFetcher, "Past cases sheets", dataSource);
if (result == null) {
return Collections.emptyList();
}
return Arrays.asList(
getTableExport(DEFAULT_TEMPLATE, Bundle.PastCasesPanel_notableFileTable_tabName(), result.getTaggedNotable()),
getTableExport(DEFAULT_TEMPLATE, Bundle.PastCasesPanel_sameIdsTable_tabName(), result.getSameIdsResults())
);
}
@Override
public void close() {
ingestRunningLabel.unregister();

View File

@ -18,39 +18,100 @@
*/
package org.sleuthkit.autopsy.datasourcesummary.ui;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary.RecentAttachmentDetails;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary.RecentDownloadDetails;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary.RecentFileDetails;
import static org.sleuthkit.autopsy.datasourcesummary.ui.BaseDataSourceSummaryPanel.getTableExport;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer.DefaultCellModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer.MenuItem;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ColumnModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetcher;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DefaultCellModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.GuiCellModel.MenuItem;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel.ColumnModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ListTableModel;
import org.sleuthkit.datamodel.DataSource;
/**
* Data Source Summary recent files panel.
*/
@Messages({
"RecentFilesPanel_docsTable_tabName=Recently Opened Documents",
"RecentFilesPanel_downloadsTable_tabName=Recently Downloads",
"RecentFilesPanel_attachmentsTable_tabName=Recent Attachments",})
public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
private static final long serialVersionUID = 1L;
private static final String DATETIME_FORMAT_STR = "yyyy/MM/dd HH:mm:ss";
private static final DateFormat DATETIME_FORMAT = new SimpleDateFormat(DATETIME_FORMAT_STR, Locale.getDefault());
private final List<JTablePanel<?>> tablePanelList = new ArrayList<>();
private final List<DataFetchWorker.DataFetchComponents<DataSource, ?>> dataFetchComponents = new ArrayList<>();
private final IngestRunningLabel ingestRunningLabel = new IngestRunningLabel();
private final RecentFilesSummary dataHandler;
private final DataFetcher<DataSource, List<RecentFileDetails>> docsFetcher;
private final DataFetcher<DataSource, List<RecentDownloadDetails>> downloadsFetcher;
private final DataFetcher<DataSource, List<RecentAttachmentDetails>> attachmentsFetcher;
private final List<ColumnModel<RecentFileDetails, DefaultCellModel<?>>> docsTemplate = Arrays.asList(
new ColumnModel<>(Bundle.RecentFilePanel_col_header_path(),
(prog) -> {
return new DefaultCellModel<>(prog.getPath())
.setPopupMenuRetriever(getPopupFunct(prog));
}, 250),
new ColumnModel<>(Bundle.RecentFilesPanel_col_head_date(),
getDateFunct(),
80));
private final List<ColumnModel<RecentDownloadDetails, DefaultCellModel<?>>> downloadsTemplate = Arrays.asList(
new ColumnModel<>(Bundle.RecentFilePanel_col_header_domain(),
(prog) -> {
return new DefaultCellModel<>(prog.getWebDomain())
.setPopupMenuRetriever(getPopupFunct(prog));
}, 100),
new ColumnModel<>(Bundle.RecentFilePanel_col_header_path(),
(prog) -> {
return new DefaultCellModel<>(prog.getPath())
.setPopupMenuRetriever(getPopupFunct(prog));
}, 250),
new ColumnModel<>(Bundle.RecentFilesPanel_col_head_date(),
getDateFunct(),
80));
private final List<ColumnModel<RecentAttachmentDetails, DefaultCellModel<?>>> attachmentsTemplate = Arrays.asList(
new ColumnModel<>(Bundle.RecentFilePanel_col_header_path(),
(prog) -> {
return new DefaultCellModel<>(prog.getPath())
.setPopupMenuRetriever(getPopupFunct(prog));
}, 250),
new ColumnModel<>(Bundle.RecentFilesPanel_col_head_date(),
getDateFunct(),
80),
new ColumnModel<>(Bundle.RecentFilePanel_col_header_sender(),
(prog) -> {
return new DefaultCellModel<>(prog.getSender())
.setPopupMenuRetriever(getPopupFunct(prog));
}, 150));
/**
* Default constructor.
*/
@Messages({
"RecentFilesPanel_col_head_date=Date",
"RecentFilePanel_col_header_domain=Domain",
@ -58,10 +119,6 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
"RecentFilePanel_col_header_sender=Sender",
"RecentFilePanel_emailParserModuleName=Email Parser"
})
/**
* Default constructor.
*/
public RecentFilesPanel() {
this(new RecentFilesSummary());
}
@ -71,12 +128,28 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
*/
public RecentFilesPanel(RecentFilesSummary dataHandler) {
super(dataHandler);
this.dataHandler = dataHandler;
docsFetcher = (dataSource) -> dataHandler.getRecentlyOpenedDocuments(dataSource, 10);
downloadsFetcher = (dataSource) -> dataHandler.getRecentDownloads(dataSource, 10);
attachmentsFetcher = (dataSource) -> dataHandler.getRecentAttachments(dataSource, 10);
initComponents();
initalizeTables();
}
/**
* Returns a function that gets the date from the RecentFileDetails object and
* converts into a DefaultCellModel to be displayed in a table.
*
* @return The function that determines the date cell from a RecentFileDetails object.
*/
private <T extends RecentFileDetails> Function<T, DefaultCellModel<?>> getDateFunct() {
return (T lastAccessed) -> {
Function<Date, String> dateParser = (dt) -> dt == null ? "" : DATETIME_FORMAT.format(dt);
return new DefaultCellModel<>(new Date(lastAccessed.getDateAsLong() * 1000), dateParser, DATETIME_FORMAT_STR)
.setPopupMenuRetriever(getPopupFunct(lastAccessed));
};
}
/**
* Takes a base class of RecentFileDetails and provides the pertinent menu
* items.
@ -117,6 +190,16 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
onNewDataSource(dataFetchComponents, tablePanelList, dataSource);
}
@Override
List<ExcelExport.ExcelSheetExport> getExports(DataSource dataSource) {
return Stream.of(
getTableExport(docsFetcher, docsTemplate, Bundle.RecentFilesPanel_docsTable_tabName(), dataSource),
getTableExport(downloadsFetcher, downloadsTemplate, Bundle.RecentFilesPanel_downloadsTable_tabName(), dataSource),
getTableExport(attachmentsFetcher, attachmentsTemplate, Bundle.RecentFilesPanel_attachmentsTable_tabName(), dataSource))
.filter(sheet -> sheet != null)
.collect(Collectors.toList());
}
@Override
public void close() {
ingestRunningLabel.unregister();
@ -140,30 +223,18 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
*/
@SuppressWarnings("unchecked")
private void initalizeOpenDocsTable() {
List<ColumnModel<RecentFileDetails>> list = Arrays.asList(
new ColumnModel<>(Bundle.RecentFilePanel_col_header_path(),
(prog) -> {
return new DefaultCellModel(prog.getPath())
.setPopupMenuRetriever(getPopupFunct(prog));
}, 250),
new ColumnModel<>(Bundle.RecentFilesPanel_col_head_date(),
(prog) -> {
return new DefaultCellModel(prog.getDateAsString())
.setPopupMenuRetriever(getPopupFunct(prog));
}, 80));
ListTableModel<RecentFileDetails> tableModel = JTablePanel.getTableModel(list);
ListTableModel<RecentFileDetails> tableModel = JTablePanel.getTableModel(docsTemplate);
JTablePanel<RecentFileDetails> pane = (JTablePanel<RecentFileDetails>) openedDocPane;
pane.setModel(tableModel);
pane.setColumnModel(JTablePanel.getTableColumnModel(list));
pane.setColumnModel(JTablePanel.getTableColumnModel(docsTemplate));
pane.setKeyFunction((recentFile) -> recentFile.getPath());
pane.setCellListener(CellModelTableCellRenderer.getMouseListener());
tablePanelList.add(pane);
DataFetchWorker.DataFetchComponents<DataSource, List<RecentFileDetails>> worker
= new DataFetchWorker.DataFetchComponents<>(
(dataSource) -> dataHandler.getRecentlyOpenedDocuments(dataSource, 10),
docsFetcher,
(result) -> pane.showDataFetchResult(result));
dataFetchComponents.add(worker);
@ -174,35 +245,18 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
*/
@SuppressWarnings("unchecked")
private void initalizeDownloadTable() {
List<ColumnModel<RecentDownloadDetails>> list = Arrays.asList(
new ColumnModel<>(Bundle.RecentFilePanel_col_header_domain(),
(prog) -> {
return new DefaultCellModel(prog.getWebDomain())
.setPopupMenuRetriever(getPopupFunct(prog));
}, 100),
new ColumnModel<>(Bundle.RecentFilePanel_col_header_path(),
(prog) -> {
return new DefaultCellModel(prog.getPath())
.setPopupMenuRetriever(getPopupFunct(prog));
}, 250),
new ColumnModel<>(Bundle.RecentFilesPanel_col_head_date(),
(prog) -> {
return new DefaultCellModel(prog.getDateAsString())
.setPopupMenuRetriever(getPopupFunct(prog));
}, 80));
ListTableModel<RecentDownloadDetails> tableModel = JTablePanel.getTableModel(list);
ListTableModel<RecentDownloadDetails> tableModel = JTablePanel.getTableModel(downloadsTemplate);
JTablePanel<RecentDownloadDetails> pane = (JTablePanel<RecentDownloadDetails>) downloadsPane;
pane.setModel(tableModel);
pane.setKeyFunction((download) -> download.getPath());
pane.setColumnModel(JTablePanel.getTableColumnModel(list));
pane.setColumnModel(JTablePanel.getTableColumnModel(downloadsTemplate));
pane.setCellListener(CellModelTableCellRenderer.getMouseListener());
tablePanelList.add(pane);
DataFetchWorker.DataFetchComponents<DataSource, List<RecentDownloadDetails>> worker
= new DataFetchWorker.DataFetchComponents<>(
(dataSource) -> dataHandler.getRecentDownloads(dataSource, 10),
downloadsFetcher,
(result) -> pane.showDataFetchResult(result));
dataFetchComponents.add(worker);
@ -213,35 +267,18 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
*/
@SuppressWarnings("unchecked")
private void initalizeAttchementsTable() {
List<ColumnModel<RecentAttachmentDetails>> list = Arrays.asList(
new ColumnModel<>(Bundle.RecentFilePanel_col_header_path(),
(prog) -> {
return new DefaultCellModel(prog.getPath())
.setPopupMenuRetriever(getPopupFunct(prog));
}, 250),
new ColumnModel<>(Bundle.RecentFilesPanel_col_head_date(),
(prog) -> {
return new DefaultCellModel(prog.getDateAsString())
.setPopupMenuRetriever(getPopupFunct(prog));
}, 80),
new ColumnModel<>(Bundle.RecentFilePanel_col_header_sender(),
(prog) -> {
return new DefaultCellModel(prog.getSender())
.setPopupMenuRetriever(getPopupFunct(prog));
}, 150));
ListTableModel<RecentAttachmentDetails> tableModel = JTablePanel.getTableModel(list);
ListTableModel<RecentAttachmentDetails> tableModel = JTablePanel.getTableModel(attachmentsTemplate);
JTablePanel<RecentAttachmentDetails> pane = (JTablePanel<RecentAttachmentDetails>) attachmentsPane;
pane.setModel(tableModel);
pane.setKeyFunction((attachment) -> attachment.getPath());
pane.setColumnModel(JTablePanel.getTableColumnModel(list));
pane.setColumnModel(JTablePanel.getTableColumnModel(attachmentsTemplate));
pane.setCellListener(CellModelTableCellRenderer.getMouseListener());
tablePanelList.add(pane);
DataFetchWorker.DataFetchComponents<DataSource, List<RecentAttachmentDetails>> worker
= new DataFetchWorker.DataFetchComponents<>(
(dataSource) -> dataHandler.getRecentAttachments(dataSource, 10),
attachmentsFetcher,
(result) -> pane.showDataFetchResult(result)
);

View File

@ -23,6 +23,7 @@ import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
@ -45,6 +46,7 @@ import org.sleuthkit.autopsy.datasourcesummary.uiutils.BarChartPanel.OrderedKey;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker.DataFetchComponents;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.LoadableComponent;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.LoadableLabel;
@ -65,7 +67,7 @@ import org.sleuthkit.datamodel.TskCoreException;
"TimlinePanel_last30DaysChart_fileEvts_title=File Events",
"TimlinePanel_last30DaysChart_artifactEvts_title=Result Events",})
public class TimelinePanel extends BaseDataSourceSummaryPanel {
private static final Logger logger = Logger.getLogger(TimelinePanel.class.getName());
private static final long serialVersionUID = 1L;
private static final DateFormat EARLIEST_LATEST_FORMAT = getUtcFormat("MMM d, yyyy");
@ -94,7 +96,7 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel {
// actions to load data for this tab
private final List<DataFetchComponents<DataSource, ?>> dataFetchComponents;
public TimelinePanel() {
this(new TimelineSummary());
}
@ -104,14 +106,14 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel {
*/
public TimelinePanel(TimelineSummary timelineData) {
super(timelineData);
// set up data acquisition methods
dataFetchComponents = Arrays.asList(
new DataFetchWorker.DataFetchComponents<>(
(dataSource) -> timelineData.getData(dataSource, MOST_RECENT_DAYS_COUNT),
(result) -> handleResult(result))
);
initComponents();
}
@ -127,7 +129,7 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel {
private static String formatDate(Date date, DateFormat formatter) {
return date == null ? null : formatter.format(date);
}
private static final Color FILE_EVT_COLOR = new Color(228, 22, 28);
private static final Color ARTIFACT_EVT_COLOR = new Color(21, 227, 100);
@ -147,25 +149,25 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel {
// Create a bar chart item for each recent days activity item
List<BarChartItem> fileEvtCounts = new ArrayList<>();
List<BarChartItem> artifactEvtCounts = new ArrayList<>();
for (int i = 0; i < recentDaysActivity.size(); i++) {
DailyActivityAmount curItem = recentDaysActivity.get(i);
long fileAmt = curItem.getFileActivityCount();
long artifactAmt = curItem.getArtifactActivityCount() * 100;
String formattedDate = (i == 0 || i == recentDaysActivity.size() - 1)
? formatDate(curItem.getDay(), CHART_FORMAT) : "";
OrderedKey thisKey = new OrderedKey(formattedDate, i);
fileEvtCounts.add(new BarChartItem(thisKey, fileAmt));
artifactEvtCounts.add(new BarChartItem(thisKey, artifactAmt));
}
return Arrays.asList(
new BarChartSeries(Bundle.TimlinePanel_last30DaysChart_fileEvts_title(), FILE_EVT_COLOR, fileEvtCounts),
new BarChartSeries(Bundle.TimlinePanel_last30DaysChart_artifactEvts_title(), ARTIFACT_EVT_COLOR, artifactEvtCounts));
}
private final Object timelineBtnLock = new Object();
private TimelineSummaryData curTimelineData = null;
@ -181,11 +183,11 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel {
earliestLabel.showDataFetchResult(DataFetchResult.getSubResult(result, r -> formatDate(r.getMinDate(), EARLIEST_LATEST_FORMAT)));
latestLabel.showDataFetchResult(DataFetchResult.getSubResult(result, r -> formatDate(r.getMaxDate(), EARLIEST_LATEST_FORMAT)));
last30DaysChart.showDataFetchResult(DataFetchResult.getSubResult(result, r -> parseChartData(r.getMostRecentDaysActivity())));
if (result != null
&& result.getResultType() == DataFetchResult.ResultType.SUCCESS
&& result.getData() != null) {
synchronized (this.timelineBtnLock) {
this.curTimelineData = result.getData();
this.viewInTimelineBtn.setEnabled(true);
@ -210,7 +212,7 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel {
if (curTimelineData == null) {
return;
}
dataSource = curTimelineData.getDataSource();
if (CollectionUtils.isNotEmpty(curTimelineData.getMostRecentDaysActivity())) {
minDate = curTimelineData.getMostRecentDaysActivity().get(0).getDay();
@ -221,7 +223,7 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel {
}
}
}
openFilteredChart(dataSource, minDate, maxDate);
}
@ -240,46 +242,51 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel {
// notify dialog (if in dialog) should close.
TimelinePanel.this.notifyParentClose();
Interval timeSpan = null;
try {
final TimeLineController controller = TimeLineModule.getController();
if (dataSource != null) {
controller.pushFilters(timelineUtils.getDataSourceFilterState(dataSource));
}
if (minDate != null && maxDate != null) {
timeSpan = new Interval(new DateTime(minDate), new DateTime(maxDate));
}
} catch (NoCurrentCaseException | TskCoreException ex) {
logger.log(Level.WARNING, "Unable to view time range in Timeline view", ex);
}
try {
openTimelineAction.showTimeline(timeSpan);
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "An unexpected exception occurred while opening the timeline.", ex);
}
}
@Override
protected void fetchInformation(DataSource dataSource) {
fetchInformation(dataFetchComponents, dataSource);
}
@Override
protected void onNewDataSource(DataSource dataSource) {
onNewDataSource(dataFetchComponents, loadableComponents, dataSource);
}
@Override
public void close() {
ingestRunningLabel.unregister();
super.close();
}
@Override
List<ExcelExport.ExcelSheetExport> getExports(DataSource dataSource) {
return Collections.emptyList();
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
@ -373,4 +380,5 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel {
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton viewInTimelineBtn;
// End of variables declaration//GEN-END:variables
}

View File

@ -23,6 +23,7 @@ import java.sql.SQLException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@ -39,6 +40,7 @@ import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult.ResultType;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker.DataFetchComponents;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.LoadableComponent;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.LoadableLabel;
@ -404,6 +406,11 @@ class TypesPanel extends BaseDataSourceSummaryPanel {
return longVal == null ? "0" : COMMA_FORMATTER.format(longVal);
}
@Override
List<ExcelExport.ExcelSheetExport> getExports(DataSource dataSource) {
return Collections.emptyList();
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always

View File

@ -25,6 +25,9 @@ import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.lang.StringUtils;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary;
@ -34,12 +37,15 @@ import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.Top
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopWebSearchResult;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopDomainsResult;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopProgramsResult;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer.DefaultCellModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer.MenuItem;
import static org.sleuthkit.autopsy.datasourcesummary.ui.BaseDataSourceSummaryPanel.getTableExport;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ColumnModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker.DataFetchComponents;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetcher;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DefaultCellModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.GuiCellModel.MenuItem;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel.ColumnModel;
import org.sleuthkit.datamodel.DataSource;
/**
@ -47,6 +53,11 @@ import org.sleuthkit.datamodel.DataSource;
*/
@Messages({
"UserActivityPanel_tab_title=User Activity",
"UserActivityPanel_TopProgramsTableModel_tabName=Recent Programs",
"UserActivityPanel_TopDomainsTableModel_tabName=Recent Domains",
"UserActivityPanel_TopWebSearchTableModel_tabName=Recent Web Searches",
"UserActivityPanel_TopDeviceAttachedTableModel_tabName=Recent Devices Attached",
"UserActivityPanel_TopAccountTableModel_tabName=Recent Account Types Used",
"UserActivityPanel_TopProgramsTableModel_name_header=Program",
"UserActivityPanel_TopProgramsTableModel_folder_header=Folder",
"UserActivityPanel_TopProgramsTableModel_count_header=Run Times",
@ -66,33 +77,21 @@ import org.sleuthkit.datamodel.DataSource;
public class UserActivityPanel extends BaseDataSourceSummaryPanel {
private static final long serialVersionUID = 1L;
private static final DateFormat DATETIME_FORMAT = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.getDefault());
private static final String DATETIME_FORMAT_STR = "yyyy/MM/dd HH:mm:ss";
private static final DateFormat DATETIME_FORMAT = new SimpleDateFormat(DATETIME_FORMAT_STR, Locale.getDefault());
private static final int TOP_PROGS_COUNT = 10;
private static final int TOP_DOMAINS_COUNT = 10;
private static final int TOP_SEARCHES_COUNT = 10;
private static final int TOP_ACCOUNTS_COUNT = 5;
private static final int TOP_DEVICES_COUNT = 10;
private static final String ANDROID_FACTORY = "org.python.proxies.module$AndroidModuleFactory";
private static final String ANDROID_MODULE_NAME = "Android Analyzer";
/**
* Gets a string formatted date or returns empty string if the date is null.
*
* @param date The date.
*
* @return The formatted date string or empty string if the date is null.
*/
private static String getFormatted(Date date) {
return date == null ? "" : DATETIME_FORMAT.format(date);
}
// set up recent programs table
private final JTablePanel<TopProgramsResult> topProgramsTable = JTablePanel.getJTablePanel(Arrays.asList(
private final List<ColumnModel<TopProgramsResult, DefaultCellModel<?>>> topProgramsTemplate = Arrays.asList(
// program name column
new ColumnModel<TopProgramsResult>(
new ColumnModel<>(
Bundle.UserActivityPanel_TopProgramsTableModel_name_header(),
(prog) -> {
return new DefaultCellModel(prog.getProgramName())
return new DefaultCellModel<>(prog.getProgramName())
.setTooltip(prog.getProgramPath())
.setPopupMenu(getPopup(prog));
},
@ -101,7 +100,7 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
new ColumnModel<>(
Bundle.UserActivityPanel_TopProgramsTableModel_folder_header(),
(prog) -> {
return new DefaultCellModel(
return new DefaultCellModel<>(
getShortFolderName(
prog.getProgramPath(),
prog.getProgramName()))
@ -113,29 +112,24 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
new ColumnModel<>(
Bundle.UserActivityPanel_TopProgramsTableModel_count_header(),
(prog) -> {
String runTimes = prog.getRunTimes() == null ? "" : Long.toString(prog.getRunTimes());
return new DefaultCellModel(runTimes)
return new DefaultCellModel<>(prog.getRunTimes(), (num) -> num == null ? "" : num.toString())
.setPopupMenu(getPopup(prog));
},
80),
// last run date column
new ColumnModel<>(
Bundle.UserActivityPanel_TopProgramsTableModel_lastrun_header(),
(prog) -> {
return new DefaultCellModel(getFormatted(prog.getLastAccessed()))
.setPopupMenu(getPopup(prog));
},
getDateFunct(),
150)
))
.setKeyFunction((prog) -> prog.getProgramPath() + ":" + prog.getProgramName());
);
// set up recent domains table
private final JTablePanel<TopDomainsResult> recentDomainsTable = JTablePanel.getJTablePanel(Arrays.asList(
private final List<ColumnModel<TopDomainsResult, DefaultCellModel<?>>> topDomainsTemplate = Arrays.asList(
// domain column
new ColumnModel<TopDomainsResult>(
new ColumnModel<>(
Bundle.UserActivityPanel_TopDomainsTableModel_domain_header(),
(recentDomain) -> {
return new DefaultCellModel(recentDomain.getDomain())
return new DefaultCellModel<>(recentDomain.getDomain())
.setPopupMenu(getPopup(recentDomain));
},
250),
@ -143,29 +137,24 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
new ColumnModel<>(
Bundle.UserActivityPanel_TopDomainsTableModel_count_header(),
(recentDomain) -> {
String visitTimes = recentDomain.getVisitTimes() == null ? "" : Long.toString(recentDomain.getVisitTimes());
return new DefaultCellModel(visitTimes)
return new DefaultCellModel<>(recentDomain.getVisitTimes(), (num) -> num == null ? "" : num.toString())
.setPopupMenu(getPopup(recentDomain));
},
100),
// last accessed column
new ColumnModel<>(
Bundle.UserActivityPanel_TopDomainsTableModel_lastAccess_header(),
(recentDomain) -> {
return new DefaultCellModel(getFormatted(recentDomain.getLastAccessed()))
.setPopupMenu(getPopup(recentDomain));
},
getDateFunct(),
150)
))
.setKeyFunction((domain) -> domain.getDomain());
);
// top web searches table
private final JTablePanel<TopWebSearchResult> topWebSearchesTable = JTablePanel.getJTablePanel(Arrays.asList(
private final List<ColumnModel<TopWebSearchResult, DefaultCellModel<?>>> topWebSearchesTemplate = Arrays.asList(
// search string column
new ColumnModel<TopWebSearchResult>(
new ColumnModel<>(
Bundle.UserActivityPanel_TopWebSearchTableModel_searchString_header(),
(webSearch) -> {
return new DefaultCellModel(webSearch.getSearchString())
return new DefaultCellModel<>(webSearch.getSearchString())
.setPopupMenu(getPopup(webSearch));
},
250
@ -173,31 +162,27 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
// last accessed
new ColumnModel<>(
Bundle.UserActivityPanel_TopWebSearchTableModel_dateAccessed_header(),
(webSearch) -> {
return new DefaultCellModel(getFormatted(webSearch.getLastAccessed()))
.setPopupMenu(getPopup(webSearch));
},
getDateFunct(),
150
),
// translated value
new ColumnModel<>(
Bundle.UserActivityPanel_TopWebSearchTableModel_translatedResult_header(),
(webSearch) -> {
return new DefaultCellModel(webSearch.getTranslatedResult())
return new DefaultCellModel<>(webSearch.getTranslatedResult())
.setPopupMenu(getPopup(webSearch));
},
250
)
))
.setKeyFunction((query) -> query.getSearchString());
);
// top devices attached table
private final JTablePanel<TopDeviceAttachedResult> topDevicesAttachedTable = JTablePanel.getJTablePanel(Arrays.asList(
private final List<ColumnModel<TopDeviceAttachedResult, DefaultCellModel<?>>> topDevicesTemplate = Arrays.asList(
// device id column
new ColumnModel<TopDeviceAttachedResult>(
new ColumnModel<>(
Bundle.UserActivityPanel_TopDeviceAttachedTableModel_deviceId_header(),
(device) -> {
return new DefaultCellModel(device.getDeviceId())
return new DefaultCellModel<>(device.getDeviceId())
.setPopupMenu(getPopup(device));
},
250
@ -205,10 +190,7 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
// last accessed
new ColumnModel<>(
Bundle.UserActivityPanel_TopDeviceAttachedTableModel_dateAccessed_header(),
(device) -> {
return new DefaultCellModel(getFormatted(device.getLastAccessed()))
.setPopupMenu(getPopup(device));
},
getDateFunct(),
150
),
// make and model
@ -220,21 +202,20 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
String makeModelString = (make.isEmpty() || model.isEmpty())
? make + model
: String.format("%s - %s", make, model);
return new DefaultCellModel(makeModelString)
return new DefaultCellModel<>(makeModelString)
.setPopupMenu(getPopup(device));
},
250
)
))
.setKeyFunction((topDevice) -> topDevice.getDeviceId());
);
// top accounts table
private final JTablePanel<TopAccountResult> topAccountsTable = JTablePanel.getJTablePanel(Arrays.asList(
private final List<ColumnModel<TopAccountResult, DefaultCellModel<?>>> topAccountsTemplate = Arrays.asList(
// account type column
new ColumnModel<TopAccountResult>(
new ColumnModel<>(
Bundle.UserActivityPanel_TopAccountTableModel_accountType_header(),
(account) -> {
return new DefaultCellModel(account.getAccountType())
return new DefaultCellModel<>(account.getAccountType())
.setPopupMenu(getPopup(account));
},
250
@ -242,15 +223,37 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
// last accessed
new ColumnModel<>(
Bundle.UserActivityPanel_TopAccountTableModel_lastAccess_header(),
(account) -> {
return new DefaultCellModel(getFormatted(account.getLastAccessed()))
.setPopupMenu(getPopup(account));
},
getDateFunct(),
150
)
))
);
// set up recent programs table
private final JTablePanel<TopProgramsResult> topProgramsTable = JTablePanel.getJTablePanel(topProgramsTemplate)
.setKeyFunction((prog) -> prog.getProgramPath() + ":" + prog.getProgramName());
// set up recent domains table
private final JTablePanel<TopDomainsResult> recentDomainsTable = JTablePanel.getJTablePanel(topDomainsTemplate)
.setKeyFunction((domain) -> domain.getDomain());
// top web searches table
private final JTablePanel<TopWebSearchResult> topWebSearchesTable = JTablePanel.getJTablePanel(topWebSearchesTemplate)
.setKeyFunction((query) -> query.getSearchString());
// top devices attached table
private final JTablePanel<TopDeviceAttachedResult> topDevicesAttachedTable = JTablePanel.getJTablePanel(topDevicesTemplate)
.setKeyFunction((topDevice) -> topDevice.getDeviceId());
// top accounts table
private final JTablePanel<TopAccountResult> topAccountsTable = JTablePanel.getJTablePanel(topAccountsTemplate)
.setKeyFunction((topAccount) -> topAccount.getAccountType());
private final DataFetcher<DataSource, List<TopProgramsResult>> topProgramsFetcher;
private final DataFetcher<DataSource, List<TopDomainsResult>> topDomainsFetcher;
private final DataFetcher<DataSource, List<TopWebSearchResult>> topWebSearchesFetcher;
private final DataFetcher<DataSource, List<TopDeviceAttachedResult>> topDevicesAttachedFetcher;
private final DataFetcher<DataSource, List<TopAccountResult>> topAccountsFetcher;
private final List<JTablePanel<?>> tables = Arrays.asList(
topProgramsTable,
recentDomainsTable,
@ -281,33 +284,47 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
super(userActivityData);
this.userActivityData = userActivityData;
this.topProgramsFetcher = (dataSource) -> userActivityData.getTopPrograms(dataSource, TOP_PROGS_COUNT);
this.topDomainsFetcher = (dataSource) -> userActivityData.getRecentDomains(dataSource, TOP_DOMAINS_COUNT);
this.topWebSearchesFetcher = (dataSource) -> userActivityData.getMostRecentWebSearches(dataSource, TOP_SEARCHES_COUNT);
this.topDevicesAttachedFetcher = (dataSource) -> userActivityData.getRecentDevices(dataSource, TOP_DEVICES_COUNT);
this.topAccountsFetcher = (dataSource) -> userActivityData.getRecentAccounts(dataSource, TOP_ACCOUNTS_COUNT);
// set up data acquisition methods
this.dataFetchComponents = Arrays.asList(
// top programs query
new DataFetchComponents<DataSource, List<TopProgramsResult>>(
(dataSource) -> userActivityData.getTopPrograms(dataSource, TOP_PROGS_COUNT),
topProgramsFetcher,
(result) -> topProgramsTable.showDataFetchResult(result)),
// top domains query
new DataFetchComponents<DataSource, List<TopDomainsResult>>(
(dataSource) -> userActivityData.getRecentDomains(dataSource, TOP_DOMAINS_COUNT),
topDomainsFetcher,
(result) -> recentDomainsTable.showDataFetchResult(result)),
// top web searches query
new DataFetchComponents<DataSource, List<TopWebSearchResult>>(
(dataSource) -> userActivityData.getMostRecentWebSearches(dataSource, TOP_SEARCHES_COUNT),
topWebSearchesFetcher,
(result) -> topWebSearchesTable.showDataFetchResult(result)),
// top devices query
new DataFetchComponents<DataSource, List<TopDeviceAttachedResult>>(
(dataSource) -> userActivityData.getRecentDevices(dataSource, TOP_DEVICES_COUNT),
topDevicesAttachedFetcher,
(result) -> topDevicesAttachedTable.showDataFetchResult(result)),
// top accounts query
new DataFetchComponents<DataSource, List<TopAccountResult>>(
(dataSource) -> userActivityData.getRecentAccounts(dataSource, TOP_ACCOUNTS_COUNT),
topAccountsFetcher,
(result) -> topAccountsTable.showDataFetchResult(result))
);
initComponents();
}
private <T extends LastAccessedArtifact> Function<T, DefaultCellModel<?>> getDateFunct() {
return (T lastAccessed) -> {
Function<Date, String> dateParser = (dt) -> dt == null ? "" : DATETIME_FORMAT.format(dt);
return new DefaultCellModel<>(lastAccessed.getLastAccessed(), dateParser, DATETIME_FORMAT_STR)
.setPopupMenu(getPopup(lastAccessed));
};
}
/**
* Takes a base class of LastAccessedArtifact and provides the pertinent
* menu items. going to artifact.
@ -349,6 +366,18 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
super.close();
}
@Override
List<ExcelExport.ExcelSheetExport> getExports(DataSource dataSource) {
return Stream.of(
getTableExport(topProgramsFetcher, topProgramsTemplate, Bundle.UserActivityPanel_TopProgramsTableModel_tabName(), dataSource),
getTableExport(topDomainsFetcher, topDomainsTemplate, Bundle.UserActivityPanel_TopDomainsTableModel_tabName(), dataSource),
getTableExport(topWebSearchesFetcher, topWebSearchesTemplate, Bundle.UserActivityPanel_TopWebSearchTableModel_tabName(), dataSource),
getTableExport(topDevicesAttachedFetcher, topDevicesTemplate, Bundle.UserActivityPanel_TopDeviceAttachedTableModel_tabName(), dataSource),
getTableExport(topAccountsFetcher, topAccountsTemplate, Bundle.UserActivityPanel_TopAccountTableModel_tabName(), dataSource))
.filter(sheet -> sheet != null)
.collect(Collectors.toList());
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always

View File

@ -1,5 +1,7 @@
AbstractLoadableComponent_errorMessage_defaultText=There was an error loading results.
AbstractLoadableComponent_loadingMessage_defaultText=Loading results...
AbstractLoadableComponent_noDataExists_defaultText=No data exists.
# {0} - sheetNumber
ExcelExport_writeExcel_noSheetName=Sheet {0}
IngestRunningLabel_defaultMessage=Ingest is currently running.
PieChartPanel_noDataLabel=No Data

View File

@ -0,0 +1,79 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2021 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.datasourcesummary.uiutils;
import javax.swing.JLabel;
/**
* Basic interface for a cell model.
*/
public interface CellModel {
/**
* Describes the horizontal alignment.
*/
public enum HorizontalAlign {
LEFT(JLabel.LEFT),
CENTER(JLabel.CENTER),
RIGHT(JLabel.RIGHT);
private final int jlabelAlignment;
/**
* Constructor for a HorizontalAlign enum.
*
* @param jlabelAlignment The corresponding JLabel horizontal alignment
* number.
*/
HorizontalAlign(int jlabelAlignment) {
this.jlabelAlignment = jlabelAlignment;
}
/**
* @return The corresponding JLabel horizontal alignment (i.e.
* JLabel.LEFT).
*/
int getJLabelAlignment() {
return this.jlabelAlignment;
}
}
/**
* @return The root data object.
*/
Object getData();
/**
* @return The text to be shown in the cell.
*/
default String getText() {
Object data = getData();
return (data == null) ? null : data.toString();
}
/**
* @return The tooltip (if any) to be displayed in the cell.
*/
String getTooltip();
/**
* @return The horizontal alignment for the text in the cell.
*/
HorizontalAlign getHorizontalAlignment();
}

View File

@ -21,10 +21,7 @@ package org.sleuthkit.autopsy.datasourcesummary.uiutils;
import java.awt.Component;
import java.awt.Insets;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;
import javax.swing.BorderFactory;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
@ -34,6 +31,7 @@ import javax.swing.border.Border;
import javax.swing.table.DefaultTableCellRenderer;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.GuiCellModel.MenuItem;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel.CellMouseEvent;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel.CellMouseListener;
@ -44,234 +42,6 @@ import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel.CellMouseList
public class CellModelTableCellRenderer extends DefaultTableCellRenderer {
private static final long serialVersionUID = 1L;
/**
* Describes the horizontal alignment.
*/
public enum HorizontalAlign {
LEFT(JLabel.LEFT),
CENTER(JLabel.CENTER),
RIGHT(JLabel.RIGHT);
private final int jlabelAlignment;
/**
* Constructor for a HorizontalAlign enum.
*
* @param jlabelAlignment The corresponding JLabel horizontal alignment
* number.
*/
HorizontalAlign(int jlabelAlignment) {
this.jlabelAlignment = jlabelAlignment;
}
/**
* @return The corresponding JLabel horizontal alignment (i.e.
* JLabel.LEFT).
*/
int getJLabelAlignment() {
return this.jlabelAlignment;
}
}
/**
* A menu item to be used within a popup menu.
*/
public interface MenuItem {
/**
* @return The title for that popup menu item.
*/
String getTitle();
/**
* @return The action if that popup menu item is clicked.
*/
Runnable getAction();
}
/**
* Default implementation of a menu item.
*/
public static class DefaultMenuItem implements MenuItem {
private final String title;
private final Runnable action;
/**
* Main constructor.
*
* @param title The title for the menu item.
* @param action The action should the menu item be clicked.
*/
public DefaultMenuItem(String title, Runnable action) {
this.title = title;
this.action = action;
}
@Override
public String getTitle() {
return title;
}
@Override
public Runnable getAction() {
return action;
}
}
/**
* Basic interface for a cell model.
*/
public interface CellModel {
/**
* @return The text to be shown in the cell.
*/
String getText();
/**
* @return The tooltip (if any) to be displayed in the cell.
*/
String getTooltip();
/**
* @return The horizontal alignment for the text in the cell.
*/
HorizontalAlign getHorizontalAlignment();
/**
* @return The insets for the cell text.
*/
Insets getInsets();
/**
* @return The popup menu associated with this cell or null if no popup
* menu should be shown for this cell.
*/
List<MenuItem> getPopupMenu();
}
/**
* The default cell model.
*/
public static class DefaultCellModel implements CellModel {
private final String text;
private String tooltip;
private HorizontalAlign horizontalAlignment;
private Insets insets;
private List<MenuItem> popupMenu;
private Supplier<List<MenuItem>> menuItemSupplier;
/**
* Main constructor.
*
* @param text The text to be displayed in the cell.
*/
public DefaultCellModel(String text) {
this.text = text;
this.tooltip = text;
}
@Override
public String getText() {
return text;
}
@Override
public String getTooltip() {
return tooltip;
}
/**
* Sets the tooltip for this cell model.
*
* @param tooltip The tooltip for the cell model.
*
* @return As a utility, returns this.
*/
public DefaultCellModel setTooltip(String tooltip) {
this.tooltip = tooltip;
return this;
}
@Override
public HorizontalAlign getHorizontalAlignment() {
return horizontalAlignment;
}
/**
* Sets the horizontal alignment for this cell model.
*
* @param alignment The horizontal alignment for the cell model.
*
* @return As a utility, returns this.
*/
public DefaultCellModel setHorizontalAlignment(HorizontalAlign alignment) {
this.horizontalAlignment = alignment;
return this;
}
@Override
public Insets getInsets() {
return insets;
}
/**
* Sets the insets for the text within the cell
*
* @param insets The insets.
*
* @return As a utility, returns this.
*/
public DefaultCellModel setInsets(Insets insets) {
this.insets = insets;
return this;
}
@Override
public List<MenuItem> getPopupMenu() {
if (popupMenu != null) {
return Collections.unmodifiableList(popupMenu);
}
if (menuItemSupplier != null) {
return this.menuItemSupplier.get();
}
return null;
}
/**
* Sets a function to lazy load the popup menu items.
*
* @param menuItemSupplier The lazy load function for popup items.
* @return
*/
public DefaultCellModel setPopupMenuRetriever(Supplier<List<MenuItem>> menuItemSupplier) {
this.menuItemSupplier = menuItemSupplier;
return this;
}
/**
* Sets the list of items for a popup menu
*
* @param popupMenu
* @return As a utility, returns this.
*/
public DefaultCellModel setPopupMenu(List<MenuItem> popupMenu) {
this.popupMenu = popupMenu == null ? null : new ArrayList<>(popupMenu);
return this;
}
@Override
public String toString() {
return getText();
}
}
private static final int DEFAULT_ALIGNMENT = JLabel.LEFT;
private static final Border DEFAULT_BORDER = BorderFactory.createEmptyBorder(1, 5, 1, 5);
@ -280,8 +50,8 @@ public class CellModelTableCellRenderer extends DefaultTableCellRenderer {
boolean isSelected, boolean hasFocus, int row, int column) {
JLabel c = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if (value instanceof CellModel) {
return getTableCellRendererComponent(c, (CellModel) value);
if (value instanceof GuiCellModel) {
return getTableCellRendererComponent(c, (GuiCellModel) value);
} else {
return c;
}
@ -297,7 +67,7 @@ public class CellModelTableCellRenderer extends DefaultTableCellRenderer {
*
* @return The provided defaultCell.
*/
protected Component getTableCellRendererComponent(JLabel defaultCell, CellModel cellModel) {
protected Component getTableCellRendererComponent(JLabel defaultCell, GuiCellModel cellModel) {
// sets the text for the cell or null if not present.
String text = cellModel.getText();
if (StringUtils.isNotBlank(text)) {
@ -340,9 +110,9 @@ public class CellModelTableCellRenderer extends DefaultTableCellRenderer {
@Override
public void mouseClicked(CellMouseEvent cellEvent) {
if (cellEvent.getCellValue() instanceof CellModel && cellEvent.getMouseEvent().getButton() != MouseEvent.BUTTON1) {
if (cellEvent.getCellValue() instanceof GuiCellModel && cellEvent.getMouseEvent().getButton() != MouseEvent.BUTTON1) {
cellEvent.getTable().setRowSelectionInterval(cellEvent.getRow(), cellEvent.getRow());
CellModel cellModel = (CellModel) cellEvent.getCellValue();
GuiCellModel cellModel = (GuiCellModel) cellEvent.getCellValue();
List<MenuItem> menuItems = cellModel.getPopupMenu();
// if there are menu items, show a popup menu for

View File

@ -0,0 +1,80 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2021 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.datasourcesummary.uiutils;
import java.util.function.Function;
/**
* Describes aspects of a column which can be used with getTableModel or
* getJTablePanel. 'T' represents the object that will represent rows in the
* table.
*/
public class ColumnModel<T, C extends CellModel> {
private final String headerTitle;
private final Function<T, ? extends C> cellRenderer;
private final Integer width;
/**
* Constructor for a DataResultColumnModel.
*
* @param headerTitle The title for the column.
* @param cellRenderer The method that generates a CellModel for the column
* based on the data.
*/
public ColumnModel(String headerTitle, Function<T, ? extends C> cellRenderer) {
this(headerTitle, cellRenderer, null);
}
/**
* Constructor for a DataResultColumnModel.
*
* @param headerTitle The title for the column.
* @param cellRenderer The method that generates a CellModel for the column
* based on the data.
* @param width The preferred width of the column.
*/
public ColumnModel(String headerTitle, Function<T, ? extends C> cellRenderer, Integer width) {
this.headerTitle = headerTitle;
this.cellRenderer = cellRenderer;
this.width = width;
}
/**
* @return The title for the column.
*/
public String getHeaderTitle() {
return headerTitle;
}
/**
* @return The method that generates a CellModel for the column based on the
* data.
*/
public Function<T, ? extends C> getCellRenderer() {
return cellRenderer;
}
/**
* @return The preferred width of the column (can be null).
*/
public Integer getWidth() {
return width;
}
}

View File

@ -0,0 +1,190 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2021 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.datasourcesummary.uiutils;
import java.awt.Insets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelTableExport.ExcelCellModel;
/**
* The default cell model.
*/
public class DefaultCellModel<T> implements GuiCellModel, ExcelCellModel {
private final T data;
private final Function<T, String> stringConverter;
String tooltip;
CellModel.HorizontalAlign horizontalAlignment;
Insets insets;
List<MenuItem> popupMenu;
Supplier<List<MenuItem>> menuItemSupplier;
private final String excelFormatString;
/**
* Main constructor.
*
* @param data The data to be displayed in the cell.
*/
public DefaultCellModel(T data) {
this(data, null, null);
}
/**
* Constructor.
*
* @param data The data to be displayed in the cell.
* @param stringConverter The means of converting that data to a string or
* null to use .toString method on object.
*/
public DefaultCellModel(T data, Function<T, String> stringConverter) {
this(data, stringConverter, null);
}
/**
* Constructor.
*
* @param data The data to be displayed in the cell.
* @param stringConverter The means of converting that data to a string or
* null to use .toString method on object.
* @param excelFormatString The apache poi excel format string to use with
* the data.
*
* NOTE: Only certain data types can be exported. See
* ExcelTableExport.createCell() for types.
*/
public DefaultCellModel(T data, Function<T, String> stringConverter, String excelFormatString) {
this.data = data;
this.stringConverter = stringConverter;
this.excelFormatString = excelFormatString;
}
@Override
public T getData() {
return this.data;
}
@Override
public String getExcelFormatString() {
return this.excelFormatString;
}
@Override
public String getText() {
if (this.stringConverter == null) {
return this.data == null ? "" : this.data.toString();
} else {
return this.stringConverter.apply(this.data);
}
}
@Override
public String getTooltip() {
return tooltip;
}
/**
* Sets the tooltip for this cell model.
*
* @param tooltip The tooltip for the cell model.
*
* @return As a utility, returns this.
*/
public DefaultCellModel<T> setTooltip(String tooltip) {
this.tooltip = tooltip;
return this;
}
@Override
public HorizontalAlign getHorizontalAlignment() {
return horizontalAlignment;
}
/**
* Sets the horizontal alignment for this cell model.
*
* @param alignment The horizontal alignment for the cell model.
*
* @return As a utility, returns this.
*/
public DefaultCellModel<T> setHorizontalAlignment(CellModel.HorizontalAlign alignment) {
this.horizontalAlignment = alignment;
return this;
}
@Override
public Insets getInsets() {
return insets;
}
/**
* Sets the insets for the text within the cell
*
* @param insets The insets.
*
* @return As a utility, returns this.
*/
public DefaultCellModel<T> setInsets(Insets insets) {
this.insets = insets;
return this;
}
@Override
public List<MenuItem> getPopupMenu() {
if (popupMenu != null) {
return Collections.unmodifiableList(popupMenu);
}
if (menuItemSupplier != null) {
return this.menuItemSupplier.get();
}
return null;
}
/**
* Sets a function to lazy load the popup menu items.
*
* @param menuItemSupplier The lazy load function for popup items.
* @return
*/
public DefaultCellModel<T> setPopupMenuRetriever(Supplier<List<MenuItem>> menuItemSupplier) {
this.menuItemSupplier = menuItemSupplier;
return this;
}
/**
* Sets the list of items for a popup menu
*
* @param popupMenu
* @return As a utility, returns this.
*/
public DefaultCellModel<T> setPopupMenu(List<MenuItem> popupMenu) {
this.popupMenu = popupMenu == null ? null : new ArrayList<>(popupMenu);
return this;
}
@Override
public String toString() {
return getText();
}
}

View File

@ -0,0 +1,193 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2021 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.datasourcesummary.uiutils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.openide.util.NbBundle.Messages;
/**
* Class for handling Excel exporting.
*/
public class ExcelExport {
/**
* Exception thrown in the event of an excel export issue.
*/
public static class ExcelExportException extends Exception {
/**
* Constructor.
*
* @param string The message.
*/
public ExcelExportException(String string) {
super(string);
}
/**
* Constructor.
*
* @param string The message.
* @param thrwbl The inner exception.
*/
public ExcelExportException(String string, Throwable thrwbl) {
super(string, thrwbl);
}
}
/**
* Class detailing aspects of the worksheet.
*/
public static class WorksheetEnv {
private final CellStyle headerStyle;
private final Workbook parentWorkbook;
/**
* Main constructor.
*
* @param headerStyle The cell style to use for headers.
* @param parentWorkbook The parent workbook.
*/
WorksheetEnv(CellStyle headerStyle, Workbook parentWorkbook) {
this.headerStyle = headerStyle;
this.parentWorkbook = parentWorkbook;
}
/**
* Returns the cell style to use for headers.
*
* @return The cell style to use for headers.
*/
public CellStyle getHeaderStyle() {
return headerStyle;
}
/**
* Returns the parent workbook.
*
* @return The parent workbook.
*/
public Workbook getParentWorkbook() {
return parentWorkbook;
}
}
/**
* An item to be exported as a sheet during export.
*/
public static interface ExcelSheetExport {
/**
* Returns the name of the sheet to use with this item.
*
* NOTE: there can be no duplicates in a workbook.
*
* @return The name of the sheet to use with this item.
*/
String getSheetName();
/**
* Renders this item to an excel worksheet.
*
* @param sheet The worksheet.
* @param env The environment and preferences to use while exporting.
* @throws ExcelExportException
*/
void renderSheet(Sheet sheet, WorksheetEnv env) throws ExcelExportException;
}
private static ExcelExport instance = null;
/**
* Retrieves a singleton instance of this class.
* @return The instance.
*/
public static ExcelExport getInstance() {
if (instance == null) {
instance = new ExcelExport();
}
return instance;
}
private ExcelExport() {
}
/**
* Writes the exports to a workbook.
* @param exports The sheets to export.
* @param path The path to the output file.
* @throws IOException
* @throws ExcelExportException
*/
@Messages({
"# {0} - sheetNumber",
"ExcelExport_writeExcel_noSheetName=Sheet {0}"
})
public void writeExcel(List<ExcelSheetExport> exports, File path) throws IOException, ExcelExportException {
// Create a Workbook
Workbook workbook = new XSSFWorkbook(); // new HSSFWorkbook() for generating `.xls` file
// Create a Font for styling header cells
Font headerFont = workbook.createFont();
headerFont.setBold(true);
//headerFont.setFontHeightInPoints((short) 14);
// Create a CellStyle with the font
CellStyle headerCellStyle = workbook.createCellStyle();
headerCellStyle.setFont(headerFont);
WorksheetEnv env = new WorksheetEnv(headerCellStyle, workbook);
if (exports != null) {
for (int i = 0; i < exports.size(); i++) {
ExcelSheetExport export = exports.get(i);
if (export == null) {
continue;
}
String sheetName = export.getSheetName();
if (sheetName == null) {
sheetName = Bundle.ExcelExport_writeExcel_noSheetName(i + 1);
}
Sheet sheet = workbook.createSheet(sheetName);
export.renderSheet(sheet, env);
}
}
// Write the output to a file
FileOutputStream fileOut = new FileOutputStream(path);
workbook.write(fileOut);
fileOut.close();
// Closing the workbook
workbook.close();
}
}

View File

@ -0,0 +1,173 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2021 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.datasourcesummary.uiutils;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelTableExport.ExcelCellModel;
/**
* An excel sheet export of table data.
*/
public class ExcelTableExport<T, C extends ExcelCellModel> implements ExcelExport.ExcelSheetExport {
/**
* Basic interface for a cell model.
*/
public interface ExcelCellModel extends CellModel {
/**
* @return The format string to be used with Apache POI during excel
* export or null if none necessary.
*/
String getExcelFormatString();
}
private final String sheetName;
private final List<ColumnModel<T, C>> columns;
private final List<T> data;
/**
* Main constructor.
*
* @param sheetName The name of the sheet. NOTE: There can be no duplicates
* in a workbook.
* @param columns The columns of the table.
* @param data The data to export.
*/
public ExcelTableExport(String sheetName, List<ColumnModel<T, C>> columns, List<T> data) {
this.sheetName = sheetName;
this.columns = columns;
this.data = data;
}
@Override
public String getSheetName() {
return sheetName;
}
@Override
public void renderSheet(Sheet sheet, ExcelExport.WorksheetEnv style) throws ExcelExport.ExcelExportException {
renderSheet(sheet, style, columns, data);
// Resize all columns to fit the content size
for (int i = 0; i < columns.size(); i++) {
sheet.autoSizeColumn(i);
}
}
/**
* Renders the data into the excel sheet.
*
* @param sheet The sheet.
* @param worksheetEnv The worksheet environment and preferences.
* @param columns The columns.
* @param data The data.
* @throws ExcelExportException
*/
private static <T, C extends ExcelCellModel> void renderSheet(
Sheet sheet, ExcelExport.WorksheetEnv worksheetEnv, List<ColumnModel<T, C>> columns, List<T> data)
throws ExcelExport.ExcelExportException {
List<T> safeData = data == null ? Collections.emptyList() : data;
// Create a header row
Row headerRow = sheet.createRow(0);
// Create header cells
for (int i = 0; i < columns.size(); i++) {
Cell cell = headerRow.createCell(i);
cell.setCellValue(columns.get(i).getHeaderTitle());
cell.setCellStyle(worksheetEnv.getHeaderStyle());
}
// freeze header row
sheet.createFreezePane(0, 1);
// Create Cell Style for each column (if one is needed)
Map<String, CellStyle> cellStyles = new HashMap<>();
for (int rowNum = 0; rowNum < safeData.size(); rowNum++) {
T rowData = safeData.get(rowNum);
Row row = sheet.createRow(rowNum + 1);
for (int colNum = 0; colNum < columns.size(); colNum++) {
ColumnModel<T, ? extends ExcelCellModel> colModel = columns.get(colNum);
ExcelCellModel cellModel = colModel.getCellRenderer().apply(rowData);
String formatString = cellModel.getExcelFormatString();
Optional<CellStyle> cellStyle = (formatString == null)
? Optional.empty()
: Optional.of(cellStyles.computeIfAbsent(formatString, k -> createCellStyle(worksheetEnv.getParentWorkbook(), formatString)));
createCell(row, colNum, cellModel, cellStyle);
}
}
}
/**
* Create a cell style in the workbook with the given format string.
*
* @param workbook The workbook.
* @param formatString The format string.
* @return The cell style.
*/
private static <T> CellStyle createCellStyle(Workbook workbook, String formatString) {
CellStyle cellStyle = workbook.createCellStyle();
cellStyle.setDataFormat(workbook.getCreationHelper().createDataFormat().getFormat(formatString));
return cellStyle;
}
/**
* Creates an excel cell given the model.
*
* @param row The row in the excel document.
* @param colNum The column number (not zero-indexed).
* @param cellModel The model for the cell.
* @param cellStyle The style to use.
* @return The created cell.
*/
private static Cell createCell(Row row, int colNum, ExcelCellModel cellModel, Optional<CellStyle> cellStyle) {
Object cellData = cellModel.getData();
Cell cell = row.createCell(colNum);
if (cellData instanceof Calendar) {
cell.setCellValue((Calendar) cellData);
} else if (cellData instanceof Date) {
cell.setCellValue((Date) cellData);
} else if (cellData instanceof Double) {
cell.setCellValue((Double) cellData);
} else if (cellData instanceof String) {
cell.setCellValue((String) cellData);
} else if (cellData instanceof Short) {
cell.setCellValue((Short) cellData);
} else if (cellData instanceof Integer) {
cell.setCellValue((Integer) cellData);
} else if (cellData instanceof Long) {
cell.setCellValue((Long) cellData);
} else if (cellData instanceof Float) {
cell.setCellValue((Float) cellData);
} else {
cell.setCellValue(cellModel.getText());
}
cellStyle.ifPresent(cs -> cell.setCellStyle(cs));
return cell;
}
}

View File

@ -0,0 +1,86 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2021 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.datasourcesummary.uiutils;
import java.awt.Insets;
import java.util.List;
/**
* Basic interface for a cell model.
*/
public interface GuiCellModel extends CellModel {
/**
* A menu item to be used within a popup menu.
*/
public interface MenuItem {
/**
* @return The title for that popup menu item.
*/
String getTitle();
/**
* @return The action if that popup menu item is clicked.
*/
Runnable getAction();
}
/**
* Default implementation of a menu item.
*/
public static class DefaultMenuItem implements MenuItem {
private final String title;
private final Runnable action;
/**
* Main constructor.
*
* @param title The title for the menu item.
* @param action The action should the menu item be clicked.
*/
public DefaultMenuItem(String title, Runnable action) {
this.title = title;
this.action = action;
}
@Override
public String getTitle() {
return title;
}
@Override
public Runnable getAction() {
return action;
}
}
/**
* @return The insets for the cell text.
*/
Insets getInsets();
/**
* @return The popup menu associated with this cell or null if no popup menu
* should be shown for this cell.
*/
List<MenuItem> getPopupMenu();
}

View File

@ -34,7 +34,6 @@ import javax.swing.plaf.LayerUI;
import javax.swing.table.DefaultTableColumnModel;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer.CellModel;
/**
* A table that displays a list of items and also can display messages for
@ -158,65 +157,6 @@ public class JTablePanel<T> extends AbstractLoadableComponent<List<T>> {
}
}
/**
* Describes aspects of a column which can be used with getTableModel or
* getJTablePanel. 'T' represents the object that will represent rows in the
* table.
*/
public static class ColumnModel<T> {
private final String headerTitle;
private final Function<T, CellModelTableCellRenderer.CellModel> cellRenderer;
private final Integer width;
/**
* Constructor for a DataResultColumnModel.
*
* @param headerTitle The title for the column.
* @param cellRenderer The method that generates a CellModel for the
* column based on the data.
*/
public ColumnModel(String headerTitle, Function<T, CellModelTableCellRenderer.CellModel> cellRenderer) {
this(headerTitle, cellRenderer, null);
}
/**
* Constructor for a DataResultColumnModel.
*
* @param headerTitle The title for the column.
* @param cellRenderer The method that generates a CellModel for the
* column based on the data.
* @param width The preferred width of the column.
*/
public ColumnModel(String headerTitle, Function<T, CellModelTableCellRenderer.CellModel> cellRenderer, Integer width) {
this.headerTitle = headerTitle;
this.cellRenderer = cellRenderer;
this.width = width;
}
/**
* @return The title for the column.
*/
public String getHeaderTitle() {
return headerTitle;
}
/**
* @return The method that generates a CellModel for the column based on
* the data.
*/
public Function<T, CellModel> getCellRenderer() {
return cellRenderer;
}
/**
* @return The preferred width of the column (can be null).
*/
public Integer getWidth() {
return width;
}
}
private static final long serialVersionUID = 1L;
private static final CellModelTableCellRenderer DEFAULT_CELL_RENDERER = new CellModelTableCellRenderer();
@ -228,12 +168,12 @@ public class JTablePanel<T> extends AbstractLoadableComponent<List<T>> {
*
* @return The corresponding TableColumnModel to be used with a JTable.
*/
public static <T> TableColumnModel getTableColumnModel(List<ColumnModel<T>> columns) {
public static <T, C extends GuiCellModel> TableColumnModel getTableColumnModel(List<ColumnModel<T, C>> columns) {
TableColumnModel tableModel = new DefaultTableColumnModel();
for (int i = 0; i < columns.size(); i++) {
TableColumn col = new TableColumn(i);
ColumnModel<T> model = columns.get(i);
ColumnModel<T, C> model = columns.get(i);
// if a preferred width is specified in the column definition,
// set the underlying TableColumn preferred width.
if (model.getWidth() != null && model.getWidth() >= 0) {
@ -260,7 +200,7 @@ public class JTablePanel<T> extends AbstractLoadableComponent<List<T>> {
*
* @return The corresponding ListTableModel.
*/
public static <T> ListTableModel<T> getTableModel(List<ColumnModel<T>> columns) {
public static <T, C extends GuiCellModel> ListTableModel<T> getTableModel(List<ColumnModel<T, C>> columns) {
List<Function<T, ? extends Object>> columnRenderers = columns.stream()
.map((colModel) -> colModel.getCellRenderer())
.collect(Collectors.toList());
@ -276,7 +216,7 @@ public class JTablePanel<T> extends AbstractLoadableComponent<List<T>> {
*
* @return The corresponding JTablePanel.
*/
public static <T> JTablePanel<T> getJTablePanel(List<ColumnModel<T>> columns) {
public static <T, C extends GuiCellModel> JTablePanel<T> getJTablePanel(List<ColumnModel<T, C>> columns) {
ListTableModel<T> tableModel = getTableModel(columns);
JTablePanel<T> resultTable = new JTablePanel<>(tableModel)
.setColumnModel(getTableColumnModel(columns))
@ -285,6 +225,7 @@ public class JTablePanel<T> extends AbstractLoadableComponent<List<T>> {
return resultTable;
}
private JScrollPane tableScrollPane;
private Overlay overlayLayer;
private ListTableModel<T> tableModel;

View File

@ -81,6 +81,7 @@ import org.sleuthkit.autopsy.datamodel.InterestingHits;
import org.sleuthkit.autopsy.datamodel.KeywordHits;
import org.sleuthkit.autopsy.datamodel.ResultsNode;
import org.sleuthkit.autopsy.datamodel.AutopsyTreeChildFactory;
import org.sleuthkit.autopsy.datamodel.PersonGroupingNode;
import org.sleuthkit.autopsy.datamodel.Tags;
import org.sleuthkit.autopsy.datamodel.ViewsNode;
import org.sleuthkit.autopsy.datamodel.accounts.Accounts;
@ -89,6 +90,9 @@ import org.sleuthkit.datamodel.Account;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.Host;
import org.sleuthkit.datamodel.Person;
import org.sleuthkit.datamodel.TskCoreException;
/**
@ -193,7 +197,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
private void preExpandNodes(Children rootChildren) {
BeanTreeView tree = getTree();
Node results = rootChildren.findChild(ResultsNode.NAME);
Node results = rootChildren.findChild(ResultsNode.getNameIdentifier());
if (!Objects.isNull(results)) {
tree.expandNode(results);
Children resultsChildren = results.getChildren();
@ -265,7 +269,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
* Setter to determine if rejected results should be shown or not.
*
* @param showRejectedResults True if showing rejected results; otherwise
* false.
* false.
*/
public void setShowRejectedResults(boolean showRejectedResults) {
this.showRejectedResults = showRejectedResults;
@ -797,7 +801,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
} // change in node selection
else if (changed.equals(ExplorerManager.PROP_SELECTED_NODES)) {
respondSelection((Node[]) event.getOldValue(), (Node[]) event.getNewValue());
}
}
}
}
@ -1012,8 +1016,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
* Set the selected node using a path to a previously selected node.
*
* @param previouslySelectedNodePath Path to a previously selected node.
* @param rootNodeName Name of the root node to match, may be
* null.
* @param rootNodeName Name of the root node to match, may be null.
*/
private void setSelectedNode(final String[] previouslySelectedNodePath, final String rootNodeName) {
if (previouslySelectedNodePath == null) {
@ -1070,12 +1073,97 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
return false;
}
/**
* Does dfs search of node while nodes are Host, Person, or
* DataSourcesByType looking for the Results Node.
*
* @param node The node.
* @return The child nodes that are at the data source level.
*/
private Node getResultsNodeSearch(Node node, long dataSourceId) {
if (node == null) {
return null;
} else if (node.getLookup().lookup(Host.class) != null
|| node.getLookup().lookup(Person.class) != null
|| PersonGroupingNode.getUnknownPersonId().equals(node.getLookup().lookup(String.class))) {
Children children = node.getChildren();
Node[] childNodes = children == null ? null : children.getNodes();
if (childNodes != null) {
for (Node child : childNodes) {
Node foundExtracted = getResultsNodeSearch(child, dataSourceId);
if (foundExtracted != null) {
return foundExtracted;
}
}
}
} else {
DataSource dataSource = node.getLookup().lookup(DataSource.class);
if (dataSource != null && dataSource.getId() == dataSourceId) {
Children dsChildren = node.getChildren();
if (dsChildren != null) {
return dsChildren.findChild(ResultsNode.getNameIdentifier());
}
}
}
return null;
}
/**
* Finds the results node for the specific artifact.
*
* @param art The artifact to find the relevant Results Node.
* @return THe Results Node or null.
*/
private Node getResultsNode(final BlackboardArtifact art) {
Children rootChilds = em.getRootContext().getChildren();
Node resultsNode = rootChilds.findChild(ResultsNode.getNameIdentifier());
if (resultsNode != null) {
return resultsNode;
}
long dataSourceId;
try {
dataSourceId = art.getDataSource().getId();
} catch (TskCoreException ex) {
LOGGER.log(Level.WARNING, "There was an error fetching the data source id for artifact.", ex);
return null;
}
Node[] rootNodes = rootChilds.getNodes();
if (rootNodes != null) {
for (Node rootNode : rootNodes) {
resultsNode = getResultsNodeSearch(rootNode, dataSourceId);
if (resultsNode != null) {
return resultsNode;
}
}
}
return null;
}
/**
* Navigates to artifact and shows in view.
*
* NOTE: This code will likely need updating in the event that the structure
* of the nodes is changed (i.e. adding parent levels). Places to look when
* changing node structure include:
*
* DirectoryTreeTopComponent.viewArtifact, ViewContextAction
*
* @param art The artifact.
*/
public void viewArtifact(final BlackboardArtifact art) {
int typeID = art.getArtifactTypeID();
String typeName = art.getArtifactTypeName();
Children rootChilds = em.getRootContext().getChildren();
Node treeNode = null;
Node resultsNode = rootChilds.findChild(ResultsNode.NAME);
Node resultsNode = getResultsNode(art);
if (resultsNode == null) {
return;
}
Children resultsChilds = resultsNode.getChildren();
if (typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID()) {
Node hashsetRootNode = resultsChilds.findChild(typeName);
@ -1160,11 +1248,16 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
if (setNode == null) {
return;
}
Children interestingChildren = setNode.getChildren();
if (interestingChildren == null) {
Children fileArtifactChildren = setNode.getChildren();
Node[] fileArtifactNodes = fileArtifactChildren == null ? null : fileArtifactChildren.getNodes();
if (fileArtifactNodes == null || fileArtifactNodes.length != 2) {
return;
}
treeNode = interestingChildren.findChild(art.getDisplayName());
treeNode = (typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID())
? fileArtifactNodes[0]
: fileArtifactNodes[1];
} catch (TskCoreException ex) {
LOGGER.log(Level.WARNING, "Error retrieving attributes", ex); //NON-NLS
}

View File

@ -22,10 +22,13 @@ import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.beans.PropertyVetoException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.sleuthkit.autopsy.coreutils.Logger;
import javax.swing.AbstractAction;
import org.openide.nodes.AbstractNode;
@ -43,8 +46,10 @@ import org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode;
import org.sleuthkit.autopsy.datamodel.AbstractFsContentNode;
import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode;
import org.sleuthkit.autopsy.datamodel.ContentNodeSelectionInfo;
import org.sleuthkit.autopsy.datamodel.DataSourcesByTypeNode;
import org.sleuthkit.autopsy.datamodel.DataSourcesNode;
import org.sleuthkit.autopsy.datamodel.DisplayableItemNode;
import org.sleuthkit.autopsy.datamodel.PersonGroupingNode;
import org.sleuthkit.autopsy.datamodel.RootContentChildren;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
@ -52,6 +57,8 @@ import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentVisitor;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.FileSystem;
import org.sleuthkit.datamodel.Host;
import org.sleuthkit.datamodel.Person;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
@ -141,6 +148,12 @@ public class ViewContextAction extends AbstractAction {
* branch of the tree view to the level of the parent of the content,
* selecting the parent in the tree view, then selecting the content in the
* results view.
*
* NOTE: This code will likely need updating in the event that the structure
* of the nodes is changed (i.e. adding parent levels). Places to look when
* changing node structure include:
*
* DirectoryTreeTopComponent.viewArtifact, ViewContextAction
*
* @param event The action event.
*/
@ -186,23 +199,26 @@ public class ViewContextAction extends AbstractAction {
DataSource datasource = skCase.getDataSource(contentDSObjid);
dsname = datasource.getName();
Children rootChildren = treeViewExplorerMgr.getRootContext().getChildren();
if (null != parentContent) {
// the tree view needs to be searched to find the parent treeview node.
/* NOTE: we can't do a lookup by data source name here, becase if there
are multiple data sources with the same name, then "getChildren().findChild(dsname)"
simply returns the first one that it finds. Instead we have to loop over all
data sources with that name, and make sure we find the correct one.
*/
for (int i = 0; i < rootChildren.getNodesCount(); i++) {
*/
List<Node> dataSourceLevelNodes = Stream.of(rootChildren.getNodes())
.flatMap(rootNode -> getDataSourceLevelNodes(rootNode).stream())
.collect(Collectors.toList());
for (Node treeNode : dataSourceLevelNodes) {
// in the root, look for a data source node with the name of interest
Node treeNode = rootChildren.getNodeAt(i);
if (!(treeNode.getName().equals(dsname))) {
continue;
}
// for this data source, get the "Data Sources" child node
Node datasourceGroupingNode = treeNode.getChildren().findChild(DataSourcesNode.NAME);
Node datasourceGroupingNode = treeNode.getChildren().findChild(DataSourcesNode.getNameIdentifier());
// check whether this is the data source we are looking for
parentTreeViewNode = findParentNodeInTree(parentContent, datasourceGroupingNode);
@ -218,7 +234,7 @@ public class ViewContextAction extends AbstractAction {
Node datasourceGroupingNode = rootChildren.findChild(dsname);
if (!Objects.isNull(datasourceGroupingNode)) {
Children dsChildren = datasourceGroupingNode.getChildren();
parentTreeViewNode = dsChildren.findChild(DataSourcesNode.NAME);
parentTreeViewNode = dsChildren.findChild(DataSourcesNode.getNameIdentifier());
}
}
@ -234,13 +250,19 @@ public class ViewContextAction extends AbstractAction {
}
} else { // Classic view
// Start the search at the DataSourcesNode
parentTreeViewNode = treeViewExplorerMgr.getRootContext().getChildren().findChild(DataSourcesNode.NAME);
if (null != parentContent) {
// the tree view needs to be searched to find the parent treeview node.
Node potentialParentTreeViewNode = findParentNodeInTree(parentContent, parentTreeViewNode);
if (potentialParentTreeViewNode != null) {
parentTreeViewNode = potentialParentTreeViewNode;
Children rootChildren = treeViewExplorerMgr.getRootContext().getChildren();
Node rootDsNode = rootChildren == null ? null : rootChildren.findChild(DataSourcesByTypeNode.getNameIdentifier());
if (rootDsNode != null) {
for (Node dataSourceLevelNode : getDataSourceLevelNodes(rootDsNode)) {
DataSource dataSource = dataSourceLevelNode.getLookup().lookup(DataSource.class);
if (dataSource != null) {
// the tree view needs to be searched to find the parent treeview node.
Node potentialParentTreeViewNode = findParentNodeInTree(parentContent, dataSourceLevelNode);
if (potentialParentTreeViewNode != null) {
parentTreeViewNode = potentialParentTreeViewNode;
break;
}
}
}
}
}
@ -286,6 +308,34 @@ public class ViewContextAction extends AbstractAction {
});
}
/**
* If the node has lookup of host or person, returns children. If not, just
* returns itself.
*
* @param node The node.
* @return The child nodes that are at the data source level.
*/
private List<Node> getDataSourceLevelNodes(Node node) {
if (node == null) {
return Collections.emptyList();
} else if (node.getLookup().lookup(Host.class) != null ||
node.getLookup().lookup(Person.class) != null ||
DataSourcesByTypeNode.getNameIdentifier().equals(node.getLookup().lookup(String.class)) ||
PersonGroupingNode.getUnknownPersonId().equals(node.getLookup().lookup(String.class))) {
Children children = node.getChildren();
Node[] childNodes = children == null ? null : children.getNodes();
if (childNodes == null) {
return Collections.emptyList();
}
return Stream.of(node.getChildren().getNodes())
.flatMap(parent -> getDataSourceLevelNodes(parent).stream())
.collect(Collectors.toList());
} else {
return Arrays.asList(node);
}
}
/**
* Searches tree for parent node by getting an ordered list of the ancestors
* of the specified content.

View File

@ -20,12 +20,19 @@ package org.sleuthkit.autopsy.discovery.ui;
import com.google.common.eventbus.Subscribe;
import java.awt.Component;
import java.util.logging.Level;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.apache.commons.lang.StringUtils;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.centralrepository.contentviewer.OtherOccurrencesPanel;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNormalizationException;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.discovery.search.DiscoveryEventUtils;
import org.sleuthkit.datamodel.BlackboardArtifact;
@ -38,6 +45,7 @@ import org.sleuthkit.autopsy.discovery.search.SearchData;
final class DomainDetailsPanel extends JPanel {
private static final long serialVersionUID = 1L;
private static final Logger logger = Logger.getLogger(DomainDetailsPanel.class.getName());
private ArtifactsWorker singleArtifactDomainWorker;
private String domain;
private String selectedTabName = null;
@ -47,6 +55,7 @@ final class DomainDetailsPanel extends JPanel {
*
* @param selectedTabName The name of the tab to select initially.
*/
@NbBundle.Messages({"DomainDetailsPanel.otherOccurrencesTab.title=Other Occurrences"})
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
DomainDetailsPanel() {
initComponents();
@ -56,6 +65,9 @@ final class DomainDetailsPanel extends JPanel {
for (BlackboardArtifact.ARTIFACT_TYPE type : SearchData.Type.DOMAIN.getArtifactTypes()) {
jTabbedPane1.add(type.getDisplayName(), new DomainArtifactsTabPanel(type));
}
if (CentralRepository.isEnabled()) {
jTabbedPane1.add(Bundle.DomainDetailsPanel_otherOccurrencesTab_title(), new OtherOccurrencesPanel());
}
}
/**
@ -182,6 +194,17 @@ final class DomainDetailsPanel extends JPanel {
runDomainWorker((DomainArtifactsTabPanel) selectedComponent, false);
} else if (selectedComponent instanceof MiniTimelinePanel) {
runMiniTimelineWorker((MiniTimelinePanel) selectedComponent, false);
} else if (selectedComponent instanceof OtherOccurrencesPanel) {
if (CentralRepository.isEnabled()) {
try {
((OtherOccurrencesPanel) selectedComponent).populateTableForOneType(CentralRepository.getInstance().getCorrelationTypeById(CorrelationAttributeInstance.DOMAIN_TYPE_ID), domain);
} catch (CentralRepoException ex) {
logger.log(Level.INFO, "Central repository exception while trying to get instances by type and value for domain: " + domain, ex);
((OtherOccurrencesPanel) selectedComponent).reset();
}
} else {
((OtherOccurrencesPanel) selectedComponent).reset();
}
}
//send fade in event
DiscoveryEventUtils.getDiscoveryEventBus().post(new DiscoveryEventUtils.DetailsVisibleEvent(true));
@ -200,6 +223,8 @@ final class DomainDetailsPanel extends JPanel {
((DomainArtifactsTabPanel) comp).setStatus(DomainArtifactsTabPanel.ArtifactRetrievalStatus.UNPOPULATED);
} else if (comp instanceof MiniTimelinePanel) {
((MiniTimelinePanel) comp).setStatus(DomainArtifactsTabPanel.ArtifactRetrievalStatus.UNPOPULATED, domain);
} else if (comp instanceof OtherOccurrencesPanel) {
((OtherOccurrencesPanel) comp).reset();
}
}
}

View File

@ -93,16 +93,12 @@ public class DomainSummaryViewer extends javax.swing.JPanel {
* reflect the currently selected domain. Will populate the list with
* nothing when a domain is not used.
*
* @param useDomain If the currently selected domain should be used to
* retrieve a list.
*/
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
void sendPopulateEvent(boolean useDomain) {
void sendPopulateEvent() {
String domain = "";
if (useDomain) {
if (domainList.getSelectedIndex() != -1) {
domain = domainListModel.getElementAt(domainList.getSelectedIndex()).getResultDomain().getDomain();
}
if (domainList.getSelectedIndex() != -1) {
domain = domainListModel.getElementAt(domainList.getSelectedIndex()).getResultDomain().getDomain();
}
//send populateMesage
DiscoveryEventUtils.getDiscoveryEventBus().post(new DiscoveryEventUtils.PopulateDomainTabsEvent(domain));

View File

@ -128,8 +128,8 @@ final class ResultsPanel extends javax.swing.JPanel {
}
});
domainSummaryViewer.addListSelectionListener((e) -> {
if (resultType == SearchData.Type.DOMAIN) {
domainSummaryViewer.sendPopulateEvent(!e.getValueIsAdjusting());
if (resultType == SearchData.Type.DOMAIN && !e.getValueIsAdjusting()) {
domainSummaryViewer.sendPopulateEvent();
}
});
}

View File

@ -323,6 +323,11 @@ abstract class IngestTaskPipeline<T extends IngestTask> {
* performing the task.
*/
abstract void performTask(IngestJobPipeline ingestJobPipeline, T task) throws IngestModuleException;
@Override
public void shutDown() {
module.shutDown();
}
}

View File

@ -22,19 +22,20 @@ ILeappAnalyzerIngestModule.init.exception.msg=Unable to find {0}.
ILeappAnalyzerIngestModule.processing.file=Processing file {0}
ILeappAnalyzerIngestModule.parsing.file=Parsing file {0}
ILeappAnalyzerIngestModule.processing.filesystem=Processing filesystem
IleappAnalyzerIngestModule.not.64.bit.os=iLeapp will not run on 32bit operating system
IleappAnalyzerIngestModule.not.64.bit.os=iLeapp will not run on a 32bit operating system
ALeappAnalyzerIngestModule.init.exception.msg=Unable to find {0}.
ALeappAnalyzerIngestModule.processing.file=Processing file {0}
ALeappAnalyzerIngestModule.parsing.file=Parsing file {0}
ALeappAnalyzerIngestModule.processing.filesystem=Processing filesystem
AleappAnalyzerIngestModule.not.64.bit.os=aLeapp will not run on 32bit operating system
AleappAnalyzerIngestModule.not.64.bit.os=aLeapp will not run on a 32bit operating system
ILeappAnalyzerIngestModule.report.name=iLeapp Html Report
ILeappAnalyzerIngestModule.requires.windows=iLeapp module requires windows.
ILeappAnalyzerIngestModule.running.iLeapp=Running iLeapp
ILeappAnalyzerIngestModule.starting.iLeapp=Starting iLeapp
ILeappAnalyzerModuleFactory_moduleDesc=Uses iLEAPP to analyze logical acquisitions of iOS devices.
ILeappAnalyzerModuleFactory_moduleName=iOS Analyzer (iLEAPP)
LeappFileProcessor.cannot.load.artifact.xml=Cannor load xml artifact file.
LeappFileProcessor.cannot.create.message.relationship=Cannot create TSK_MESSAGE Relationship.
LeappFileProcessor.cannot.load.artifact.xml=Cannot load xml artifact file.
LeappFileProcessor.cannotBuildXmlParser=Cannot buld an XML parser.
LeappFileProcessor.completed=Leapp Processing Completed
LeappFileProcessor.error.creating.new.artifacts=Error creating new artifacts.

View File

@ -23,7 +23,6 @@ import com.fasterxml.jackson.dataformat.csv.CsvMapper;
import com.fasterxml.jackson.dataformat.csv.CsvParser;
import com.fasterxml.jackson.dataformat.csv.CsvSchema;
import com.google.common.collect.ImmutableMap;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
@ -65,13 +64,21 @@ import org.sleuthkit.autopsy.coreutils.PlatformUtil;
import org.sleuthkit.autopsy.ingest.IngestModule.IngestModuleException;
import org.sleuthkit.autopsy.ingest.IngestModule.ProcessResult;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.Account;
import org.sleuthkit.datamodel.Blackboard;
import org.sleuthkit.datamodel.Blackboard.BlackboardException;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskException;
import org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper;
import org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper.CallMediaType;
import org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper.CommunicationDirection;
import org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper.MessageReadStatus;
import org.sleuthkit.datamodel.blackboardutils.attributes.MessageAttachments;
import org.sleuthkit.datamodel.blackboardutils.attributes.MessageAttachments.FileAttachment;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.NodeList;
@ -141,6 +148,26 @@ public final class LeappFileProcessor {
.put("TSK_IP_DHCP", "DHCP Information")
.build();
private static final Map<String, String> ACCOUNT_RELATIONSHIPS = ImmutableMap.<String, String>builder()
.put("Zapya.tsv", "message")
.put("sms messages.tsv", "message")
.put("mms messages.tsv", "message")
.put("Viber - Messages.tsv", "message")
.put("Viber - Contacts.tsv", "contact")
.put("Viber - Call Logs.tsv", "calllog")
.put("Xender file transfer - Messages.tsv", "message")
.put("Whatsapp - Contacts.tsv", "contact")
.put("Whatsapp - Group Call Logs.tsv", "calllog")
.put("Whatsapp - Single Call Logs.tsv", "calllog")
.put("Whatsapp - Messages Logs.tsv", "message")
.put("Shareit file transfer.tsv", "message")
.put("tangomessages messages.tsv", "message")
.put("Contacts.tsv", "contact")
.put("IMO - AccountId.tsv", "contact")
.put("IMO - messages.tsv", "message")
.build();
Blackboard blkBoard;
public LeappFileProcessor(String xmlFile, String moduleName) throws IOException, IngestModuleException, NoCurrentCaseException {
@ -267,7 +294,7 @@ public final class LeappFileProcessor {
for (String LeappFileName : LeappFilesToProcess) {
String fileName = FilenameUtils.getName(LeappFileName);
File LeappFile = new File(LeappFileName);
File LeappFile = new File(LeappFileName);
if (tsvFileAttributes.containsKey(fileName)) {
List<TsvColumn> attrList = tsvFileAttributes.get(fileName);
BlackboardArtifact.Type artifactType = tsvFileArtifacts.get(fileName);
@ -321,9 +348,22 @@ public final class LeappFileProcessor {
Collection<BlackboardAttribute> bbattributes = processReadLine(columnItems, columnIndexes, attrList, fileName, lineNum);
if (!bbattributes.isEmpty()) {
BlackboardArtifact bbartifact = createArtifactWithAttributes(artifactType.getTypeID(), dataSource, bbattributes);
if (bbartifact != null) {
bbartifacts.add(bbartifact);
switch (ACCOUNT_RELATIONSHIPS.getOrDefault(fileName, "norelationship").toLowerCase()) {
case "message":
createMessageRelationship(bbattributes, dataSource, fileName);
break;
case "contact":
createContactRelationship(bbattributes, dataSource, fileName);
break;
case "calllog":
createCalllogRelationship(bbattributes, dataSource, fileName);
break;
default: // There is no relationship defined so just process the artifact normally
BlackboardArtifact bbartifact = createArtifactWithAttributes(artifactType.getTypeID(), dataSource, bbattributes);
if (bbartifact != null) {
bbartifacts.add(bbartifact);
}
break;
}
}
@ -333,6 +373,266 @@ public final class LeappFileProcessor {
}
}
@NbBundle.Messages({
"LeappFileProcessor.cannot.create.message.relationship=Cannot create TSK_MESSAGE Relationship.",
})
private void createMessageRelationship(Collection<BlackboardAttribute> bbattributes, Content dataSource, String fileName) throws IngestModuleException {
String messageType = null;
CommunicationDirection communicationDirection = CommunicationDirection.UNKNOWN;
String senderId = null;
String receipentId = null;
String[] receipentIdList = null;
Long dateTime = Long.valueOf(0);
MessageReadStatus messageStatus = MessageReadStatus.UNKNOWN;
String subject = null;
String messageText = null;
String threadId = null;
List<BlackboardAttribute> otherAttributes = new ArrayList<>();
List<FileAttachment> fileAttachments = new ArrayList<>();
String sourceFile = null;
MessageAttachments messageAttachments = null;
try {
for (BlackboardAttribute bba : bbattributes) {
switch (bba.getAttributeType().getTypeName()) {
case "TSK_DIRECTION":
if (bba.getValueString().toLowerCase().equals("outgoing")) {
communicationDirection = CommunicationDirection.OUTGOING;
} else if (bba.getValueString().toLowerCase().equals("incoming")) {
communicationDirection = CommunicationDirection.INCOMING;
}
break;
case "TSK_PHONE_NUMBER_FROM":
if (!bba.getValueString().isEmpty()) {
senderId = bba.getValueString();
}
break;
case "TSK_PHONE_NUMBER_TO":
if (!bba.getValueString().isEmpty()) {
receipentIdList = bba.getValueString().split(",", 0);
}
break;
case "TSK_DATETIME":
dateTime = bba.getValueLong();
break;
case "TSK_COMMENT":
messageType = bba.getValueString();
break;
case "TSK_ATTACHMENTS":
if (!bba.getValueString().isEmpty()) {
fileAttachments.add(new FileAttachment(Case.getCurrentCaseThrows().getSleuthkitCase(), dataSource, bba.getValueString()));
}
break;
case "TSK_TEXT_FILE":
sourceFile = bba.getValueString();
break;
case "TSK_READ_STATUS":
if (bba.getValueInt() == 1 ) {
messageStatus = MessageReadStatus.READ;
} else {
messageStatus = MessageReadStatus.UNREAD;
}
break;
case "TSK_TEXT":
messageText = bba.getValueString();
break;
case "TSK_SUBJECT":
subject = bba.getValueString();
break;
default:
otherAttributes.add(bba);
break;
}
}
AbstractFile absFile = findAbstractFile(dataSource, sourceFile);
Account.Type accountType = getAccountType(fileName);
if ((absFile != null) || (accountType != null)) {
CommunicationArtifactsHelper accountArtifact = new CommunicationArtifactsHelper(Case.getCurrentCaseThrows().getSleuthkitCase(),
moduleName, absFile, accountType);
BlackboardArtifact messageArtifact = accountArtifact.addMessage(messageType, communicationDirection, senderId,
receipentId, dateTime, messageStatus, subject,
messageText, threadId, otherAttributes);
if (!fileAttachments.isEmpty()) {
messageAttachments = new MessageAttachments(fileAttachments, new ArrayList<>());
accountArtifact.addAttachments(messageArtifact, messageAttachments);
}
}
} catch (NoCurrentCaseException | TskCoreException | BlackboardException ex) {
throw new IngestModuleException(Bundle.LeappFileProcessor_cannot_create_message_relationship() + ex.getLocalizedMessage(), ex); //NON-NLS
}
}
private void createContactRelationship(Collection<BlackboardAttribute> bbattributes, Content dataSource, String fileName) throws IngestModuleException {
String alternateId = null;
String contactName = null;
String phoneNumber = null;
String homePhoneNumber = null;
String mobilePhoneNumber = null;
String emailAddr = null;
List<BlackboardAttribute> otherAttributes = new ArrayList<>();
String sourceFile = null;
try {
for (BlackboardAttribute bba : bbattributes) {
switch (bba.getAttributeType().getTypeName()) {
case "TSK_PHONE_NUMBER":
if (!bba.getValueString().isEmpty()) {
phoneNumber = bba.getValueString();
}
break;
case "TSK_NAME":
if (!bba.getValueString().isEmpty()) {
contactName = bba.getValueString();
}
break;
case "TSK_TEXT_FILE":
sourceFile = bba.getValueString();
break;
case "TSK_PHONE_NUMBER_HOME":
homePhoneNumber = bba.getValueString();
break;
case "TSK_PHONE_NUMBER_MOBILE":
mobilePhoneNumber = bba.getValueString();
break;
case "TSK_EMAIL":
emailAddr = bba.getValueString();
break;
case "TSK_ID":
alternateId = bba.getValueString();
break;
default:
otherAttributes.add(bba);
break;
}
}
AbstractFile absFile = findAbstractFile(dataSource, sourceFile);
Account.Type accountType = getAccountType(fileName);
if ((absFile != null) || (accountType != null)) {
CommunicationArtifactsHelper accountArtifact;
if (alternateId == null) {
accountArtifact = new CommunicationArtifactsHelper(Case.getCurrentCaseThrows().getSleuthkitCase(),
moduleName, absFile, accountType);
} else {
accountArtifact = new CommunicationArtifactsHelper(Case.getCurrentCaseThrows().getSleuthkitCase(),
moduleName, absFile, accountType, accountType, alternateId);
}
BlackboardArtifact messageArtifact = accountArtifact.addContact(contactName, phoneNumber, homePhoneNumber, mobilePhoneNumber, emailAddr, otherAttributes);
}
} catch (NoCurrentCaseException | TskCoreException | BlackboardException ex) {
throw new IngestModuleException(Bundle.LeappFileProcessor_cannot_create_message_relationship() + ex.getLocalizedMessage(), ex); //NON-NLS
}
}
private void createCalllogRelationship(Collection<BlackboardAttribute> bbattributes, Content dataSource, String fileName) throws IngestModuleException {
String callerId = null;
List<String> calleeId = Arrays.asList();
CommunicationDirection communicationDirection = CommunicationDirection.UNKNOWN;
Long startDateTime = Long.valueOf(0);
Long endDateTime = Long.valueOf(0);
CallMediaType mediaType = CallMediaType.UNKNOWN;
List<BlackboardAttribute> otherAttributes = new ArrayList<>();
String sourceFile = null;
try {
for (BlackboardAttribute bba : bbattributes) {
switch (bba.getAttributeType().getTypeName()) {
case "TSK_TEXT_FILE":
sourceFile = bba.getValueString();
break;
case "TSK_DATETIME_START":
startDateTime = bba.getValueLong();
break;
case "TSK_DATETIME_END":
startDateTime = bba.getValueLong();
break;
case "TSK_DIRECTION":
if (bba.getValueString().toLowerCase().equals("outgoing")) {
communicationDirection = CommunicationDirection.OUTGOING;
} else if (bba.getValueString().toLowerCase().equals("incoming")) {
communicationDirection = CommunicationDirection.INCOMING;
}
break;
case "TSK_PHONE_NUMBER_FROM":
if (!bba.getValueString().isEmpty()) {
callerId = bba.getValueString();
}
break;
case "TSK_PHONE_NUMBER_TO":
if (!bba.getValueString().isEmpty()) {
String [] calleeTempList = bba.getValueString().split(",", 0);
calleeId = Arrays.asList(calleeTempList);
}
break;
default:
otherAttributes.add(bba);
break;
}
}
if (calleeId.isEmpty() && communicationDirection == CommunicationDirection.OUTGOING) {
String [] calleeTempList = callerId.split(",", 0);
calleeId = Arrays.asList(calleeTempList);
callerId = null;
}
AbstractFile absFile = findAbstractFile(dataSource, sourceFile);
Account.Type accountType = getAccountType(fileName);
if ((absFile != null) || (accountType != null)) {
CommunicationArtifactsHelper accountArtifact = new CommunicationArtifactsHelper(Case.getCurrentCaseThrows().getSleuthkitCase(),
moduleName, absFile, accountType);
BlackboardArtifact callLogArtifact = accountArtifact.addCalllog(communicationDirection, callerId, calleeId, startDateTime, endDateTime, mediaType, otherAttributes);
}
} catch (NoCurrentCaseException | TskCoreException | BlackboardException ex) {
throw new IngestModuleException(Bundle.LeappFileProcessor_cannot_create_message_relationship() + ex.getLocalizedMessage(), ex); //NON-NLS
}
}
private Account.Type getAccountType(String AccountTypeName) {
switch (AccountTypeName.toLowerCase()) {
case "zapya.tsv":
return Account.Type.ZAPYA;
case "sms messages.tsv":
return Account.Type.PHONE;
case "contacts.tsv":
return Account.Type.PHONE;
case "imo - accountid.tsv":
return Account.Type.IMO;
case "imo - messages.tsv":
return Account.Type.IMO;
case "mms messages.tsv":
return Account.Type.PHONE;
case "viber - call logs.tsv":
return Account.Type.VIBER;
case "viber - contacts.tsv":
return Account.Type.VIBER;
case "viber - messages.tsv":
return Account.Type.VIBER;
case "xender file transfer - messages.tsv":
return Account.Type.XENDER;
case "whatsapp - single call logs.tsv":
return Account.Type.WHATSAPP;
case "whatsapp - messages logs.tsv":
return Account.Type.WHATSAPP;
case "whatsapp - group call logs.tsv":
return Account.Type.WHATSAPP;
case "whatsapp - contacts.tsv":
return Account.Type.WHATSAPP;
case "tangomessages messages.tsv":
return Account.Type.TANGO;
case "shareit file transfer.tsv":
return Account.Type.SHAREIT;
default:
return null;
}
}
/**
* Process the line read and create the necessary attributes for it.
*
@ -399,16 +699,17 @@ public final class LeappFileProcessor {
/**
* Check type of attribute and possibly format string based on it.
*
*
* @param colAttr Column Attribute information
* @param value string to be formatted
* @return formatted string based on attribute type if no attribute type found then return original string
* @return formatted string based on attribute type if no attribute type
* found then return original string
*/
private String formatValueBasedOnAttrType(TsvColumn colAttr, String value) {
if (colAttr.getAttributeType().getTypeName().equals("TSK_DOMAIN")) {
return NetworkUtils.extractDomain(value);
}
return value;
}
@ -460,6 +761,7 @@ public final class LeappFileProcessor {
// Log this and continue on with processing
logger.log(Level.WARNING, String.format("Attribute Type %s for file %s not defined.", attrType, fileName)); //NON-NLS
return null;
}
}
@ -514,7 +816,7 @@ public final class LeappFileProcessor {
}
@NbBundle.Messages({
"LeappFileProcessor.cannot.load.artifact.xml=Cannor load xml artifact file.",
"LeappFileProcessor.cannot.load.artifact.xml=Cannot load xml artifact file.",
"LeappFileProcessor.cannotBuildXmlParser=Cannot buld an XML parser.",
"LeappFileProcessor_cannotParseXml=Cannot Parse XML file.",
"LeappFileProcessor.postartifacts_error=Error posting Blackboard Artifact",
@ -607,7 +909,7 @@ public final class LeappFileProcessor {
for (int k = 0; k < attributeNlist.getLength(); k++) {
NamedNodeMap nnm = attributeNlist.item(k).getAttributes();
String attributeName = nnm.getNamedItem("attributename").getNodeValue();
if (!attributeName.toLowerCase().matches("null")) {
String columnName = nnm.getNamedItem("columnName").getNodeValue();
String required = nnm.getNamedItem("required").getNodeValue();
@ -704,7 +1006,7 @@ public final class LeappFileProcessor {
*/
private void configExtractor() throws IOException {
PlatformUtil.extractResourceToUserConfigDir(LeappFileProcessor.class,
xmlFile, true);
xmlFile, true);
}
private static final Set<String> ALLOWED_EXTENSIONS = new HashSet<>(Arrays.asList("zip", "tar", "tgz"));
@ -761,4 +1063,35 @@ public final class LeappFileProcessor {
}
}
}
private AbstractFile findAbstractFile(Content dataSource, String fileNamePath) {
if (fileNamePath == null) {
return null;
}
List<AbstractFile> files;
String fileName = FilenameUtils.getName(fileNamePath);
String filePath = FilenameUtils.normalize(FilenameUtils.getPath(fileNamePath), true);
FileManager fileManager = Case.getCurrentCase().getServices().getFileManager();
try {
files = fileManager.findFiles(dataSource, fileName); //NON-NLS
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "Unable to find prefetch files.", ex); //NON-NLS
return null; // No need to continue
}
for (AbstractFile pFile : files) {
if (pFile.getParentPath().toLowerCase().endsWith(filePath.toLowerCase())) {
return pFile;
}
}
return null;
}
}

View File

@ -344,17 +344,18 @@
<AttributeName attributename="TSK_DATETIME" columnName="Date" required="yes"/>
<AttributeName attributename="null" columnName="MSG ID" required="no"/>
<AttributeName attributename="TSK_THREAD_ID" columnName="Thread ID" required="yes"/>
<AttributeName attributename="TSK_DATETIME_SENT" columnName="Date sent" required="yes"/>
<AttributeName attributename="null" columnName="Date sent" required="no"/>
<AttributeName attributename="TSK_READ_STATUS" columnName="Read" required="yes"/>
<AttributeName attributename="TSK_PHONE_NUMBER_FROM" columnName="From" required="yes"/>
<AttributeName attributename="TSK_PHONE_NUMBER_TO" columnName="To" required="yes"/>
<AttributeName attributename="null" columnName="Cc" required="no"/>
<AttributeName attributename="null" columnName="Bcc" required="no"/>
<AttributeName attributename="TSK_TEXT" columnName="Body" required="yes"/>
<AttributeName attributename="TSK_TEXT_FILE" columnName="source file" required="yes"/>
</ArtifactName>
</FileName>
<!-- <FileName filename="partner settings.tsv" description="Partner Settings">
<!-- <FileName filename="partner settings.tsv" description="Partner Settings">
<ArtifactName artifactname="TSK_" comment="null">
<AttributeName attributename="null" columnName="Name" required="no" />
<AttributeName attributename="null" columnName="Value ) # Dont remove the comma" required="no" />
@ -365,17 +366,139 @@
<FileName filename="sms messages.tsv" description="SMS messages">
<ArtifactName artifactname="TSK_MESSAGE" comment="SMS messages">
<AttributeName attributename="TSK_DATETIME" columnName="Date" required="yes"/>
<AttributeName attributename="null" columnName="Date" required="no"/>
<AttributeName attributename="null" columnName="MSG ID" required="no"/>
<AttributeName attributename="TSK_THREAD_ID" columnName="Thread ID" required="yes"/>
<AttributeName attributename="TSK_PHONE_NUMBER_FROM" columnName="Address" required="yes" />
<AttributeName attributename="null" columnName="Contact ID" required="yes"/>
<AttributeName attributename="TSK_DATETIME_SENT" columnName="Date sent" required="yes"/>
<AttributeName attributename="TSK_PHONE_NUMBER_TO" columnName="Contact ID" required="yes"/>
<AttributeName attributename="TSK_DATETIME" columnName="Date sent" required="yes"/>
<AttributeName attributename="TSK_READ_STATUS" columnName="Read" required="yes"/>
<AttributeName attributename="TSK_TEXT" columnName="Body" required="yes"/>
<AttributeName attributename="null" columnName="Service Center" required="yes"/>
<AttributeName attributename="null" columnName="Service Center" required="no"/>
<AttributeName attributename="null" columnName="Error Code" required="no"/>
<AttributeName attributename="TSK_TEXT_FILE" columnName="source file" required="yes"/>
</ArtifactName>
</FileName>
<FileName filename="Viber - Messages.tsv" description="Viber">
<ArtifactName artifactname="TSK_MESSAGE" comment="Viber Message">
<AttributeName attributename="TSK_DATETIME" columnName="Message Date" required="yes" />
<AttributeName attributename="TSK_PHONE_NUMBER_FROM" columnName="From Phone Number" required="yes"/>
<AttributeName attributename="TSK_PHONE_NUMBER_TO" columnName="Recipients" required="yes"/>
<AttributeName attributename="TSK_THREAD_ID" columnName="Thread ID" required="yes" />
<AttributeName attributename="TSK_TEXT" columnName="Message" required="yes" />
<AttributeName attributename="TSK_DIRECTION" columnName="direction" required="yes"/>
<AttributeName attributename="TSK_READ_STATUS" columnName="Read Status" required="yes"/>
<AttributeName attributename="TSK_ATTACHMENTS" columnName="File Attachment" required="yes"/>
<AttributeName attributename="TSK_TEXT_FILE" columnName="source file" required="yes"/>
</ArtifactName>
</FileName>
</aLeap_Files_To_Process>
<FileName filename="Viber - Contacts.tsv" description="Viber">
<ArtifactName artifactname="TSK_CONTACT" comment="Viber Contacts">
<AttributeName attributename="TSK_NAME" columnName="display name" required="yes" />
<AttributeName attributename="TSK_PHONE_NUMBER" columnName="phone number" required="yes"/>
<AttributeName attributename="TSK_TEXT_FILE" columnName="source file" required="yes"/>
</ArtifactName>
</FileName>
<FileName filename="Viber - Call Logs.tsv" description="Viber">
<ArtifactName artifactname="TSK_CALLLOG" comment="Viber Contacts">
<AttributeName attributename="TSK_DATETIME_START" columnName="Call Start Time" required="yes" />
<AttributeName attributename="TSK_PHONE_NUMBER_FROM" columnName="phone number" required="yes"/>
<AttributeName attributename="TSK_DIRECTION" columnName="Call Direction" required="yes"/>
<AttributeName attributename="TSK_DATETIME_END" columnName="Call End Time" required="yes"/>
<AttributeName attributename="null" columnName="Call Type" required="no"/>
<AttributeName attributename="TSK_TEXT_FILE" columnName="source file" required="yes"/>
</ArtifactName>
</FileName>
<FileName filename="Zapya.tsv" description="Zapya">
<ArtifactName artifactname="TSK_MESSAGE" comment="Zapya Message">
<AttributeName attributename="null" columnName="Device" required="no"/>
<AttributeName attributename="null" columnName="Name" required="no"/>
<AttributeName attributename="TSK_DIRECTION" columnName="direction" required="yes"/>
<AttributeName attributename="TSK_PHONE_NUMBER_FROM" columnName="fromid" required="yes"/>
<AttributeName attributename="TSK_PHONE_NUMBER_TO" columnName="toid" required="yes"/>
<AttributeName attributename="TSK_DATETIME" columnName="createtime" required="yes" />
<AttributeName attributename="TSK_ATTACHMENTS" columnName="path" required="yes"/>
<AttributeName attributename="null" columnName="title" required="no"/>
<AttributeName attributename="TSK_TEXT_FILE" columnName="source file" required="yes"/>
</ArtifactName>
</FileName>
<FileName filename="Xender file transfer - Messages.tsv" description="Xender">
<ArtifactName artifactname="TSK_MESSAGE" comment="Xender Message">
<AttributeName attributename="TSK_ATTACHMENTS" columnName="file_path" required="yes"/>
<AttributeName attributename="null" columnName="file_display_name" required="no"/>
<AttributeName attributename="null" columnName="file_size" required="no"/>
<AttributeName attributename="TSK_DATETIME" columnName="timestamp" required="yes" />
<AttributeName attributename="TSK_DIRECTION" columnName="direction" required="yes"/>
<AttributeName attributename="TSK_PHONE_NUMBER_TO" columnName="to_id" required="yes"/>
<AttributeName attributename="TSK_PHONE_NUMBER_FROM" columnName="from_id" required="yes"/>
<AttributeName attributename="TSK_THREAD_ID" columnName="session_id" required="yes" />
<AttributeName attributename="null" columnName="sender_name" required="no"/>
<AttributeName attributename="null" columnName="sender_device_id" required="no"/>
<AttributeName attributename="null" columnName="recipient_name" required="no"/>
<AttributeName attributename="null" columnName="recipient_device_id" required="no"/>
<AttributeName attributename="TSK_TEXT_FILE" columnName="source file" required="yes"/>
</ArtifactName>
</FileName>
<FileName filename="Whatsapp - Single Call Logs.tsv" description="Whatsapp">
<ArtifactName artifactname="TSK_CALLLOG" comment="Whatsapp Single Call Log">
<AttributeName attributename="TSK_DATETIME_START" columnName="start_time" required="yes" />
<AttributeName attributename="null" columnName="call_type" required="no"/>
<AttributeName attributename="TSK_DATETIME_END" columnName="end_time" required="yes"/>
<AttributeName attributename="TSK_PHONE_NUMBER_FROM" columnName="num" required="yes"/>
<AttributeName attributename="TSK_DIRECTION" columnName="call_direction" required="yes"/>
<AttributeName attributename="TSK_TEXT_FILE" columnName="source file" required="yes"/>
</ArtifactName>
</FileName>
<FileName filename="Whatsapp - Group Call Logs.tsv" description="Whatsapp">
<ArtifactName artifactname="TSK_CALLLOG" comment="Whatsapp Group Call Log">
<AttributeName attributename="null" columnName="call_type" required="no"/>
<AttributeName attributename="TSK_DATETIME_START" columnName="start_time" required="yes" />
<AttributeName attributename="TSK_DATETIME_END" columnName="end_time" required="yes"/>
<AttributeName attributename="TSK_DIRECTION" columnName="call_direction" required="yes"/>
<AttributeName attributename="TSK_PHONE_NUMBER_FROM" columnName="from_id" required="yes"/>
<AttributeName attributename="TSK_PHONE_NUMBER_TO" columnName="group_members" required="yes"/>
<AttributeName attributename="TSK_TEXT_FILE" columnName="source file" required="yes"/>
</ArtifactName>
</FileName>
<FileName filename="Whatsapp - Contacts.tsv" description="Whatsapp">
<ArtifactName artifactname="TSK_CONTACT" comment="Whatsapp Contacts">
<AttributeName attributename="TSK_EMAIL" columnName="number" required="yes"/>
<AttributeName attributename="TSK_NAME" columnName="name" required="yes" />
<AttributeName attributename="TSK_TEXT_FILE" columnName="source file" required="yes"/>
</ArtifactName>
</FileName>
<FileName filename="Whatsapp - Messages.tsv" description="Whatsapp">
<ArtifactName artifactname="TSK_MESSAGE" comment="Whatsapp Messages">
<AttributeName attributename="TSK_THREAD_ID" columnName="messages_id" required="yes"/>
<AttributeName attributename="TSK_PHONE_NUMBER_TO" columnName="recipients" required="yes"/>
<AttributeName attributename="TSK_DIRECTION" columnName="direction" required="yes"/>
<AttributeName attributename="TSK_TEXT" columnName="content" required="yes"/>
<AttributeName attributename="TSK_DATETIME_START" columnName="send_timestamp" required="yes" />
<AttributeName attributename="TSK_DATETIME_END" columnName="received_timestamp" required="yes"/>
<AttributeName attributename="TSK_PHONE_NUMBER_FROM" columnName="number" required="yes"/>
<AttributeName attributename="TSK_ATTACHMENTS" columnName="name" required="yes" />
<AttributeName attributename="TSK_TEXT_FILE" columnName="source file" required="yes"/>
</ArtifactName>
</FileName>
<FileName filename="Contacts.tsv" description="Contacts">
<ArtifactName artifactname="TSK_CONTACT" comment="Contacts">
<AttributeName attributename="null" columnName="mimetype" required="no" />
<AttributeName attributename="null" columnName="data1" required="no" />
<AttributeName attributename="TSK_NAME" columnName="display_name" required="yes" />
<AttributeName attributename="TSK_PHONE_NUMBER" columnName="phone_number" required="yes"/>
<AttributeName attributename="TSK_EMAIL" columnName="email address" required="yes"/>
<AttributeName attributename="TSK_TEXT_FILE" columnName="source file" required="yes"/>
</ArtifactName>
</FileName>
</aLeap_Files_To_Process>

View File

@ -1085,7 +1085,7 @@ public class PortableCaseReportModule implements ReportModule {
Host newHost = null;
if (content instanceof DataSource) {
Host oldHost = ((DataSource)content).getHost();
newHost = portableSkCase.getHostManager().getOrCreateHost(oldHost.getName());
newHost = portableSkCase.getHostManager().createHost(oldHost.getName());
}
CaseDbTransaction trans = portableSkCase.beginTransaction();

View File

@ -34,7 +34,7 @@ public class DomainSearchTestUtils {
long totalVisits, long visits, long filesDownloaded, long dataSourceId) {
Content dataSource = TskMockUtils.getDataSource(dataSourceId);
return new ResultDomain(domain, start, end, totalVisits,
visits, filesDownloaded, 0L, dataSource);
visits, filesDownloaded, 0L, "", dataSource);
}
public static ResultDomain mockDomainResult(String domain) {

View File

@ -662,7 +662,7 @@ public final class ImageGalleryController {
private static ListeningExecutorService getNewDBExecutor() {
return MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor(
new ThreadFactoryBuilder().setNameFormat("DB-Worker-Thread-%d").build()));
new ThreadFactoryBuilder().setNameFormat("ImageGallery-DB-Worker-Thread-%d").build()));
}
/**

View File

@ -148,7 +148,7 @@ abstract class AdHocSearchPanel extends javax.swing.JPanel {
}
/**
* Get a list of data source display name.
* Get a list of data source display names.
*
* @return The list of data source name
*/

View File

@ -9,8 +9,20 @@ DropdownListSearchPanel.selected=Ad Hoc Search data source filter is selected
DropdownSingleTermSearchPanel.selected=Ad Hoc Search data source filter is selected
DropdownSingleTermSearchPanel.warning.text=Boundary characters ^ and $ do not match word boundaries. Consider\nreplacing with an explicit list of boundary characters, such as [ \\.,]
DropdownSingleTermSearchPanel.warning.title=Warning
ExtractAllTermsReport.description.text=Extracts all unique words out of the current case. NOTE: The extracted words are lower-cased.
ExtractAllTermsReport.error.noOpenCase=No currently open case.
ExtractAllTermsReport.export.error=Error During Unique Word Extraction
ExtractAllTermsReport.exportComplete=Unique Word Extraction Complete
ExtractAllTermsReport.getName.text=Extract Unique Words
# {0} - Number of extracted terms
ExtractAllTermsReport.numberExtractedTerms=Extracted {0} terms...
ExtractAllTermsReport.search.ingestInProgressBody=<html>Keyword Search Ingest is currently running.<br />Not all files have been indexed and unique word extraction might yield incomplete results.<br />Do you want to proceed with unique word extraction anyway?</html>
# {0} - Keyword search commit frequency
ExtractAllTermsReport.search.noFilesInIdxMsg=No files are in index yet. Try again later. Index is updated every {0} minutes.
ExtractAllTermsReport.search.noFilesInIdxMsg2=No files are in index yet. Try again later
ExtractAllTermsReport.search.searchIngestInProgressTitle=Keyword Search Ingest in Progress
ExtractAllTermsReport.startExport=Starting Unique Word Extraction
ExtractedContentPanel.setMarkup.panelTxt=<span style='font-style:italic'>Loading text... Please wait</span>
# {0} - Content name
ExtractedContentPanel.SetMarkup.progress.loading=Loading text for {0}
GlobalEditListPanel.editKeyword.title=Edit Keyword
GlobalEditListPanel.warning.text=Boundary characters ^ and $ do not match word boundaries. Consider\nreplacing with an explicit list of boundary characters, such as [ \\.,]
@ -218,6 +230,7 @@ Server.deleteCore.exception.msg=Failed to delete Solr colelction {0}
Server.exceptionMessage.unableToBackupCollection=Unable to backup Solr collection
Server.exceptionMessage.unableToCreateCollection=Unable to create Solr collection
Server.exceptionMessage.unableToRestoreCollection=Unable to restore Solr collection
Server.getAllTerms.error=Extraction of all unique Solr terms failed:
Server.start.exception.cantStartSolr.msg=Could not start Solr server process
Server.start.exception.cantStartSolr.msg2=Could not start Solr server process
Server.isRunning.exception.errCheckSolrRunning.msg=Error checking if Solr server is running

View File

@ -0,0 +1,134 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2021 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.keywordsearch;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.logging.Level;
import org.openide.util.NbBundle;
import org.openide.util.lookup.ServiceProvider;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.autopsy.report.GeneralReportModule;
import org.sleuthkit.autopsy.report.GeneralReportSettings;
import org.sleuthkit.autopsy.report.ReportProgressPanel;
/**
* Instances of this class plug in to the reporting infrastructure to provide a
* convenient way to extract all unique terms from Solr index.
*/
@ServiceProvider(service = GeneralReportModule.class)
public class ExtractAllTermsReport implements GeneralReportModule {
private static final Logger logger = Logger.getLogger(ExtractAllTermsReport.class.getName());
private static final String OUTPUT_FILE_NAME = "Unique Words.txt";
@NbBundle.Messages({
"ExtractAllTermsReport.getName.text=Extract Unique Words"})
@Override
public String getName() {
return Bundle.ExtractAllTermsReport_getName_text();
}
@NbBundle.Messages({
"ExtractAllTermsReport.error.noOpenCase=No currently open case.",
"# {0} - Keyword search commit frequency",
"ExtractAllTermsReport.search.noFilesInIdxMsg=No files are in index yet. Try again later. Index is updated every {0} minutes.",
"ExtractAllTermsReport.search.noFilesInIdxMsg2=No files are in index yet. Try again later",
"ExtractAllTermsReport.search.searchIngestInProgressTitle=Keyword Search Ingest in Progress",
"ExtractAllTermsReport.search.ingestInProgressBody=<html>Keyword Search Ingest is currently running.<br />Not all files have been indexed and unique word extraction might yield incomplete results.<br />Do you want to proceed with unique word extraction anyway?</html>",
"ExtractAllTermsReport.startExport=Starting Unique Word Extraction",
"ExtractAllTermsReport.export.error=Error During Unique Word Extraction",
"ExtractAllTermsReport.exportComplete=Unique Word Extraction Complete"
})
@Override
public void generateReport(GeneralReportSettings settings, ReportProgressPanel progressPanel) {
if (!Case.isCaseOpen()) {
logger.log(Level.SEVERE, "No open case when attempting to run {0} report", Bundle.ExtractAllTermsReport_getName_text()); //NON-NLS
progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR, Bundle.ExtractAllTermsReport_error_noOpenCase());
return;
}
progressPanel.setIndeterminate(true);
progressPanel.start();
progressPanel.updateStatusLabel("Extracting unique words...");
boolean isIngestRunning = IngestManager.getInstance().isIngestRunning();
int filesIndexed = 0;
try { // see if there are any indexed files
filesIndexed = KeywordSearch.getServer().queryNumIndexedFiles();
} catch (KeywordSearchModuleException | NoOpenCoreException ignored) {
}
if (filesIndexed == 0) {
if (isIngestRunning) {
progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR, Bundle.ExtractAllTermsReport_search_noFilesInIdxMsg(KeywordSearchSettings.getUpdateFrequency().getTime()));
} else {
progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR, Bundle.ExtractAllTermsReport_search_noFilesInIdxMsg2());
}
progressPanel.setIndeterminate(false);
return;
}
// check if keyword search module ingest is running (indexing, etc)
if (isIngestRunning) {
if (KeywordSearchUtil.displayConfirmDialog(Bundle.ExtractAllTermsReport_search_searchIngestInProgressTitle(),
Bundle.ExtractAllTermsReport_search_ingestInProgressBody(), KeywordSearchUtil.DIALOG_MESSAGE_TYPE.WARN) == false) {
progressPanel.cancel();
return;
}
}
final Server server = KeywordSearch.getServer();
try {
progressPanel.updateStatusLabel(Bundle.ExtractAllTermsReport_startExport());
Path outputFile = Paths.get(settings.getReportDirectoryPath(), getRelativeFilePath());
server.extractAllTermsForDataSource(outputFile, progressPanel);
} catch (KeywordSearchModuleException | NoOpenCoreException ex) {
logger.log(Level.SEVERE, "Exception while extracting unique terms", ex); //NON-NLS
progressPanel.setIndeterminate(false);
progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR, Bundle.ExtractAllTermsReport_export_error());
return;
}
progressPanel.setIndeterminate(false);
progressPanel.complete(ReportProgressPanel.ReportStatus.COMPLETE, Bundle.ExtractAllTermsReport_exportComplete());
}
@Override
public boolean supportsDataSourceSelection() {
return false;
}
@NbBundle.Messages({
"ExtractAllTermsReport.description.text=Extracts all unique words out of the current case. NOTE: The extracted words are lower-cased."})
@Override
public String getDescription() {
return Bundle.ExtractAllTermsReport_description_text();
}
@Override
public String getRelativeFilePath() {
return OUTPUT_FILE_NAME;
}
}

View File

@ -21,10 +21,8 @@ package org.sleuthkit.autopsy.keywordsearch;
import java.io.File;
import java.nio.file.Paths;
import java.util.List;
import java.util.logging.Level;
import org.apache.commons.lang.math.NumberUtils;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.appservices.AutopsyService;
/**
@ -32,7 +30,6 @@ import org.sleuthkit.autopsy.appservices.AutopsyService;
*/
class IndexFinder {
private static final Logger logger = Logger.getLogger(IndexFinder.class.getName());
private static final String KWS_OUTPUT_FOLDER_NAME = "keywordsearch";
private static final String KWS_DATA_FOLDER_NAME = "data";
private static final String INDEX_FOLDER_NAME = "index";
@ -48,7 +45,7 @@ class IndexFinder {
return CURRENT_SOLR_SCHEMA_VERSION;
}
static Index findLatestVersionIndexDir(List<Index> allIndexes) {
static Index findLatestVersionIndex(List<Index> allIndexes) {
for (Index index : allIndexes) {
if (index.getSolrVersion().equals(CURRENT_SOLR_VERSION) && index.getSchemaVersion().equals(CURRENT_SOLR_SCHEMA_VERSION)) {
return index;
@ -57,7 +54,7 @@ class IndexFinder {
return null;
}
static Index createLatestVersionIndexDir(Case theCase) throws AutopsyService.AutopsyServiceException {
static Index createLatestVersionIndex(Case theCase) throws AutopsyService.AutopsyServiceException {
String indexFolderName = "solr" + CURRENT_SOLR_VERSION + "_schema" + CURRENT_SOLR_SCHEMA_VERSION;
// new index should be stored in "\ModuleOutput\keywordsearch\data\solrX_schemaY\index"
File targetDirPath = Paths.get(theCase.getModuleDirectory(), KWS_OUTPUT_FOLDER_NAME, KWS_DATA_FOLDER_NAME, indexFolderName, INDEX_FOLDER_NAME).toFile(); //NON-NLS

View File

@ -36,6 +36,7 @@ import java.net.ServerSocket;
import java.net.SocketException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.OpenOption;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
import java.nio.file.Path;
import java.nio.file.Paths;
@ -52,6 +53,7 @@ import java.util.logging.Level;
import javax.swing.AbstractAction;
import org.apache.commons.io.FileUtils;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.toList;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrRequest;
@ -66,8 +68,10 @@ import org.apache.solr.client.solrj.response.CollectionAdminResponse;
import org.apache.solr.client.solrj.request.CoreAdminRequest;
import org.apache.solr.client.solrj.response.CoreAdminResponse;
import org.apache.solr.client.solrj.impl.BaseHttpSolrClient.RemoteSolrException;
import org.apache.solr.client.solrj.request.QueryRequest;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.client.solrj.response.TermsResponse;
import org.apache.solr.client.solrj.response.TermsResponse.Term;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.SolrException;
@ -80,6 +84,7 @@ import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.Case.CaseType;
import org.sleuthkit.autopsy.casemodule.CaseMetadata;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.coreutils.FileUtil;
import org.sleuthkit.autopsy.coreutils.Logger;
@ -90,6 +95,8 @@ import org.sleuthkit.autopsy.coreutils.ThreadUtils;
import org.sleuthkit.autopsy.healthmonitor.HealthMonitor;
import org.sleuthkit.autopsy.healthmonitor.TimingMetric;
import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchServiceException;
import org.sleuthkit.autopsy.report.GeneralReportSettings;
import org.sleuthkit.autopsy.report.ReportProgressPanel;
import org.sleuthkit.datamodel.Content;
/**
@ -1785,6 +1792,34 @@ public class Server {
currentCoreLock.writeLock().unlock();
}
}
/**
* Extract all unique terms/words from current index.
*
* @param outputFile Absolute path to the output file
* @param progressPanel ReportProgressPanel to update
*
* @throws NoOpenCoreException
*/
@NbBundle.Messages({
"Server.getAllTerms.error=Extraction of all unique Solr terms failed:"})
void extractAllTermsForDataSource(Path outputFile, ReportProgressPanel progressPanel) throws KeywordSearchModuleException, NoOpenCoreException {
try {
currentCoreLock.writeLock().lock();
if (null == currentCollection) {
throw new NoOpenCoreException();
}
try {
currentCollection.extractAllTermsForDataSource(outputFile, progressPanel);
} catch (Exception ex) {
// intentional "catch all" as Solr is known to throw all kinds of Runtime exceptions
logger.log(Level.SEVERE, "Extraction of all unique Solr terms failed: ", ex); //NON-NLS
throw new KeywordSearchModuleException(Bundle.Server_getAllTerms_error(), ex);
}
} finally {
currentCoreLock.writeLock().unlock();
}
}
/**
* Get the text contents of the given file as stored in SOLR.
@ -2132,6 +2167,71 @@ public class Server {
queryClient.deleteByQuery(deleteQuery);
}
/**
* Extract all unique terms/words from current index. Gets 1,000 terms at a time and
* writes them to output file. Updates ReportProgressPanel status.
*
* @param outputFile Absolute path to the output file
* @param progressPanel ReportProgressPanel to update
* @throws IOException
* @throws SolrServerException
* @throws NoCurrentCaseException
* @throws KeywordSearchModuleException
*/
@NbBundle.Messages({
"# {0} - Number of extracted terms",
"ExtractAllTermsReport.numberExtractedTerms=Extracted {0} terms..."
})
private void extractAllTermsForDataSource(Path outputFile, ReportProgressPanel progressPanel) throws IOException, SolrServerException, NoCurrentCaseException, KeywordSearchModuleException {
Files.deleteIfExists(outputFile);
OpenOption[] options = new OpenOption[] { java.nio.file.StandardOpenOption.CREATE, java.nio.file.StandardOpenOption.APPEND };
// step through the terms
int termStep = 1000;
long numExtractedTerms = 0;
String firstTerm = "";
while (true) {
SolrQuery query = new SolrQuery();
query.setRequestHandler("/terms");
query.setTerms(true);
query.setTermsLimit(termStep);
query.setTermsLower(firstTerm);
query.setTermsLowerInclusive(false);
// Returned terms sorted by "index" order, which is the fastest way. Per Solr documentation:
// "Retrieving terms in index order is very fast since the implementation directly uses Lucenes TermEnum to iterate over the term dictionary."
// All other sort criteria return very inconsistent and overlapping resuts.
query.setTermsSortString("index");
// "text" field is the schema field that we populate with (lowercased) terms
query.addTermsField(Server.Schema.TEXT.toString());
query.setTermsMinCount(0);
// Unfortunatelly Solr "terms queries" do not support any filtering so we can't filter by data source this way.
// query.addFilterQuery(Server.Schema.IMAGE_ID.toString() + ":" + dataSourceId);
QueryRequest request = new QueryRequest(query);
TermsResponse response = request.process(queryClient).getTermsResponse();
List<Term> terms = response.getTerms(Server.Schema.TEXT.toString());
if (terms == null || terms.isEmpty()) {
numExtractedTerms += terms.size();
progressPanel.updateStatusLabel(Bundle.ExtractAllTermsReport_numberExtractedTerms(numExtractedTerms));
break;
}
// set the first term for the next query
firstTerm = terms.get(terms.size()-1).getTerm();
List<String> listTerms = terms.stream().map(Term::getTerm).collect(Collectors.toList());
Files.write(outputFile, listTerms, options);
numExtractedTerms += termStep;
progressPanel.updateStatusLabel(Bundle.ExtractAllTermsReport_numberExtractedTerms(numExtractedTerms));
}
}
/**
* Add a Solr document for indexing. Documents get batched instead of
@ -2168,7 +2268,6 @@ public class Server {
*
* @throws KeywordSearchModuleException
*/
// ELTODO DECIDE ON SYNCHRONIZATION
private void sendBufferedDocs(List<SolrInputDocument> docBuffer) throws KeywordSearchModuleException {
if (docBuffer.isEmpty()) {

View File

@ -309,14 +309,14 @@ public class SolrSearchService implements KeywordSearchService, AutopsyService {
// new case that doesn't have an existing index. create new index folder
progressUnitsCompleted++;
progress.progress(Bundle.SolrSearch_creatingNewIndex_msg(), progressUnitsCompleted);
currentVersionIndex = IndexFinder.createLatestVersionIndexDir(theCase);
currentVersionIndex = IndexFinder.createLatestVersionIndex(theCase);
// add current index to the list of indexes that exist for this case
indexes.add(currentVersionIndex);
} else {
// check if one of the existing indexes is for latest Solr version and schema
progressUnitsCompleted++;
progress.progress(Bundle.SolrSearch_checkingForLatestIndex_msg(), progressUnitsCompleted);
currentVersionIndex = IndexFinder.findLatestVersionIndexDir(indexes);
currentVersionIndex = IndexFinder.findLatestVersionIndex(indexes);
if (currentVersionIndex == null) {
// found existing index(es) but none were for latest Solr version and schema version
progressUnitsCompleted++;

View File

@ -2315,8 +2315,9 @@ class ExtractRegistry extends Extract {
// "Default Admin User", "Custom Limited Acct"
// and "Default Guest Acct"
value = userInfo.get(ACCOUNT_TYPE_KEY);
if (value != null && !value.isEmpty()) {
osAccount.setIsAdmin(value.toLowerCase().contains("Admin"));
if (value != null && !value.isEmpty() && value.toLowerCase().contains("admin")) {
attributes.add(createOsAccountAttribute(ATTRIBUTE_TYPE.TSK_IS_ADMIN,
1, osAccount, host, regFile));
}
value = userInfo.get(USER_COMMENT_KEY);

View File

@ -329,7 +329,8 @@ class TskDbDiff(object):
id_legacy_artifact_types = build_id_legacy_artifact_types_table(conn.cursor(), isMultiUser)
id_reports_table = build_id_reports_table(conn.cursor(), isMultiUser)
id_images_table = build_id_image_names_table(conn.cursor(), isMultiUser)
id_obj_path_table = build_id_obj_path_table(id_files_table, id_objects_table, id_artifact_types_table, id_reports_table, id_images_table)
id_accounts_table = build_id_accounts_table(conn.cursor(), isMultiUser)
id_obj_path_table = build_id_obj_path_table(id_files_table, id_objects_table, id_artifact_types_table, id_reports_table, id_images_table, id_accounts_table)
if isMultiUser: # Use PostgreSQL
os.environ['PGPASSWORD']=pgSettings.password
@ -352,7 +353,7 @@ class TskDbDiff(object):
if 'INSERT INTO image_gallery_groups_seen' in dump_line:
dump_line = ''
continue;
dump_line = normalize_db_entry(dump_line, id_obj_path_table, id_vs_parts_table, id_vs_info_table, id_fs_info_table, id_objects_table, id_reports_table, id_images_table, id_legacy_artifact_types)
dump_line = normalize_db_entry(dump_line, id_obj_path_table, id_vs_parts_table, id_vs_info_table, id_fs_info_table, id_objects_table, id_reports_table, id_images_table, id_legacy_artifact_types, id_accounts_table)
db_log.write('%s\n' % dump_line)
dump_line = ''
postgreSQL_db.close()
@ -366,7 +367,7 @@ class TskDbDiff(object):
for line in conn.iterdump():
if 'INSERT INTO "image_gallery_groups_seen"' in line:
continue
line = normalize_db_entry(line, id_obj_path_table, id_vs_parts_table, id_vs_info_table, id_fs_info_table, id_objects_table, id_reports_table, id_images_table, id_legacy_artifact_types)
line = normalize_db_entry(line, id_obj_path_table, id_vs_parts_table, id_vs_info_table, id_fs_info_table, id_objects_table, id_reports_table, id_images_table, id_legacy_artifact_types, id_accounts_table)
db_log.write('%s\n' % line)
# Now sort the file
srtcmdlst = ["sort", dump_file, "-o", dump_file]
@ -419,7 +420,7 @@ class PGSettings(object):
return self.password
def normalize_db_entry(line, files_table, vs_parts_table, vs_info_table, fs_info_table, objects_table, reports_table, images_table, artifact_table):
def normalize_db_entry(line, files_table, vs_parts_table, vs_info_table, fs_info_table, objects_table, reports_table, images_table, artifact_table, accounts_table):
""" Make testing more consistent and reasonable by doctoring certain db entries.
Args:
@ -442,6 +443,7 @@ def normalize_db_entry(line, files_table, vs_parts_table, vs_info_table, fs_info
ig_groups_index = line.find('INSERT INTO "image_gallery_groups"') > -1 or line.find('INSERT INTO image_gallery_groups ') > -1
ig_groups_seen_index = line.find('INSERT INTO "image_gallery_groups_seen"') > -1 or line.find('INSERT INTO image_gallery_groups_seen ') > -1
os_account_index = line.find('INSERT INTO "tsk_os_accounts"') > -1 or line.find('INSERT INTO tsk_os_accounts') > -1
os_account_attr_index = line.find('INSERT INTO "tsk_os_account_attributes"') > -1 or line.find('INSERT INTO tsk_os_account_attributes') > -1
parens = line[line.find('(') + 1 : line.rfind(')')]
no_space_parens = parens.replace(" ", "")
@ -569,6 +571,8 @@ def normalize_db_entry(line, files_table, vs_parts_table, vs_info_table, fs_info
parent_path = fs_info_table[parent_id]
elif parent_id in images_table.keys():
parent_path = images_table[parent_id]
elif parent_id in accounts_table.keys():
parent_path = accounts_table[parent_id]
elif parent_id == 'NULL':
parent_path = "NULL"
@ -636,7 +640,29 @@ def normalize_db_entry(line, files_table, vs_parts_table, vs_info_table, fs_info
newLine = ('INSERT INTO "tsk_event_descriptions" VALUES(' + ','.join(fields_list[1:]) + ');') # remove report_id
return newLine
elif os_account_index:
newLine = ('INSERT INTO "tsk_os_accounts" VALUES(' + ','.join(fields_list[1:]) + ');') # remove id
newLine = ('INSERT INTO "tsk_os_accounts" VALUES(' + ','.join(fields_list[1:]) + ');') # remove id since value that would be substituted is in diff line already
return newLine
elif os_account_attr_index:
#substitue the account object id for a non changing value
os_account_id = int(fields_list[1])
fields_list[1] = accounts_table[os_account_id]
#substitue the source object id for a non changing value
source_obj_id = int(fields_list[3])
if source_obj_id in files_table.keys():
fields_list[3] = files_table[source_obj_id]
elif source_obj_id in vs_parts_table.keys():
fields_list[3] = vs_parts_table[source_obj_id]
elif source_obj_id in vs_info_table.keys():
fields_list[3] = vs_info_table[source_obj_id]
elif source_obj_id in fs_info_table.keys():
fields_list[3] = fs_info_table[source_obj_id]
elif source_obj_id in images_table.keys():
fields_list[3] = images_table[source_obj_id]
elif source_obj_id in accounts_table.keys():
fields_list[3] = accounts_table[source_obj_id]
elif source_obj_id == 'NULL':
fields_list[3] = "NULL"
newLine = ('INSERT INTO "tsk_os_account_attributes" VALUES(' + ','.join(fields_list[1:]) + ');') # remove id
return newLine
else:
return line
@ -758,8 +784,18 @@ def build_id_reports_table(db_cursor, isPostgreSQL):
mapping = dict([(row[0], row[1]) for row in sql_select_execute(db_cursor, isPostgreSQL, "SELECT obj_id, path FROM reports")])
return mapping
def build_id_accounts_table(db_cursor, isPostgreSQL):
"""Build the map of object ids to OS account SIDs.
def build_id_obj_path_table(files_table, objects_table, artifacts_table, reports_table, images_table):
Args:
db_cursor: the database cursor
"""
# for each row in the db, take the object id and account SID then creates a tuple in the dictionary
# with the object id as the key and the OS Account's SID as the value
mapping = dict([(row[0], row[1]) for row in sql_select_execute(db_cursor, isPostgreSQL, "SELECT os_account_obj_id, unique_id FROM tsk_os_accounts")])
return mapping
def build_id_obj_path_table(files_table, objects_table, artifacts_table, reports_table, images_table, accounts_table):
"""Build the map of object ids to artifact ids.
Args:
@ -767,6 +803,8 @@ def build_id_obj_path_table(files_table, objects_table, artifacts_table, reports
objects_table: obj_id, par_obj_id, type
artifacts_table: obj_id, artifact_type_name
reports_table: obj_id, path
images_table: obj_id, name
accounts_table: obj_id, unique_id
"""
# make a copy of files_table and update it with new data from artifacts_table and reports_table
mapping = files_table.copy()
@ -786,6 +824,8 @@ def build_id_obj_path_table(files_table, objects_table, artifacts_table, reports
elif par_obj_id in images_table.keys():
path = images_table[par_obj_id]
mapping[k] = path + "/" + artifacts_table[k]
elif k in accounts_table.keys(): # For an OS Account object ID we use its unique_id field which is the account SID
mapping[k] = accounts_table[k]
elif v[0] not in mapping.keys():
if v[0] in artifacts_table.keys():
par_obj_id = objects_table[v[0]]

Binary file not shown.

View File

@ -619,8 +619,6 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule {
size, cTime, crTime, aTime, mTime, true, abstractFile, "",
EmailParserModuleFactory.getModuleName(), EmailParserModuleFactory.getModuleVersion(), "", encodingType);
associateAttachmentWithMesssge(messageArtifact, df);
files.add(df);
fileAttachments.add(new FileAttachment(df));
@ -646,19 +644,6 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule {
return files;
}
/**
* Creates a TSK_ASSOCIATED_OBJECT artifact between the attachment file and
* the message artifact.
*/
private BlackboardArtifact associateAttachmentWithMesssge(BlackboardArtifact message, AbstractFile attachedFile) throws TskCoreException {
Collection<BlackboardAttribute> attributes = new ArrayList<>();
attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT, EmailParserModuleFactory.getModuleName(), message.getArtifactID()));
BlackboardArtifact bba = attachedFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT);
bba.addAttributes(attributes); //write out to bb
return bba;
}
/**
* Finds and returns a set of unique email addresses found in the input
* string