diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/TextUtil.java b/Core/src/org/sleuthkit/autopsy/coreutils/TextUtil.java
index 053968d598..f2eed8e474 100644
--- a/Core/src/org/sleuthkit/autopsy/coreutils/TextUtil.java
+++ b/Core/src/org/sleuthkit/autopsy/coreutils/TextUtil.java
@@ -57,4 +57,26 @@ public class TextUtil {
return orientation;
}
+
+
+ /**
+ * This method determines if a passed-in Java char (16 bits) is a valid
+ * UTF-8 printable character, returning true if so, false if not.
+ *
+ * Note that this method can have ramifications for characters outside the
+ * Unicode Base Multilingual Plane (BMP), which require more than 16 bits.
+ * We are using Java characters (16 bits) to look at the data and this will
+ * not accurately identify any non-BMP character (larger than 16 bits)
+ * ending with 0xFFFF and 0xFFFE. In the interest of a fast solution, we
+ * have chosen to ignore the extended planes above Unicode BMP for the time
+ * being. The net result of this is some non-BMP characters may be
+ * interspersed with '^' characters in Autopsy.
+ *
+ * @param ch the character to test
+ *
+ * @return Returns true if the character is valid UTF-8, false if not.
+ */
+ public static boolean isValidSolrUTF8(char ch) {
+ return ((ch <= 0xFDD0 || ch >= 0xFDEF) && (ch > 0x1F || ch == 0x9 || ch == 0xA || ch == 0xD) && (ch != 0xFFFF) && (ch != 0xFFFE));
+ }
}
diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbCreateDatabaseDialog.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbCreateDatabaseDialog.java
index 96f0c1b16e..1b60250a6b 100644
--- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbCreateDatabaseDialog.java
+++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbCreateDatabaseDialog.java
@@ -22,6 +22,7 @@ import java.awt.Dimension;
import java.awt.Toolkit;
import java.io.File;
import java.io.IOException;
+import java.nio.file.Paths;
import java.util.logging.Level;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
@@ -30,6 +31,7 @@ import org.apache.commons.io.FilenameUtils;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ModuleSettings;
+import org.sleuthkit.autopsy.coreutils.PlatformUtil;
import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDb;
import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDb.KnownFilesType;
import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDbManagerException;
@@ -273,12 +275,17 @@ final class HashDbCreateDatabaseDialog extends javax.swing.JDialog {
private void saveAsButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_saveAsButtonActionPerformed
try {
- String lastBaseDirectory = "";
+ String lastBaseDirectory = Paths.get(PlatformUtil.getUserConfigDirectory(), "HashDatabases").toString();
if (ModuleSettings.settingExists(ModuleSettings.MAIN_SETTINGS, LAST_FILE_PATH_KEY)) {
lastBaseDirectory = ModuleSettings.getConfigSetting(ModuleSettings.MAIN_SETTINGS, LAST_FILE_PATH_KEY);
}
StringBuilder path = new StringBuilder();
path.append(lastBaseDirectory);
+ File hashDbFolder = new File(path.toString());
+ // create the folder if it doesn't exist
+ if (!hashDbFolder.exists()){
+ hashDbFolder.mkdir();
+ }
if (!hashSetNameTextField.getText().isEmpty()) {
path.append(File.separator).append(hashSetNameTextField.getText());
} else {
diff --git a/Core/src/org/sleuthkit/autopsy/report/Bundle.properties b/Core/src/org/sleuthkit/autopsy/report/Bundle.properties
index 0be3a1924f..ba477498c3 100644
--- a/Core/src/org/sleuthkit/autopsy/report/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/report/Bundle.properties
@@ -179,7 +179,7 @@ ReportHTML.addThumbRows.dataType.msg=Tagged Results and Contents that contain im
ReportHTML.thumbLink.tags=Tags\:
ReportHTML.getName.text=Results - HTML
ReportHTML.getDesc.text=A report about results and tagged items in HTML format.
-ReportHTML.writeIndex.title=Autopsy Report for case {0}
+ReportHTML.writeIndex.title=for case {0}
ReportHTML.writeIndex.noFrames.msg=Your browser is not compatible with our frame setup.
ReportHTML.writeIndex.noFrames.seeNav=Please see the navigation page for artifact links,
ReportHTML.writeIndex.seeSum=and the summary page for a case summary.
diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java b/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java
index eeca1efe56..5af71effae 100644
--- a/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java
+++ b/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java
@@ -838,7 +838,8 @@ class ReportHTML implements TableReportModule {
try {
indexOut = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(indexFilePath), "UTF-8")); //NON-NLS
StringBuilder index = new StringBuilder();
- index.append("
\n").append( //NON-NLS
+ final String reportTitle = reportBranding.getReportTitle();
+ index.append("\n").append(reportTitle).append(" ").append(
NbBundle.getMessage(this.getClass(), "ReportHTML.writeIndex.title", currentCase.getName())).append(
"\n"); //NON-NLS
index.append("\n"); //NON-NLS
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListTimeline.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListTimeline.java
index fc224ae60e..dc1e0e6b06 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListTimeline.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListTimeline.java
@@ -23,9 +23,10 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
+import java.util.function.Function;
import java.util.logging.Level;
import javafx.application.Platform;
-import javafx.beans.binding.Bindings;
+import javafx.beans.binding.StringBinding;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
@@ -57,7 +58,7 @@ import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
import org.sleuthkit.datamodel.TskCoreException;
/**
- * The inner component that makes up the Lsit view. Manages the table.
+ * The inner component that makes up the List view. Manages the TableView.
*/
class ListTimeline extends BorderPane {
@@ -100,6 +101,9 @@ class ListTimeline extends BorderPane {
}
@FXML
+ @NbBundle.Messages({
+ "# {0} - the number of events",
+ "ListTimeline.evetnCountLabel.text={0} events"})
void initialize() {
assert eventCountLabel != null : "fx:id=\"eventCountLabel\" was not injected: check your FXML file 'ListViewPane.fxml'.";
assert table != null : "fx:id=\"table\" was not injected: check your FXML file 'ListViewPane.fxml'.";
@@ -114,31 +118,44 @@ class ListTimeline extends BorderPane {
//override default row with one that provides context menu.S
table.setRowFactory(tableView -> new EventRow());
- //remove idColumn (can be used for debugging).
+ //remove idColumn (can be restored for debugging).
table.getColumns().remove(idColumn);
- // set up cell and cell-value factories for columns
- //
+ ///// set up cell and cell-value factories for columns
millisColumn.setCellValueFactory(CELL_VALUE_FACTORY);
- millisColumn.setCellFactory(col -> new EpochMillisCell());
+ millisColumn.setCellFactory(col -> new TextEventTableCell(singleEvent ->
+ TimeLineController.getZonedFormatter().print(singleEvent.getStartMillis())));
iconColumn.setCellValueFactory(CELL_VALUE_FACTORY);
iconColumn.setCellFactory(col -> new ImageCell());
descriptionColumn.setCellValueFactory(CELL_VALUE_FACTORY);
- descriptionColumn.setCellFactory(col -> new DescriptionCell());
+ descriptionColumn.setCellFactory(col -> new TextEventTableCell(singleEvent ->
+ singleEvent.getDescription(DescriptionLoD.FULL)));
baseTypeColumn.setCellValueFactory(CELL_VALUE_FACTORY);
- baseTypeColumn.setCellFactory(col -> new BaseTypeCell());
+ baseTypeColumn.setCellFactory(col -> new TextEventTableCell(singleEvent ->
+ singleEvent.getEventType().getBaseType().getDisplayName()));
subTypeColumn.setCellValueFactory(CELL_VALUE_FACTORY);
- subTypeColumn.setCellFactory(col -> new EventTypeCell());
+ subTypeColumn.setCellFactory(col -> new TextEventTableCell(singleEvent ->
+ singleEvent.getEventType().getDisplayName()));
knownColumn.setCellValueFactory(CELL_VALUE_FACTORY);
- knownColumn.setCellFactory(col -> new KnownCell());
+ knownColumn.setCellFactory(col -> new TextEventTableCell(singleEvent ->
+ singleEvent.getKnown().getName()));
- //bind event count lable no number of items in table
- eventCountLabel.textProperty().bind(Bindings.size(table.getItems()).asString().concat(" events"));
+ //bind event count label to number of items in the table
+ eventCountLabel.textProperty().bind(new StringBinding() {
+ {
+ bind(table.getItems());
+ }
+
+ @Override
+ protected String computeValue() {
+ return Bundle.ListTimeline_evetnCountLabel_text(table.getItems().size());
+ }
+ });
}
/**
@@ -149,6 +166,15 @@ class ListTimeline extends BorderPane {
table.getItems().clear();
}
+ /**
+ * Get the selected event ID.
+ *
+ * @return The selected event ID.
+ */
+ Long getSelectedEventID() {
+ return table.getSelectionModel().getSelectedItem();
+ }
+
/**
* Set the Collection of events (by ID) to show in the table.
*
@@ -169,6 +195,18 @@ class ListTimeline extends BorderPane {
return table.getSelectionModel().getSelectedItems();
}
+ /**
+ * Set the ID of the event that is selected.
+ *
+ * @param selectedEventID The ID of the event that should be selected.
+ */
+ void selectEventID(Long selectedEventID) {
+ //restore selection.
+ table.scrollTo(selectedEventID);
+ table.getSelectionModel().select(selectedEventID);
+ table.requestFocus();
+ }
+
/**
* TableCell to show the icon for the type of an event.
*/
@@ -187,86 +225,23 @@ class ListTimeline extends BorderPane {
}
/**
- * TableCell to show the full description for an event.
+ * TableCell to show text derived from a SingleEvent by the given Funtion.
*/
- private class DescriptionCell extends EventTableCell {
+ private class TextEventTableCell extends EventTableCell {
- @Override
- protected void updateItem(Long item, boolean empty) {
- super.updateItem(item, empty);
+ private final Function textSupplier;
- if (empty || item == null) {
- setText("");
- } else {
- setText(getEvent().getDescription(DescriptionLoD.FULL));
- }
+ TextEventTableCell(Function textSupplier) {
+ this.textSupplier = textSupplier;
}
- }
-
- /**
- * TableCell to show the base type of an event.
- */
- private class BaseTypeCell extends EventTableCell {
@Override
protected void updateItem(Long item, boolean empty) {
super.updateItem(item, empty);
-
if (empty || item == null) {
- setText("");
+ setText(null);
} else {
- setText(getEvent().getEventType().getBaseType().getDisplayName());
- }
- }
- }
-
- /**
- * TableCell to show the sub type of an event.
- */
- private class EventTypeCell extends EventTableCell {
-
- @Override
- protected void updateItem(Long item, boolean empty) {
- super.updateItem(item, empty);
-
- if (empty || item == null) {
- setText("");
- } else {
- setText(getEvent().getEventType().getDisplayName());
- }
- }
- }
-
- /**
- * TableCell to show the known state of the file backing an event.
- */
- private class KnownCell extends EventTableCell {
-
- @Override
- protected void updateItem(Long item, boolean empty) {
- super.updateItem(item, empty);
-
- if (empty || item == null) {
- setText("");
- } else {
- setText(getEvent().getKnown().getName());
- }
- }
- }
-
- /**
- * TableCell to show the (start) time of an event.
- */
- private class EpochMillisCell extends EventTableCell {
-
- @Override
- protected void updateItem(Long item, boolean empty) {
- super.updateItem(item, empty);
-
- if (empty || item == null) {
- setText("");
- } else {
- setText(TimeLineController.getZonedFormatter().print(getEvent().getStartMillis()));
+ setText(textSupplier.apply(getEvent()));
}
}
}
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListViewPane.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListViewPane.java
index 23278f0d86..91762abd7c 100644
--- a/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListViewPane.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListViewPane.java
@@ -29,23 +29,11 @@ import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.ui.AbstractTimeLineView;
/**
- * @param The type of data plotted along the x axis
- * @param The type of data plotted along the y axis
- * @param The type of nodes used to represent data items
- * @param The type of the TimeLineChart this class uses to plot
- * the data. Must extend Region.
- *
- * TODO: this is becoming (too?) closely tied to the notion that there is a
- * XYChart doing the rendering. Is this a good idea? -jm
- *
- * TODO: pull up common history context menu items out of derived classes? -jm
- *
- * public abstract class AbstractVisualizationPane> extends BorderPane {
+ * An AbstractTimeLineView that uses a TableView to represent the events.
*/
public class ListViewPane extends AbstractTimeLineView {
- private final ListTimeline listChart;
+ private final ListTimeline listTimeline;
/**
* Constructor
@@ -54,15 +42,15 @@ public class ListViewPane extends AbstractTimeLineView {
*/
public ListViewPane(TimeLineController controller) {
super(controller);
- listChart = new ListTimeline(controller);
+ listTimeline = new ListTimeline(controller);
//initialize chart;
- setCenter(listChart);
+ setCenter(listTimeline);
setSettingsNodes(new ListViewPane.ListViewSettingsPane().getChildrenUnmodifiable());
//keep controller's list of selected event IDs in sync with this list's
- listChart.getSelectedEventIDs().addListener((Observable selectedIDs) -> {
- controller.selectEventIDs(listChart.getSelectedEventIDs());
+ listTimeline.getSelectedEventIDs().addListener((Observable selectedIDs) -> {
+ controller.selectEventIDs(listTimeline.getSelectedEventIDs());
});
}
@@ -73,13 +61,10 @@ public class ListViewPane extends AbstractTimeLineView {
@Override
protected void clearData() {
- listChart.clear();
+ listTimeline.clear();
}
private static class ListViewSettingsPane extends Parent {
-
- ListViewSettingsPane() {
- }
}
private class ListUpdateTask extends ViewRefreshTask {
@@ -90,21 +75,30 @@ public class ListViewPane extends AbstractTimeLineView {
@Override
protected Boolean call() throws Exception {
- super.call(); //To change body of generated methods, choose Tools | Templates.
+ super.call();
if (isCancelled()) {
return null;
}
+
FilteredEventsModel eventsModel = getEventsModel();
- //clear the chart and set the horixontal axis
+ //grab the currently selected event
+ Long selectedEventID = listTimeline.getSelectedEventID();
+
+ //clear the chart and set the time range.
resetView(eventsModel.getTimeRange());
- updateMessage("Querying db for events");
- //get the event stripes to be displayed
+ updateMessage("Querying DB for events");
+ //get the IDs of th events to be displayed
List eventIDs = eventsModel.getEventIDs();
- Platform.runLater(() -> listChart.setEventIDs(eventIDs));
+ updateMessage("Updating UI");
+ Platform.runLater(() -> {
+ //put the event IDs into the table.
+ listTimeline.setEventIDs(eventIDs);
+ //restore the selected event
+ listTimeline.selectEventID(selectedEventID);
+ });
- updateMessage("updating ui");
return eventIDs.isEmpty() == false;
}
@@ -117,6 +111,5 @@ public class ListViewPane extends AbstractTimeLineView {
@Override
protected void setDateValues(Interval timeRange) {
}
-
}
}
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java
index 77568a8916..e7325f776c 100644
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java
@@ -31,6 +31,7 @@ import org.apache.solr.common.util.ContentStream;
import org.apache.solr.common.SolrInputDocument;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.coreutils.TextUtil;
import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.datamodel.AbstractContent;
import org.sleuthkit.datamodel.AbstractFile;
@@ -298,6 +299,21 @@ class Ingester {
String s = "";
try {
s = new String(docChunkContentBuf, 0, read, docContentEncoding);
+ // Sanitize by replacing non-UTF-8 characters with caret '^' before adding to index
+ char[] chars = null;
+ for (int i = 0; i < s.length(); i++) {
+ if (!TextUtil.isValidSolrUTF8(s.charAt(i))) {
+ // only convert string to char[] if there is a non-UTF8 character
+ if (chars == null) {
+ chars = s.toCharArray();
+ }
+ chars[i] = '^';
+ }
+ }
+ // check if the string was modified (i.e. there was a non-UTF8 character found)
+ if (chars != null) {
+ s = new String(chars);
+ }
} catch (UnsupportedEncodingException ex) {
logger.log(Level.SEVERE, "Unsupported encoding", ex); //NON-NLS
}
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java
index 26c93f21e3..aa0bb5d830 100644
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java
@@ -25,7 +25,6 @@ import org.apache.solr.client.solrj.impl.HttpSolrServer;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.TskCoreException;
-import org.sleuthkit.autopsy.keywordsearch.Ingester.IngesterException;
import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchService;
import org.apache.solr.common.util.ContentStreamBase.StringStream;
import org.openide.util.lookup.ServiceProvider;
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TikaTextExtractor.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TikaTextExtractor.java
index 3ccf669b58..7022fb1b8b 100644
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TikaTextExtractor.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TikaTextExtractor.java
@@ -32,6 +32,7 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
+import org.sleuthkit.autopsy.coreutils.TextUtil;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
@@ -188,7 +189,7 @@ class TikaTextExtractor implements TextExtractor {
// Sanitize by replacing non-UTF-8 characters with caret '^'
for (int i = 0; i < totalRead; ++i) {
- if (!isValidSolrUTF8(textChunkBuf[i])) {
+ if (!TextUtil.isValidSolrUTF8(textChunkBuf[i])) {
textChunkBuf[i] = '^';
}
}
@@ -255,27 +256,6 @@ class TikaTextExtractor implements TextExtractor {
return success;
}
- /**
- * This method determines if a passed-in Java char (16 bits) is a valid
- * UTF-8 printable character, returning true if so, false if not.
- *
- * Note that this method can have ramifications for characters outside the
- * Unicode Base Multilingual Plane (BMP), which require more than 16 bits.
- * We are using Java characters (16 bits) to look at the data and this will
- * not accurately identify any non-BMP character (larger than 16 bits)
- * ending with 0xFFFF and 0xFFFE. In the interest of a fast solution, we
- * have chosen to ignore the extended planes above Unicode BMP for the time
- * being. The net result of this is some non-BMP characters may be
- * interspersed with '^' characters in Autopsy.
- *
- * @param ch the character to test
- *
- * @return Returns true if the character is valid UTF-8, false if not.
- */
- private static boolean isValidSolrUTF8(char ch) {
- return ((ch <= 0xFDD0 || ch >= 0xFDEF) && (ch > 0x1F || ch == 0x9 || ch == 0xA || ch == 0xD) && (ch != 0xFFFF) && (ch != 0xFFFE));
- }
-
@Override
public boolean isContentTypeSpecific() {
return true;