Merge branch 'solr-8-upgrade' of github.com:sleuthkit/autopsy into solr8_libraries
@ -2300,7 +2300,6 @@ public class Case {
|
|||||||
} else {
|
} else {
|
||||||
throw new CaseActionException(Bundle.Case_open_exception_multiUserCaseNotEnabled());
|
throw new CaseActionException(Bundle.Case_open_exception_multiUserCaseNotEnabled());
|
||||||
}
|
}
|
||||||
caseDb.registerForEvents(sleuthkitEventListener);
|
|
||||||
} catch (TskUnsupportedSchemaVersionException ex) {
|
} catch (TskUnsupportedSchemaVersionException ex) {
|
||||||
throw new CaseActionException(Bundle.Case_exceptionMessage_unsupportedSchemaVersionMessage(ex.getLocalizedMessage()), ex);
|
throw new CaseActionException(Bundle.Case_exceptionMessage_unsupportedSchemaVersionMessage(ex.getLocalizedMessage()), ex);
|
||||||
} catch (UserPreferencesException ex) {
|
} catch (UserPreferencesException ex) {
|
||||||
@ -2321,6 +2320,12 @@ public class Case {
|
|||||||
private void openCaseLevelServices(ProgressIndicator progressIndicator) {
|
private void openCaseLevelServices(ProgressIndicator progressIndicator) {
|
||||||
progressIndicator.progress(Bundle.Case_progressMessage_openingCaseLevelServices());
|
progressIndicator.progress(Bundle.Case_progressMessage_openingCaseLevelServices());
|
||||||
this.caseServices = new Services(caseDb);
|
this.caseServices = new Services(caseDb);
|
||||||
|
/*
|
||||||
|
* RC Note: JM put this initialization here. I'm not sure why. However,
|
||||||
|
* my attempt to put it in the openCaseDatabase method seems to lead to
|
||||||
|
* intermittent unchecked exceptions concerning a missing subscriber.
|
||||||
|
*/
|
||||||
|
caseDb.registerForEvents(sleuthkitEventListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -78,6 +78,9 @@ public final class UserPreferences {
|
|||||||
public static final String EXTERNAL_HEX_EDITOR_PATH = "ExternalHexEditorPath";
|
public static final String EXTERNAL_HEX_EDITOR_PATH = "ExternalHexEditorPath";
|
||||||
public static final String SOLR_MAX_JVM_SIZE = "SolrMaxJVMSize";
|
public static final String SOLR_MAX_JVM_SIZE = "SolrMaxJVMSize";
|
||||||
public static final String RESULTS_TABLE_PAGE_SIZE = "ResultsTablePageSize";
|
public static final String RESULTS_TABLE_PAGE_SIZE = "ResultsTablePageSize";
|
||||||
|
private static final String GEO_TILE_OPTION = "GeolocationTileOption";
|
||||||
|
private static final String GEO_OSM_TILE_ZIP_PATH = "GeolocationOsmZipPath";
|
||||||
|
private static final String GEO_OSM_SERVER_ADDRESS = "GeolocationOsmServerAddress";
|
||||||
|
|
||||||
// Prevent instantiation.
|
// Prevent instantiation.
|
||||||
private UserPreferences() {
|
private UserPreferences() {
|
||||||
@ -534,4 +537,59 @@ public final class UserPreferences {
|
|||||||
public static String getExternalHexEditorPath() {
|
public static String getExternalHexEditorPath() {
|
||||||
return preferences.get(EXTERNAL_HEX_EDITOR_PATH, Paths.get("C:", "Program Files", "HxD", "HxD.exe").toString());
|
return preferences.get(EXTERNAL_HEX_EDITOR_PATH, Paths.get("C:", "Program Files", "HxD", "HxD.exe").toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the geolocation tile server option.
|
||||||
|
*
|
||||||
|
* @param option
|
||||||
|
*/
|
||||||
|
public static void setGeolocationTileOption(int option) {
|
||||||
|
preferences.putInt(GEO_TILE_OPTION, option);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the Geolocation tile option. If not found, the value will
|
||||||
|
* default to 0.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static int getGeolocationtTileOption() {
|
||||||
|
return preferences.getInt(GEO_TILE_OPTION, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the path to the OSM tile zip file.
|
||||||
|
*
|
||||||
|
* @param absolutePath
|
||||||
|
*/
|
||||||
|
public static void setGeolocationOsmZipPath(String absolutePath) {
|
||||||
|
preferences.put(GEO_OSM_TILE_ZIP_PATH, absolutePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the path for the OSM tile zip file or returns empty string if
|
||||||
|
* none was found.
|
||||||
|
*
|
||||||
|
* @return Path to zip file
|
||||||
|
*/
|
||||||
|
public static String getGeolocationOsmZipPath() {
|
||||||
|
return preferences.get(GEO_OSM_TILE_ZIP_PATH, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the address of the OSM tile server.
|
||||||
|
*
|
||||||
|
* @param address
|
||||||
|
*/
|
||||||
|
public static void setGeolocationOsmServerAddress(String address) {
|
||||||
|
preferences.put(GEO_OSM_SERVER_ADDRESS, address);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the address to the OSM server or null if one was not found.
|
||||||
|
*
|
||||||
|
* @return Address of OSM server
|
||||||
|
*/
|
||||||
|
public static String getGeolocationOsmServerAddress() {
|
||||||
|
return preferences.get(GEO_OSM_SERVER_ADDRESS, "");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,7 @@ import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
|
|||||||
import org.sleuthkit.datamodel.Blackboard;
|
import org.sleuthkit.datamodel.Blackboard;
|
||||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||||
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT;
|
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT;
|
||||||
|
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT;
|
||||||
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_DATA_SOURCE_USAGE;
|
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_DATA_SOURCE_USAGE;
|
||||||
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG;
|
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG;
|
||||||
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_GEN_INFO;
|
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_GEN_INFO;
|
||||||
@ -238,6 +239,7 @@ public class ExtractedContent implements AutopsyVisitableItem {
|
|||||||
doNotShow.add(new BlackboardArtifact.Type(TSK_ACCOUNT));
|
doNotShow.add(new BlackboardArtifact.Type(TSK_ACCOUNT));
|
||||||
doNotShow.add(new BlackboardArtifact.Type(TSK_DATA_SOURCE_USAGE));
|
doNotShow.add(new BlackboardArtifact.Type(TSK_DATA_SOURCE_USAGE));
|
||||||
doNotShow.add(new BlackboardArtifact.Type(TSK_DOWNLOAD_SOURCE));
|
doNotShow.add(new BlackboardArtifact.Type(TSK_DOWNLOAD_SOURCE));
|
||||||
|
doNotShow.add(new BlackboardArtifact.Type(TSK_ASSOCIATED_OBJECT));
|
||||||
}
|
}
|
||||||
|
|
||||||
private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> {
|
private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> {
|
||||||
|
@ -0,0 +1,164 @@
|
|||||||
|
/*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2019 Basis Technology Corp.
|
||||||
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.sleuthkit.autopsy.datasourceprocessors.xry;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
|
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||||
|
import org.sleuthkit.datamodel.Content;
|
||||||
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Template parse method for reports that make blackboard attributes from a
|
||||||
|
* single key value pair.
|
||||||
|
*
|
||||||
|
* This parse implementation will create 1 artifact per XRY entity.
|
||||||
|
*/
|
||||||
|
abstract class AbstractSingleKeyValueParser implements XRYFileParser {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(AbstractSingleKeyValueParser.class.getName());
|
||||||
|
|
||||||
|
private static final char KEY_VALUE_DELIMITER = ':';
|
||||||
|
|
||||||
|
protected static final String PARSER_NAME = "XRY DSP";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void parse(XRYFileReader reader, Content parent) throws IOException, TskCoreException {
|
||||||
|
Path reportPath = reader.getReportPath();
|
||||||
|
logger.log(Level.INFO, String.format("[XRY DSP] Processing report at [ %s ]", reportPath.toString()));
|
||||||
|
|
||||||
|
while (reader.hasNextEntity()) {
|
||||||
|
String xryEntity = reader.nextEntity();
|
||||||
|
String[] xryLines = xryEntity.split("\n");
|
||||||
|
|
||||||
|
List<BlackboardAttribute> attributes = new ArrayList<>();
|
||||||
|
|
||||||
|
//First line of the entity is the title.
|
||||||
|
if (xryLines.length > 0) {
|
||||||
|
logger.log(Level.INFO, String.format("[XRY DSP] Processing [ %s ]", xryLines[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
String namespace = "";
|
||||||
|
//Process each line, searching for a key value pair or a namespace.
|
||||||
|
//If neither are found, an error message is logged.
|
||||||
|
for (int i = 1; i < xryLines.length; i++) {
|
||||||
|
String xryLine = xryLines[i];
|
||||||
|
|
||||||
|
String candidateNamespace = xryLine.trim();
|
||||||
|
//Check if the line is a namespace, which gives context to the keys
|
||||||
|
//that follow.
|
||||||
|
if (isNamespace(candidateNamespace)) {
|
||||||
|
namespace = candidateNamespace;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Find the XRY key on this line. Assume key is the value between
|
||||||
|
//the start of the line and the first delimiter.
|
||||||
|
int keyDelimiter = xryLine.indexOf(KEY_VALUE_DELIMITER);
|
||||||
|
if (keyDelimiter == -1) {
|
||||||
|
logger.log(Level.SEVERE, String.format("[XRY DSP] Expected a key value "
|
||||||
|
+ "pair on this line (in brackets) [ %s ], but one was not detected."
|
||||||
|
+ " Here is the previous line [ %s ]. What does this mean?", xryLine, xryLines[i - 1]));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String key = xryLine.substring(0, keyDelimiter).trim();
|
||||||
|
String value = xryLine.substring(keyDelimiter + 1).trim();
|
||||||
|
|
||||||
|
if (!isKey(key)) {
|
||||||
|
logger.log(Level.SEVERE, String.format("[XRY DSP] The following key, "
|
||||||
|
+ "value pair (in brackets, respectively) [ %s ], [ %s ] was not recognized. Discarding..."
|
||||||
|
+ " Here is the previous line [ %s ] for context. What does this key mean?", key, value, xryLines[i - 1]));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.isEmpty()) {
|
||||||
|
logger.log(Level.SEVERE, String.format("[XRY DSP] The following key "
|
||||||
|
+ "(in brackets) [ %s ] was recognized, but the value was empty. Discarding..."
|
||||||
|
+ " Here is the previous line for context [ %s ]. What does this mean?", key, xryLines[i - 1]));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
BlackboardAttribute attribute = makeAttribute(namespace, key, value);
|
||||||
|
//Temporarily allowing null to be valid return type until a decision
|
||||||
|
//is made about how to handle keys we are choosing to ignore.
|
||||||
|
if (attribute != null) {
|
||||||
|
attributes.add(makeAttribute(namespace, key, value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Only create artifacts with non-empty attributes.
|
||||||
|
if (!attributes.isEmpty()) {
|
||||||
|
makeArtifact(attributes, parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the key candidate is a known key. A key candidate is a
|
||||||
|
* string literal that begins a line and is terminated by a semi-colon.
|
||||||
|
*
|
||||||
|
* Ex:
|
||||||
|
*
|
||||||
|
* Call Type : Missed
|
||||||
|
*
|
||||||
|
* "Call Type" would be the key candidate that was extracted.
|
||||||
|
*
|
||||||
|
* @param key Key to test. These keys are trimmed of whitespace only.
|
||||||
|
* @return Indication if this key can be processed.
|
||||||
|
*/
|
||||||
|
abstract boolean isKey(String key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the namespace candidate is a known namespace. A namespace
|
||||||
|
* candidate is a string literal that makes up an entire line.
|
||||||
|
*
|
||||||
|
* Ex:
|
||||||
|
*
|
||||||
|
* To
|
||||||
|
* Tel : +1245325
|
||||||
|
*
|
||||||
|
* "To" would be the candidate namespace that was extracted.
|
||||||
|
*
|
||||||
|
* @param nameSpace Namespace to test. Namespaces are trimmed of whitespace
|
||||||
|
* only.
|
||||||
|
* @return Indication if this namespace can be processed.
|
||||||
|
*/
|
||||||
|
abstract boolean isNamespace(String nameSpace);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an attribute from the extracted key value pair.
|
||||||
|
*
|
||||||
|
* @param nameSpace The namespace of this key value pair.
|
||||||
|
* It will have been verified with isNamespace, otherwise it will be empty.
|
||||||
|
* @param key The key that was verified with isKey.
|
||||||
|
* @param value The value associated with that key.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
abstract BlackboardAttribute makeAttribute(String nameSpace, String key, String value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes an artifact from the parsed attributes.
|
||||||
|
*/
|
||||||
|
abstract void makeArtifact(List<BlackboardAttribute> attributes, Content parent) throws TskCoreException;
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
XRYDataSourceProcessorConfigPanel.fileBrowserButton.text=Browse
|
||||||
|
XRYDataSourceProcessorConfigPanel.filePathTextField.text=
|
||||||
|
XRYDataSourceProcessorConfigPanel.xrySelectFolderLabel.text=Select an XRY Folder
|
||||||
|
XRYDataSourceProcessorConfigPanel.errorLabel.text=
|
@ -0,0 +1,16 @@
|
|||||||
|
XRYDataSourceProcessor.childNotReadable=Top level path [ %s ] is not readable
|
||||||
|
XRYDataSourceProcessor.dataSourceType=XRY Logical Report
|
||||||
|
XRYDataSourceProcessor.fileAdded=Added %s to the case database
|
||||||
|
XRYDataSourceProcessor.ioError=I/O error occured trying to test the XRY report folder
|
||||||
|
XRYDataSourceProcessor.noCurrentCase=No case is open.
|
||||||
|
XRYDataSourceProcessor.noPathSelected=Please select a XRY folder
|
||||||
|
XRYDataSourceProcessor.notAFolder=The selected path is not a folder
|
||||||
|
XRYDataSourceProcessor.notReadable=Selected path is not readable
|
||||||
|
XRYDataSourceProcessor.notXRYFolder=Selected folder did not contain any XRY files
|
||||||
|
XRYDataSourceProcessor.preppingFiles=Preparing to add files to the case database
|
||||||
|
XRYDataSourceProcessor.processingFiles=Processing all XRY files...
|
||||||
|
XRYDataSourceProcessor.unexpectedError=Internal error occurred while processing XRY report
|
||||||
|
XRYDataSourceProcessorConfigPanel.fileBrowserButton.text=Browse
|
||||||
|
XRYDataSourceProcessorConfigPanel.filePathTextField.text=
|
||||||
|
XRYDataSourceProcessorConfigPanel.xrySelectFolderLabel.text=Select an XRY Folder
|
||||||
|
XRYDataSourceProcessorConfigPanel.errorLabel.text=
|
175
Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java
Executable file
@ -0,0 +1,175 @@
|
|||||||
|
/*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2019 Basis Technology Corp.
|
||||||
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.sleuthkit.autopsy.datasourceprocessors.xry;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.time.format.DateTimeParseException;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
|
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||||
|
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||||
|
import org.sleuthkit.datamodel.Content;
|
||||||
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses XRY Calls files and creates artifacts.
|
||||||
|
*/
|
||||||
|
final class XRYCallsFileParser extends AbstractSingleKeyValueParser {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(XRYCallsFileParser.class.getName());
|
||||||
|
|
||||||
|
private static final DateTimeFormatter DATE_TIME_PARSER
|
||||||
|
= DateTimeFormatter.ofPattern("M/d/y h:m:s [a][ z]");
|
||||||
|
|
||||||
|
private static final String INCOMING = "Incoming";
|
||||||
|
|
||||||
|
//All known XRY keys for call reports.
|
||||||
|
private static final Set<String> XRY_KEYS = new HashSet<String>() {
|
||||||
|
{
|
||||||
|
add("tel");
|
||||||
|
add("number");
|
||||||
|
add("call type");
|
||||||
|
add("name (matched)");
|
||||||
|
add("time");
|
||||||
|
add("duration");
|
||||||
|
add("storage");
|
||||||
|
add("index");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//All known XRY namespaces for call reports.
|
||||||
|
private static final Set<String> XRY_NAMESPACES = new HashSet<String>() {
|
||||||
|
{
|
||||||
|
add("to");
|
||||||
|
add("from");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean isKey(String key) {
|
||||||
|
String normalizedKey = key.toLowerCase();
|
||||||
|
return XRY_KEYS.contains(normalizedKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean isNamespace(String nameSpace) {
|
||||||
|
String normalizedNamespace = nameSpace.toLowerCase();
|
||||||
|
return XRY_NAMESPACES.contains(normalizedNamespace);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
BlackboardAttribute makeAttribute(String nameSpace, String key, String value) {
|
||||||
|
String normalizedKey = key.toLowerCase();
|
||||||
|
String normalizedNamespace = nameSpace.toLowerCase();
|
||||||
|
|
||||||
|
switch (normalizedKey) {
|
||||||
|
case "time":
|
||||||
|
//Tranform value to epoch ms
|
||||||
|
try {
|
||||||
|
String dateTime = removeDateTimeLocale(value);
|
||||||
|
String normalizedDateTime = dateTime.trim();
|
||||||
|
long dateTimeInEpoch = calculateSecondsSinceEpoch(normalizedDateTime);
|
||||||
|
return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START, PARSER_NAME, dateTimeInEpoch);
|
||||||
|
} catch (DateTimeParseException ex) {
|
||||||
|
logger.log(Level.SEVERE, String.format("[XRY DSP] Assumption about the date time "
|
||||||
|
+ "formatting of call logs is not right. Here is the value [ %s ]", value), ex);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
case "duration":
|
||||||
|
//Ignore for now.
|
||||||
|
return null;
|
||||||
|
case "storage":
|
||||||
|
//Ignore for now.
|
||||||
|
return null;
|
||||||
|
case "index":
|
||||||
|
//Ignore for now.
|
||||||
|
return null;
|
||||||
|
case "tel":
|
||||||
|
//Apply the namespace
|
||||||
|
if(normalizedNamespace.equals("from")) {
|
||||||
|
return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM, PARSER_NAME, value);
|
||||||
|
} else {
|
||||||
|
return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO, PARSER_NAME, value);
|
||||||
|
}
|
||||||
|
case "call type":
|
||||||
|
String normalizedValue = value.toLowerCase();
|
||||||
|
switch (normalizedValue) {
|
||||||
|
case "missed":
|
||||||
|
case "received":
|
||||||
|
return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DIRECTION, PARSER_NAME, INCOMING);
|
||||||
|
case "dialed":
|
||||||
|
//Ignore for now.
|
||||||
|
return null;
|
||||||
|
case "last dialed":
|
||||||
|
//Ignore for now.
|
||||||
|
return null;
|
||||||
|
default:
|
||||||
|
logger.log(Level.SEVERE, String.format("Call type (in brackets) [ %s ] not recognized.", value));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
case "number":
|
||||||
|
return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER, PARSER_NAME, value);
|
||||||
|
case "name (matched)":
|
||||||
|
return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME, PARSER_NAME, value);
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException(String.format("key [ %s ] was not recognized.", key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void makeArtifact(List<BlackboardAttribute> attributes, Content parent) throws TskCoreException {
|
||||||
|
BlackboardArtifact artifact = parent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG);
|
||||||
|
artifact.addAttributes(attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the locale from the date time value.
|
||||||
|
*
|
||||||
|
* Locale in this case being (Device) or (Network).
|
||||||
|
*
|
||||||
|
* @param dateTime XRY datetime value to be sanitized.
|
||||||
|
* @return A purer date time value.
|
||||||
|
*/
|
||||||
|
private String removeDateTimeLocale(String dateTime) {
|
||||||
|
int index = dateTime.indexOf('(');
|
||||||
|
if (index == -1) {
|
||||||
|
return dateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dateTime.substring(0, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the date time value and calculates ms since epoch. The time zone is
|
||||||
|
* assumed to be UTC.
|
||||||
|
*
|
||||||
|
* @param dateTime
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private long calculateSecondsSinceEpoch(String dateTime) {
|
||||||
|
LocalDateTime localDateTime = LocalDateTime.parse(dateTime, DATE_TIME_PARSER);
|
||||||
|
//Assume dates have no offset.
|
||||||
|
return localDateTime.toInstant(ZoneOffset.UTC).getEpochSecond();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2019 Basis Technology Corp.
|
||||||
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.sleuthkit.autopsy.datasourceprocessors.xry;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||||
|
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||||
|
import org.sleuthkit.datamodel.Content;
|
||||||
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses XRY Contacts-Contacts files and creates artifacts.
|
||||||
|
*/
|
||||||
|
final class XRYContactsFileParser extends AbstractSingleKeyValueParser {
|
||||||
|
|
||||||
|
//All of the known XRY keys for contacts.
|
||||||
|
private static final Set<String> XRY_KEYS = new HashSet<String>() {{
|
||||||
|
add("name");
|
||||||
|
add("tel");
|
||||||
|
add("storage");
|
||||||
|
}};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean isKey(String key) {
|
||||||
|
String normalizedKey = key.toLowerCase();
|
||||||
|
return XRY_KEYS.contains(normalizedKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean isNamespace(String nameSpace) {
|
||||||
|
//No namespaces are currently known for this report type.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
BlackboardAttribute makeAttribute(String nameSpace, String key, String value) {
|
||||||
|
String normalizedKey = key.toLowerCase();
|
||||||
|
switch(normalizedKey) {
|
||||||
|
case "name":
|
||||||
|
return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME, PARSER_NAME, value);
|
||||||
|
case "tel":
|
||||||
|
return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER, PARSER_NAME, value);
|
||||||
|
case "storage":
|
||||||
|
//Ignore for now.
|
||||||
|
return null;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException(String.format("Key [ %s ] was not recognized", key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void makeArtifact(List<BlackboardAttribute> attributes, Content parent) throws TskCoreException {
|
||||||
|
BlackboardArtifact artifact = parent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT);
|
||||||
|
artifact.addAttributes(attributes);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,363 @@
|
|||||||
|
/*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2019 Basis Technology Corp.
|
||||||
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.sleuthkit.autopsy.datasourceprocessors.xry;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.LinkOption;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.SwingWorker;
|
||||||
|
import org.openide.util.NbBundle;
|
||||||
|
import org.openide.util.lookup.ServiceProvider;
|
||||||
|
import org.openide.util.lookup.ServiceProviders;
|
||||||
|
import org.sleuthkit.autopsy.casemodule.Case;
|
||||||
|
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||||
|
import org.sleuthkit.autopsy.casemodule.services.FileManager;
|
||||||
|
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor;
|
||||||
|
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback;
|
||||||
|
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor;
|
||||||
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
|
import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor;
|
||||||
|
import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException;
|
||||||
|
import org.sleuthkit.datamodel.AbstractFile;
|
||||||
|
import org.sleuthkit.datamodel.LocalFilesDataSource;
|
||||||
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
|
import org.sleuthkit.datamodel.TskDataException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An XRY Report data source processor.
|
||||||
|
*/
|
||||||
|
@ServiceProviders(value = {
|
||||||
|
@ServiceProvider(service = DataSourceProcessor.class),
|
||||||
|
@ServiceProvider(service = AutoIngestDataSourceProcessor.class)
|
||||||
|
})
|
||||||
|
public class XRYDataSourceProcessor implements DataSourceProcessor, AutoIngestDataSourceProcessor {
|
||||||
|
|
||||||
|
private final XRYDataSourceProcessorConfigPanel configPanel;
|
||||||
|
|
||||||
|
private static final int XRY_FILES_DEPTH = 1;
|
||||||
|
|
||||||
|
//Background processor to relieve the EDT from adding files to the case
|
||||||
|
//database and parsing the report files.
|
||||||
|
private XRYReportProcessorSwingWorker swingWorker;
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(XRYDataSourceProcessor.class.getName());
|
||||||
|
|
||||||
|
public XRYDataSourceProcessor() {
|
||||||
|
configPanel = XRYDataSourceProcessorConfigPanel.getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NbBundle.Messages({
|
||||||
|
"XRYDataSourceProcessor.dataSourceType=XRY Logical Report"
|
||||||
|
})
|
||||||
|
public String getDataSourceType() {
|
||||||
|
return Bundle.XRYDataSourceProcessor_dataSourceType();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JPanel getPanel() {
|
||||||
|
return configPanel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the selected path.
|
||||||
|
*
|
||||||
|
* This functions checks permissions to the path directly and then to each
|
||||||
|
* of its top most children, if it is a folder.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@NbBundle.Messages({
|
||||||
|
"XRYDataSourceProcessor.noPathSelected=Please select a XRY folder",
|
||||||
|
"XRYDataSourceProcessor.notReadable=Selected path is not readable",
|
||||||
|
"XRYDataSourceProcessor.notXRYFolder=Selected folder did not contain any XRY files",
|
||||||
|
"XRYDataSourceProcessor.ioError=I/O error occured trying to test the XRY report folder",
|
||||||
|
"XRYDataSourceProcessor.childNotReadable=Top level path [ %s ] is not readable",
|
||||||
|
"XRYDataSourceProcessor.notAFolder=The selected path is not a folder"
|
||||||
|
})
|
||||||
|
public boolean isPanelValid() {
|
||||||
|
configPanel.clearErrorText();
|
||||||
|
String selectedFilePath = configPanel.getSelectedFilePath();
|
||||||
|
if (selectedFilePath.isEmpty()) {
|
||||||
|
configPanel.setErrorText(Bundle.XRYDataSourceProcessor_noPathSelected());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
File selectedFile = new File(selectedFilePath);
|
||||||
|
Path selectedPath = selectedFile.toPath();
|
||||||
|
|
||||||
|
//Test permissions
|
||||||
|
if (!Files.isReadable(selectedPath)) {
|
||||||
|
configPanel.setErrorText(Bundle.XRYDataSourceProcessor_notReadable());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
BasicFileAttributes attr = Files.readAttributes(selectedPath,
|
||||||
|
BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
|
||||||
|
|
||||||
|
if (!attr.isDirectory()) {
|
||||||
|
configPanel.setErrorText(Bundle.XRYDataSourceProcessor_notAFolder());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Ensure all of the XRY_FILES_DEPTH paths are readable.
|
||||||
|
try (Stream<Path> allFiles = Files.walk(selectedPath, XRY_FILES_DEPTH)) {
|
||||||
|
Iterator<Path> allFilesIterator = allFiles.iterator();
|
||||||
|
while (allFilesIterator.hasNext()) {
|
||||||
|
Path currentPath = allFilesIterator.next();
|
||||||
|
if (!Files.isReadable(currentPath)) {
|
||||||
|
Path fileName = currentPath.subpath(currentPath.getNameCount() - 2,
|
||||||
|
currentPath.getNameCount());
|
||||||
|
configPanel.setErrorText(String.format(
|
||||||
|
Bundle.XRYDataSourceProcessor_childNotReadable(),
|
||||||
|
fileName.toString()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Validate the folder.
|
||||||
|
if (!XRYFolder.isXRYFolder(selectedPath)) {
|
||||||
|
configPanel.setErrorText(Bundle.XRYDataSourceProcessor_notXRYFolder());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (IOException | UncheckedIOException ex) {
|
||||||
|
configPanel.setErrorText(Bundle.XRYDataSourceProcessor_ioError());
|
||||||
|
logger.log(Level.WARNING, "[XRY DSP] I/O exception encountered trying to test the XRY folder.", ex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests if the given path is an XRY Folder.
|
||||||
|
*
|
||||||
|
* This function assumes the calling thread has sufficient privileges to
|
||||||
|
* read the folder and its child content.
|
||||||
|
*
|
||||||
|
* @param dataSourcePath Path to test
|
||||||
|
* @return 100 if the folder passes the XRY Folder check, 0 otherwise.
|
||||||
|
* @throws AutoIngestDataSourceProcessorException if an I/O error occurs
|
||||||
|
* during disk reads.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int canProcess(Path dataSourcePath) throws AutoIngestDataSourceProcessorException {
|
||||||
|
try {
|
||||||
|
if (XRYFolder.isXRYFolder(dataSourcePath)) {
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new AutoIngestDataSourceProcessorException("[XRY DSP] encountered I/O error " + ex.getMessage(), ex);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes the XRY folder that the examiner selected. The heavy lifting is
|
||||||
|
* done off of the EDT, so this function will return while the
|
||||||
|
* path is still being processed.
|
||||||
|
*
|
||||||
|
* This function assumes the calling thread has sufficient privileges to
|
||||||
|
* read the folder and its child content, which should have been validated
|
||||||
|
* in isPanelValid().
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@NbBundle.Messages({
|
||||||
|
"XRYDataSourceProcessor.noCurrentCase=No case is open."
|
||||||
|
})
|
||||||
|
public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
|
||||||
|
progressMonitor.setIndeterminate(true);
|
||||||
|
|
||||||
|
String selectedFilePath = configPanel.getSelectedFilePath();
|
||||||
|
File selectedFile = new File(selectedFilePath);
|
||||||
|
Path selectedPath = selectedFile.toPath();
|
||||||
|
|
||||||
|
try {
|
||||||
|
XRYFolder xryFolder = new XRYFolder(selectedPath);
|
||||||
|
FileManager fileManager = Case.getCurrentCaseThrows()
|
||||||
|
.getServices().getFileManager();
|
||||||
|
String uniqueUUID = UUID.randomUUID().toString();
|
||||||
|
//Move heavy lifting to a background task.
|
||||||
|
swingWorker = new XRYReportProcessorSwingWorker(xryFolder, progressMonitor,
|
||||||
|
callback, fileManager, uniqueUUID);
|
||||||
|
swingWorker.execute();
|
||||||
|
} catch (NoCurrentCaseException ex) {
|
||||||
|
logger.log(Level.WARNING, "[XRY DSP] No case is currently open.", ex);
|
||||||
|
callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS,
|
||||||
|
Lists.newArrayList(Bundle.XRYDataSourceProcessor_noCurrentCase(),
|
||||||
|
ex.getMessage()), Lists.newArrayList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes the XRY Folder encountered in an auto-ingest context. The heavy
|
||||||
|
* lifting is done off of the EDT, so this function will return while the
|
||||||
|
* path is still being processed.
|
||||||
|
*
|
||||||
|
* This function assumes the calling thread has sufficient privileges to
|
||||||
|
* read the folder and its child content.
|
||||||
|
*
|
||||||
|
* @param deviceId
|
||||||
|
* @param dataSourcePath
|
||||||
|
* @param progressMonitor
|
||||||
|
* @param callBack
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void process(String deviceId, Path dataSourcePath, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callBack) {
|
||||||
|
progressMonitor.setIndeterminate(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
XRYFolder xryFolder = new XRYFolder(dataSourcePath);
|
||||||
|
FileManager fileManager = Case.getCurrentCaseThrows()
|
||||||
|
.getServices().getFileManager();
|
||||||
|
//Move heavy lifting to a background task.
|
||||||
|
swingWorker = new XRYReportProcessorSwingWorker(xryFolder, progressMonitor,
|
||||||
|
callBack, fileManager, deviceId);
|
||||||
|
swingWorker.execute();
|
||||||
|
} catch (NoCurrentCaseException ex) {
|
||||||
|
logger.log(Level.WARNING, "[XRY DSP] No case is currently open.", ex);
|
||||||
|
callBack.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS,
|
||||||
|
Lists.newArrayList(Bundle.XRYDataSourceProcessor_noCurrentCase(),
|
||||||
|
ex.getMessage()), Lists.newArrayList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancel() {
|
||||||
|
if (swingWorker != null) {
|
||||||
|
swingWorker.cancel(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
//Clear the current selected file path.
|
||||||
|
configPanel.clearSelectedFilePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Relieves the EDT from having to process the XRY report and write to the
|
||||||
|
* case database.
|
||||||
|
*/
|
||||||
|
private class XRYReportProcessorSwingWorker extends SwingWorker<LocalFilesDataSource, Void> {
|
||||||
|
|
||||||
|
private final DataSourceProcessorProgressMonitor progressMonitor;
|
||||||
|
private final DataSourceProcessorCallback callback;
|
||||||
|
private final FileManager fileManager;
|
||||||
|
private final XRYFolder xryFolder;
|
||||||
|
private final String uniqueUUID;
|
||||||
|
|
||||||
|
public XRYReportProcessorSwingWorker(XRYFolder folder,
|
||||||
|
DataSourceProcessorProgressMonitor progressMonitor,
|
||||||
|
DataSourceProcessorCallback callback,
|
||||||
|
FileManager fileManager,
|
||||||
|
String uniqueUUID) {
|
||||||
|
|
||||||
|
this.xryFolder = folder;
|
||||||
|
this.progressMonitor = progressMonitor;
|
||||||
|
this.callback = callback;
|
||||||
|
this.fileManager = fileManager;
|
||||||
|
this.uniqueUUID = uniqueUUID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NbBundle.Messages({
|
||||||
|
"XRYDataSourceProcessor.preppingFiles=Preparing to add files to the case database",
|
||||||
|
"XRYDataSourceProcessor.processingFiles=Processing all XRY files..."
|
||||||
|
})
|
||||||
|
protected LocalFilesDataSource doInBackground() throws TskCoreException,
|
||||||
|
TskDataException, IOException {
|
||||||
|
progressMonitor.setProgressText(Bundle.XRYDataSourceProcessor_preppingFiles());
|
||||||
|
|
||||||
|
List<Path> nonXRYFiles = xryFolder.getNonXRYFiles();
|
||||||
|
List<String> filePaths = nonXRYFiles.stream()
|
||||||
|
//Map paths to string representations.
|
||||||
|
.map(Path::toString)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
LocalFilesDataSource dataSource = fileManager.addLocalFilesDataSource(
|
||||||
|
uniqueUUID,
|
||||||
|
"XRY Report", //Name
|
||||||
|
"", //Timezone
|
||||||
|
filePaths,
|
||||||
|
new ProgressMonitorAdapter(progressMonitor));
|
||||||
|
|
||||||
|
//Process the report files.
|
||||||
|
progressMonitor.setProgressText(Bundle.XRYDataSourceProcessor_processingFiles());
|
||||||
|
XRYReportProcessor.process(xryFolder, dataSource);
|
||||||
|
return dataSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NbBundle.Messages({
|
||||||
|
"XRYDataSourceProcessor.unexpectedError=Internal error occurred while processing XRY report"
|
||||||
|
})
|
||||||
|
public void done() {
|
||||||
|
try {
|
||||||
|
LocalFilesDataSource newDataSource = get();
|
||||||
|
callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.NO_ERRORS,
|
||||||
|
Lists.newArrayList(), Lists.newArrayList(newDataSource));
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
logger.log(Level.WARNING, "[XRY DSP] Thread was interrupted while processing the XRY report."
|
||||||
|
+ " The case may or may not have the complete XRY report.", ex);
|
||||||
|
callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.NO_ERRORS,
|
||||||
|
Lists.newArrayList(), Lists.newArrayList());
|
||||||
|
} catch (ExecutionException ex) {
|
||||||
|
logger.log(Level.SEVERE, "[XRY DSP] Unexpected internal error while processing XRY report.", ex);
|
||||||
|
callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS,
|
||||||
|
Lists.newArrayList(Bundle.XRYDataSourceProcessor_unexpectedError(),
|
||||||
|
ex.toString()), Lists.newArrayList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes the DSP progress monitor compatible with the File Manager
|
||||||
|
* progress updater.
|
||||||
|
*/
|
||||||
|
private class ProgressMonitorAdapter implements FileManager.FileAddProgressUpdater {
|
||||||
|
|
||||||
|
private final DataSourceProcessorProgressMonitor progressMonitor;
|
||||||
|
|
||||||
|
ProgressMonitorAdapter(DataSourceProcessorProgressMonitor progressMonitor) {
|
||||||
|
this.progressMonitor = progressMonitor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NbBundle.Messages({
|
||||||
|
"XRYDataSourceProcessor.fileAdded=Added %s to the case database"
|
||||||
|
})
|
||||||
|
public void fileAdded(AbstractFile newFile) {
|
||||||
|
progressMonitor.setProgressText(String.format(Bundle.XRYDataSourceProcessor_fileAdded(), newFile.getName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,88 @@
|
|||||||
|
<?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" alignment="0" attributes="0">
|
||||||
|
<EmptySpace max="-2" attributes="0"/>
|
||||||
|
<Group type="103" groupAlignment="0" attributes="0">
|
||||||
|
<Component id="errorLabel" max="32767" attributes="0"/>
|
||||||
|
<Component id="xrySelectFolderLabel" pref="380" max="32767" attributes="0"/>
|
||||||
|
<Group type="102" attributes="0">
|
||||||
|
<Component id="filePathTextField" max="32767" attributes="0"/>
|
||||||
|
<EmptySpace max="-2" attributes="0"/>
|
||||||
|
<Component id="fileBrowserButton" min="-2" max="-2" 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="xrySelectFolderLabel" min="-2" max="-2" attributes="0"/>
|
||||||
|
<EmptySpace max="-2" attributes="0"/>
|
||||||
|
<Group type="103" groupAlignment="3" attributes="0">
|
||||||
|
<Component id="filePathTextField" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||||
|
<Component id="fileBrowserButton" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||||
|
</Group>
|
||||||
|
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||||
|
<Component id="errorLabel" min="-2" max="-2" attributes="0"/>
|
||||||
|
<EmptySpace pref="235" max="32767" attributes="0"/>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
</DimensionLayout>
|
||||||
|
</Layout>
|
||||||
|
<SubComponents>
|
||||||
|
<Component class="javax.swing.JTextField" name="filePathTextField">
|
||||||
|
<Properties>
|
||||||
|
<Property name="editable" type="boolean" value="false"/>
|
||||||
|
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||||
|
<ResourceString bundle="org/sleuthkit/autopsy/datasourceprocessors/xry/Bundle.properties" key="XRYDataSourceProcessorConfigPanel.filePathTextField.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
</Component>
|
||||||
|
<Component class="javax.swing.JButton" name="fileBrowserButton">
|
||||||
|
<Properties>
|
||||||
|
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||||
|
<ResourceString bundle="org/sleuthkit/autopsy/datasourceprocessors/xry/Bundle.properties" key="XRYDataSourceProcessorConfigPanel.fileBrowserButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
<Events>
|
||||||
|
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="fileBrowserButtonActionPerformed"/>
|
||||||
|
</Events>
|
||||||
|
</Component>
|
||||||
|
<Component class="javax.swing.JLabel" name="xrySelectFolderLabel">
|
||||||
|
<Properties>
|
||||||
|
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||||
|
<ResourceString bundle="org/sleuthkit/autopsy/datasourceprocessors/xry/Bundle.properties" key="XRYDataSourceProcessorConfigPanel.xrySelectFolderLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
</Component>
|
||||||
|
<Component class="javax.swing.JLabel" name="errorLabel">
|
||||||
|
<Properties>
|
||||||
|
<Property name="foreground" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
|
||||||
|
<Color blue="0" green="0" red="ff" type="rgb"/>
|
||||||
|
</Property>
|
||||||
|
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||||
|
<ResourceString bundle="org/sleuthkit/autopsy/datasourceprocessors/xry/Bundle.properties" key="XRYDataSourceProcessorConfigPanel.errorLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
</Component>
|
||||||
|
</SubComponents>
|
||||||
|
</Form>
|
@ -0,0 +1,178 @@
|
|||||||
|
/*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2019 Basis Technology Corp.
|
||||||
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.sleuthkit.autopsy.datasourceprocessors.xry;
|
||||||
|
|
||||||
|
import java.beans.PropertyChangeListener;
|
||||||
|
import java.beans.PropertyChangeSupport;
|
||||||
|
import java.io.File;
|
||||||
|
import javax.swing.JFileChooser;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows an examiner to configure the XRY Data source processor.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
|
||||||
|
final class XRYDataSourceProcessorConfigPanel extends JPanel {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
private static final XRYDataSourceProcessorConfigPanel INSTANCE =
|
||||||
|
new XRYDataSourceProcessorConfigPanel();
|
||||||
|
|
||||||
|
//Communicates
|
||||||
|
private final PropertyChangeSupport pcs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates new form XRYDataSourceConfigPanel.
|
||||||
|
* Prevent direct instantiation.
|
||||||
|
*/
|
||||||
|
private XRYDataSourceProcessorConfigPanel() {
|
||||||
|
initComponents();
|
||||||
|
pcs = new PropertyChangeSupport(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the singleton XRYDataSourceProcessorConfigPanel.
|
||||||
|
*/
|
||||||
|
static XRYDataSourceProcessorConfigPanel getInstance() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the error label.
|
||||||
|
*/
|
||||||
|
void clearErrorText() {
|
||||||
|
errorLabel.setText(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the error label to show the supplied text.
|
||||||
|
*/
|
||||||
|
void setErrorText(String text) {
|
||||||
|
errorLabel.setText(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the selected file path.
|
||||||
|
*/
|
||||||
|
void clearSelectedFilePath() {
|
||||||
|
filePathTextField.setText(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the file path selected by the examiner.
|
||||||
|
*/
|
||||||
|
String getSelectedFilePath() {
|
||||||
|
return filePathTextField.getText();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a property change listener to this config panel.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public synchronized void addPropertyChangeListener(PropertyChangeListener pcl) {
|
||||||
|
super.addPropertyChangeListener(pcl);
|
||||||
|
pcs.addPropertyChangeListener(pcl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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() {
|
||||||
|
|
||||||
|
filePathTextField = new javax.swing.JTextField();
|
||||||
|
fileBrowserButton = new javax.swing.JButton();
|
||||||
|
xrySelectFolderLabel = new javax.swing.JLabel();
|
||||||
|
errorLabel = new javax.swing.JLabel();
|
||||||
|
|
||||||
|
filePathTextField.setEditable(false);
|
||||||
|
filePathTextField.setText(org.openide.util.NbBundle.getMessage(XRYDataSourceProcessorConfigPanel.class, "XRYDataSourceProcessorConfigPanel.filePathTextField.text")); // NOI18N
|
||||||
|
|
||||||
|
org.openide.awt.Mnemonics.setLocalizedText(fileBrowserButton, org.openide.util.NbBundle.getMessage(XRYDataSourceProcessorConfigPanel.class, "XRYDataSourceProcessorConfigPanel.fileBrowserButton.text")); // NOI18N
|
||||||
|
fileBrowserButton.addActionListener(new java.awt.event.ActionListener() {
|
||||||
|
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||||
|
fileBrowserButtonActionPerformed(evt);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
org.openide.awt.Mnemonics.setLocalizedText(xrySelectFolderLabel, org.openide.util.NbBundle.getMessage(XRYDataSourceProcessorConfigPanel.class, "XRYDataSourceProcessorConfigPanel.xrySelectFolderLabel.text")); // NOI18N
|
||||||
|
|
||||||
|
errorLabel.setForeground(new java.awt.Color(255, 0, 0));
|
||||||
|
org.openide.awt.Mnemonics.setLocalizedText(errorLabel, org.openide.util.NbBundle.getMessage(XRYDataSourceProcessorConfigPanel.class, "XRYDataSourceProcessorConfigPanel.errorLabel.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(errorLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||||
|
.addComponent(xrySelectFolderLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 380, Short.MAX_VALUE)
|
||||||
|
.addGroup(layout.createSequentialGroup()
|
||||||
|
.addComponent(filePathTextField)
|
||||||
|
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||||
|
.addComponent(fileBrowserButton)))
|
||||||
|
.addContainerGap())
|
||||||
|
);
|
||||||
|
layout.setVerticalGroup(
|
||||||
|
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||||
|
.addGroup(layout.createSequentialGroup()
|
||||||
|
.addContainerGap()
|
||||||
|
.addComponent(xrySelectFolderLabel)
|
||||||
|
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||||
|
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||||
|
.addComponent(filePathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||||
|
.addComponent(fileBrowserButton))
|
||||||
|
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
||||||
|
.addComponent(errorLabel)
|
||||||
|
.addContainerGap(235, Short.MAX_VALUE))
|
||||||
|
);
|
||||||
|
}// </editor-fold>//GEN-END:initComponents
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens a JFileChooser instance so that the examiner can select a XRY
|
||||||
|
* report folder.
|
||||||
|
*/
|
||||||
|
private void fileBrowserButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_fileBrowserButtonActionPerformed
|
||||||
|
JFileChooser fileChooser = new JFileChooser();
|
||||||
|
fileChooser.setMultiSelectionEnabled(false);
|
||||||
|
fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
|
||||||
|
int returnVal = fileChooser.showOpenDialog(this);
|
||||||
|
if (returnVal == JFileChooser.APPROVE_OPTION) {
|
||||||
|
File selection = fileChooser.getSelectedFile();
|
||||||
|
filePathTextField.setText(selection.getAbsolutePath());
|
||||||
|
|
||||||
|
//This will notify the wizard to revalidate the data source processor.
|
||||||
|
pcs.firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), false, true);
|
||||||
|
}
|
||||||
|
}//GEN-LAST:event_fileBrowserButtonActionPerformed
|
||||||
|
|
||||||
|
|
||||||
|
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||||
|
private javax.swing.JLabel errorLabel;
|
||||||
|
private javax.swing.JButton fileBrowserButton;
|
||||||
|
private javax.swing.JTextField filePathTextField;
|
||||||
|
private javax.swing.JLabel xrySelectFolderLabel;
|
||||||
|
// End of variables declaration//GEN-END:variables
|
||||||
|
}
|
@ -0,0 +1,191 @@
|
|||||||
|
/*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2019 Basis Technology Corp.
|
||||||
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.sleuthkit.autopsy.datasourceprocessors.xry;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
|
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||||
|
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||||
|
import org.sleuthkit.datamodel.Content;
|
||||||
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses XRY Device-General Information files and creates artifacts.
|
||||||
|
*/
|
||||||
|
final class XRYDeviceGenInfoFileParser implements XRYFileParser {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(XRYDeviceGenInfoFileParser.class.getName());
|
||||||
|
|
||||||
|
//Human readable name of this parser.
|
||||||
|
private static final String PARSER_NAME = "XRY DSP";
|
||||||
|
private static final char KEY_VALUE_DELIMITER = ':';
|
||||||
|
|
||||||
|
//All known XRY keys for Device Gen Info reports.
|
||||||
|
private static final String ATTRIBUTE_KEY = "attribute";
|
||||||
|
private static final String DATA_KEY = "data";
|
||||||
|
|
||||||
|
//All of the known XRY Attribute values for device gen info. The value of the
|
||||||
|
//attribute keys are actionable for this parser. See parse header for more
|
||||||
|
//details.
|
||||||
|
private static final Map<String, BlackboardAttribute.ATTRIBUTE_TYPE> KEY_TO_TYPE
|
||||||
|
= new HashMap<String, BlackboardAttribute.ATTRIBUTE_TYPE>() {
|
||||||
|
{
|
||||||
|
put("device name", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_NAME);
|
||||||
|
put("device family", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_MODEL);
|
||||||
|
put("device type", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_MAKE);
|
||||||
|
put("mobile id (imei)", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_IMEI);
|
||||||
|
put("security code", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PASSWORD);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Device-General Information reports have 2 key value pairs for every
|
||||||
|
* attribute. The two only known keys are "Data" and "Attribute", where data
|
||||||
|
* is some generic information that the Attribute key describes.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
*
|
||||||
|
* Data: Nokia XYZ
|
||||||
|
* Attribute: Device Name
|
||||||
|
*
|
||||||
|
* This parse implementation assumes that the data field does not span
|
||||||
|
* multiple lines. If the data does span multiple lines, it will log an
|
||||||
|
* error describing an expectation for an "Attribute" key that is not found.
|
||||||
|
*
|
||||||
|
* @param reader The XRYFileReader that reads XRY entities from the
|
||||||
|
* Device-General Information report.
|
||||||
|
* @param parent The parent Content to create artifacts from.
|
||||||
|
* @throws IOException If an I/O error is encountered during report reading
|
||||||
|
* @throws TskCoreException If an error during artifact creation is encountered.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void parse(XRYFileReader reader, Content parent) throws IOException, TskCoreException {
|
||||||
|
Path reportPath = reader.getReportPath();
|
||||||
|
logger.log(Level.INFO, String.format("[XRY DSP] Processing report at [ %s ]", reportPath.toString()));
|
||||||
|
|
||||||
|
while (reader.hasNextEntity()) {
|
||||||
|
String xryEntity = reader.nextEntity();
|
||||||
|
String[] xryLines = xryEntity.split("\n");
|
||||||
|
|
||||||
|
List<BlackboardAttribute> attributes = new ArrayList<>();
|
||||||
|
|
||||||
|
//First line of the entity is the title.
|
||||||
|
if (xryLines.length > 0) {
|
||||||
|
logger.log(Level.INFO, String.format("[XRY DSP] Processing [ %s ]", xryLines[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 1; i < xryLines.length; i++) {
|
||||||
|
String xryLine = xryLines[i];
|
||||||
|
|
||||||
|
//Expecting to see a "Data" key.
|
||||||
|
if (!hasDataKey(xryLine)) {
|
||||||
|
logger.log(Level.SEVERE, String.format("[XRY DSP] Expected a 'Data' key "
|
||||||
|
+ "on this line (in brackets) [ %s ], but none was found. "
|
||||||
|
+ "Discarding... Here is the previous line for context [ %s ]. "
|
||||||
|
+ "What does this mean?", xryLine, xryLines[i - 1]));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i + 1 == xryLines.length) {
|
||||||
|
logger.log(Level.SEVERE, String.format("[XRY DSP] Found a 'Data' key "
|
||||||
|
+ "but no corresponding 'Attribute' key. Discarding... Here "
|
||||||
|
+ "is the 'Data' line (in brackets) [ %s ]. Here is the previous "
|
||||||
|
+ "line for context [ %s ]. What does this mean?", xryLine, xryLines[i - 1]));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int dataKeyIndex = xryLine.indexOf(KEY_VALUE_DELIMITER);
|
||||||
|
String dataValue = xryLine.substring(dataKeyIndex + 1).trim();
|
||||||
|
|
||||||
|
String nextXryLine = xryLines[++i];
|
||||||
|
|
||||||
|
//Expecting to see an "Attribute" key
|
||||||
|
if (!hasAttributeKey(nextXryLine)) {
|
||||||
|
logger.log(Level.SEVERE, String.format("[XRY DSP] Expected an 'Attribute' "
|
||||||
|
+ "key on this line (in brackets) [ %s ], but none was found. "
|
||||||
|
+ "Discarding... Here is the previous line for context [ %s ]. "
|
||||||
|
+ "What does this mean?", nextXryLine, xryLine));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int attributeKeyIndex = nextXryLine.indexOf(KEY_VALUE_DELIMITER);
|
||||||
|
String attributeValue = nextXryLine.substring(attributeKeyIndex + 1).trim();
|
||||||
|
String normalizedAttributeValue = attributeValue.toLowerCase();
|
||||||
|
|
||||||
|
//Check if the attribute value is recognized.
|
||||||
|
if (KEY_TO_TYPE.containsKey(normalizedAttributeValue)) {
|
||||||
|
//All of the attribute types in the map expect a string.
|
||||||
|
attributes.add(new BlackboardAttribute(KEY_TO_TYPE.get(normalizedAttributeValue), PARSER_NAME, dataValue));
|
||||||
|
} else {
|
||||||
|
logger.log(Level.SEVERE, String.format("[XRY DSP] Attribute type (in brackets) "
|
||||||
|
+ "[ %s ] was not recognized. Discarding... Here is the "
|
||||||
|
+ "previous line for context [ %s ]. What does this mean?", nextXryLine, xryLine));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!attributes.isEmpty()) {
|
||||||
|
//Build the artifact.
|
||||||
|
BlackboardArtifact artifact = parent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_DEVICE_INFO);
|
||||||
|
artifact.addAttributes(attributes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the XRY line has a data key on it.
|
||||||
|
*
|
||||||
|
* @param xryLine
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private boolean hasDataKey(String xryLine) {
|
||||||
|
int dataKeyIndex = xryLine.indexOf(KEY_VALUE_DELIMITER);
|
||||||
|
//No key structure found.
|
||||||
|
if (dataKeyIndex == -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String normalizedDataKey = xryLine.substring(0,
|
||||||
|
dataKeyIndex).trim().toLowerCase();
|
||||||
|
return normalizedDataKey.equals(DATA_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the XRY line has an attribute key on it.
|
||||||
|
*
|
||||||
|
* @param xryLine
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private boolean hasAttributeKey(String xryLine) {
|
||||||
|
int attributeKeyIndex = xryLine.indexOf(KEY_VALUE_DELIMITER);
|
||||||
|
//No key structure found.
|
||||||
|
if (attributeKeyIndex == -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String normalizedDataKey = xryLine.substring(0,
|
||||||
|
attributeKeyIndex).trim().toLowerCase();
|
||||||
|
return normalizedDataKey.equals(ATTRIBUTE_KEY);
|
||||||
|
}
|
||||||
|
}
|
46
Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileParser.java
Executable file
@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2019 Basis Technology Corp.
|
||||||
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.sleuthkit.autopsy.datasourceprocessors.xry;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import org.sleuthkit.datamodel.Content;
|
||||||
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for XRY file parsing.
|
||||||
|
*/
|
||||||
|
interface XRYFileParser {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses XRY entities and creates artifacts from the interpreted content.
|
||||||
|
*
|
||||||
|
* See XRYFileReader for more information on XRY entities. It is expected
|
||||||
|
* that implementations will create artifacts on the supplied Content
|
||||||
|
* object.
|
||||||
|
*
|
||||||
|
* @param reader Produces XRY entities from a given XRY file.
|
||||||
|
* @param parent Content object that will act as the source of the
|
||||||
|
* artifacts.
|
||||||
|
* @throws IOException If an I/O error occurs during reading.
|
||||||
|
* @throws TskCoreException If an error occurs during artifact creation.
|
||||||
|
*/
|
||||||
|
void parse(XRYFileReader reader, Content parent) throws IOException, TskCoreException;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2019 Basis Technology Corp.
|
||||||
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.sleuthkit.autopsy.datasourceprocessors.xry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates XRYFileParsers by report type.
|
||||||
|
*/
|
||||||
|
final class XRYFileParserFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the correct implementation of a XRYFileParser for the specified
|
||||||
|
* report type.
|
||||||
|
*
|
||||||
|
* It is assumed that the report type is supported, which means the client
|
||||||
|
* needs to have tested with supports beforehand. Otherwise, an
|
||||||
|
* IllegalArgumentException is thrown.
|
||||||
|
*
|
||||||
|
* @param reportType A supported XRY report type.
|
||||||
|
* @return A XRYFileParser with defined behavior for the report type.
|
||||||
|
* @throws IllegalArgumentException if the report type is not supported or
|
||||||
|
* is null. This is a misuse of the API. It is assumed that the report type
|
||||||
|
* has been tested with the supports method.
|
||||||
|
*/
|
||||||
|
static XRYFileParser get(String reportType) {
|
||||||
|
if (reportType == null) {
|
||||||
|
throw new IllegalArgumentException("Report type cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (reportType.trim().toLowerCase()) {
|
||||||
|
case "calls":
|
||||||
|
return new XRYCallsFileParser();
|
||||||
|
case "contacts/contacts":
|
||||||
|
return new XRYContactsFileParser();
|
||||||
|
case "device/general information":
|
||||||
|
return new XRYDeviceGenInfoFileParser();
|
||||||
|
case "messages/sms":
|
||||||
|
return new XRYMessagesFileParser();
|
||||||
|
case "web/bookmarks":
|
||||||
|
return new XRYWebBookmarksFileParser();
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException(reportType + " not recognized.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests if a XRYFileParser implementation exists for the report type.
|
||||||
|
*
|
||||||
|
* @param reportType Report type to test.
|
||||||
|
* @return Indication if the report type can be parsed.
|
||||||
|
*/
|
||||||
|
static boolean supports(String reportType) {
|
||||||
|
try {
|
||||||
|
//Attempt a get.
|
||||||
|
get(reportType);
|
||||||
|
return true;
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Prevent direct instantiation
|
||||||
|
private XRYFileParserFactory() {
|
||||||
|
}
|
||||||
|
}
|
@ -45,36 +45,43 @@ import org.apache.commons.io.FilenameUtils;
|
|||||||
* From
|
* From
|
||||||
* Tel: 12345678
|
* Tel: 12345678
|
||||||
*/
|
*/
|
||||||
public final class XRYFileReader implements AutoCloseable {
|
final class XRYFileReader implements AutoCloseable {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(XRYFileReader.class.getName());
|
private static final Logger logger = Logger.getLogger(XRYFileReader.class.getName());
|
||||||
|
|
||||||
//Assume UTF_16LE
|
//Assume UTF_16LE
|
||||||
private static final Charset CHARSET = StandardCharsets.UTF_16LE;
|
private static final Charset CHARSET = StandardCharsets.UTF_16LE;
|
||||||
|
|
||||||
//Assume TXT extension
|
|
||||||
private static final String EXTENSION = "txt";
|
|
||||||
|
|
||||||
//Assume 0xFFFE is the BOM
|
|
||||||
private static final int[] BOM = {0xFF, 0xFE};
|
|
||||||
|
|
||||||
//Assume all XRY reports have the type on the 3rd line.
|
//Assume all XRY reports have the type on the 3rd line.
|
||||||
private static final int LINE_WITH_REPORT_TYPE = 3;
|
private static final int LINE_WITH_REPORT_TYPE = 3;
|
||||||
|
|
||||||
//Assume all headers are 5 lines in length.
|
//Assume all headers are 5 lines in length.
|
||||||
private static final int HEADER_LENGTH_IN_LINES = 5;
|
private static final int HEADER_LENGTH_IN_LINES = 5;
|
||||||
|
|
||||||
|
//Assume TXT extension
|
||||||
|
private static final String EXTENSION = "txt";
|
||||||
|
|
||||||
|
//Assume 0xFFFE is the BOM
|
||||||
|
private static final int[] BOM = {0xFF, 0xFE};
|
||||||
|
|
||||||
|
//Entity to be consumed during file iteration.
|
||||||
|
private final StringBuilder xryEntity;
|
||||||
|
|
||||||
//Underlying reader for the xry file.
|
//Underlying reader for the xry file.
|
||||||
private final BufferedReader reader;
|
private final BufferedReader reader;
|
||||||
|
|
||||||
private final StringBuilder xryEntity;
|
//Reference to the original xry file.
|
||||||
|
private final Path xryFilePath;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an XRYFileReader. As part of construction, the XRY file is opened
|
* Creates an XRYFileReader. As part of construction, the XRY file is opened
|
||||||
* and the reader is advanced past the header. This leaves the reader
|
* and the reader is advanced past the header. This leaves the reader
|
||||||
* positioned at the start of the first XRY entity.
|
* positioned at the start of the first XRY entity.
|
||||||
*
|
*
|
||||||
* The file is assumed to be encoded in UTF-16LE.
|
* The file is assumed to be encoded in UTF-16LE and is NOT verified to be
|
||||||
|
* an XRY file before reading. It is expected that the isXRYFile function
|
||||||
|
* has been called on the path beforehand. Otherwise, the behavior is
|
||||||
|
* undefined.
|
||||||
*
|
*
|
||||||
* @param xryFile XRY file to read. It is assumed that the caller has read
|
* @param xryFile XRY file to read. It is assumed that the caller has read
|
||||||
* access to the path.
|
* access to the path.
|
||||||
@ -82,6 +89,7 @@ public final class XRYFileReader implements AutoCloseable {
|
|||||||
*/
|
*/
|
||||||
public XRYFileReader(Path xryFile) throws IOException {
|
public XRYFileReader(Path xryFile) throws IOException {
|
||||||
reader = Files.newBufferedReader(xryFile, CHARSET);
|
reader = Files.newBufferedReader(xryFile, CHARSET);
|
||||||
|
xryFilePath = xryFile;
|
||||||
|
|
||||||
//Advance the reader to the start of the first XRY entity.
|
//Advance the reader to the start of the first XRY entity.
|
||||||
for (int i = 0; i < HEADER_LENGTH_IN_LINES; i++) {
|
for (int i = 0; i < HEADER_LENGTH_IN_LINES; i++) {
|
||||||
@ -91,6 +99,35 @@ public final class XRYFileReader implements AutoCloseable {
|
|||||||
xryEntity = new StringBuilder();
|
xryEntity = new StringBuilder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the report type from the XRY file.
|
||||||
|
*
|
||||||
|
* @return The XRY report type
|
||||||
|
* @throws IOException if an I/O error occurs.
|
||||||
|
* @throws IllegalArgumentExcepton If the XRY file does not have a report
|
||||||
|
* type. This is a misuse of the API. The validity of the Path should have
|
||||||
|
* been checked with isXRYFile before creating an XRYFileReader.
|
||||||
|
*/
|
||||||
|
public String getReportType() throws IOException {
|
||||||
|
Optional<String> reportType = getType(xryFilePath);
|
||||||
|
if (reportType.isPresent()) {
|
||||||
|
return reportType.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalArgumentException(xryFilePath.toString() + " does not "
|
||||||
|
+ "have a report type.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the raw path of the XRY report file.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public Path getReportPath() throws IOException {
|
||||||
|
return xryFilePath;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Advances the reader until a valid XRY entity is detected or EOF is
|
* Advances the reader until a valid XRY entity is detected or EOF is
|
||||||
* reached.
|
* reached.
|
||||||
@ -113,7 +150,7 @@ public final class XRYFileReader implements AutoCloseable {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
xryEntity.append(line).append("\n");
|
xryEntity.append(line).append('\n');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,6 +160,7 @@ public final class XRYFileReader implements AutoCloseable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an XRY entity if there is one, otherwise an exception is thrown.
|
* Returns an XRY entity if there is one, otherwise an exception is thrown.
|
||||||
|
* Clients should test for another entity by calling hasNextEntity().
|
||||||
*
|
*
|
||||||
* @return A non-empty XRY entity.
|
* @return A non-empty XRY entity.
|
||||||
* @throws IOException if an I/O error occurs.
|
* @throws IOException if an I/O error occurs.
|
||||||
@ -139,6 +177,23 @@ public final class XRYFileReader implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Peek at the next XRY entity without consuming it. If there are not more
|
||||||
|
* XRY entities left, an exception is thrown. Clients should test for
|
||||||
|
* another entity by calling hasNextEntity().
|
||||||
|
*
|
||||||
|
* @return A non-empty XRY entity.
|
||||||
|
* @throws IOException if an I/O error occurs.
|
||||||
|
* @throws NoSuchElementException if there are no more XRY entities to peek.
|
||||||
|
*/
|
||||||
|
public String peek() throws IOException {
|
||||||
|
if (hasNextEntity()) {
|
||||||
|
return xryEntity.toString();
|
||||||
|
} else {
|
||||||
|
throw new NoSuchElementException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes any file handles this reader may have open.
|
* Closes any file handles this reader may have open.
|
||||||
*
|
*
|
||||||
|
@ -24,18 +24,76 @@ import java.nio.file.Files;
|
|||||||
import java.nio.file.LinkOption;
|
import java.nio.file.LinkOption;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts XRY files and (optionally) non-XRY files from a XRY (Report) folder.
|
* Extracts XRY files and (optionally) non-XRY files from a XRY (Report) folder.
|
||||||
*/
|
*/
|
||||||
public final class XRYFolder {
|
final class XRYFolder {
|
||||||
|
|
||||||
//Depth that will contain XRY files. All XRY files will be immediate
|
//Depth that will contain XRY files. All XRY files will be immediate
|
||||||
//children of their parent folder.
|
//children of their parent folder.
|
||||||
private static final int XRY_FILES_DEPTH = 1;
|
private static final int XRY_FILES_DEPTH = 1;
|
||||||
|
|
||||||
|
//Raw path to the XRY folder.
|
||||||
|
private final Path xryFolderPath;
|
||||||
|
|
||||||
|
public XRYFolder(Path folder) {
|
||||||
|
xryFolderPath = folder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds all paths in the XRY report folder which are not XRY files. Only
|
||||||
|
* the first directory level is searched. As a result, some paths may point
|
||||||
|
* to directories.
|
||||||
|
*
|
||||||
|
* @return A non-null collection of paths
|
||||||
|
* @throws IOException If an I/O error occurs.
|
||||||
|
*/
|
||||||
|
public List<Path> getNonXRYFiles() throws IOException {
|
||||||
|
try (Stream<Path> allFiles = Files.walk(xryFolderPath, XRY_FILES_DEPTH)) {
|
||||||
|
List<Path> otherFiles = new ArrayList<>();
|
||||||
|
Iterator<Path> allFilesIterator = allFiles.iterator();
|
||||||
|
while (allFilesIterator.hasNext()) {
|
||||||
|
Path currentPath = allFilesIterator.next();
|
||||||
|
if (!currentPath.equals(xryFolderPath)
|
||||||
|
&& !XRYFileReader.isXRYFile(currentPath)) {
|
||||||
|
otherFiles.add(currentPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return otherFiles;
|
||||||
|
} catch (UncheckedIOException ex) {
|
||||||
|
throw ex.getCause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates XRYFileReader instances for all XRY files found in the top level
|
||||||
|
* of the folder.
|
||||||
|
*
|
||||||
|
* @return A non-null collection of file readers.
|
||||||
|
* @throws IOException If an I/O error occurs.
|
||||||
|
*/
|
||||||
|
public List<XRYFileReader> getXRYFileReaders() throws IOException {
|
||||||
|
try (Stream<Path> allFiles = Files.walk(xryFolderPath, XRY_FILES_DEPTH)) {
|
||||||
|
List<XRYFileReader> fileReaders = new ArrayList<>();
|
||||||
|
|
||||||
|
Iterator<Path> allFilesIterator = allFiles.iterator();
|
||||||
|
while (allFilesIterator.hasNext()) {
|
||||||
|
Path currentFile = allFilesIterator.next();
|
||||||
|
if (XRYFileReader.isXRYFile(currentFile)) {
|
||||||
|
fileReaders.add(new XRYFileReader(currentFile));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fileReaders;
|
||||||
|
} catch (UncheckedIOException ex) {
|
||||||
|
throw ex.getCause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Searches for XRY files at the top level of a given folder. If at least
|
* Searches for XRY files at the top level of a given folder. If at least
|
||||||
* one file matches, the entire directory is assumed to be an XRY report.
|
* one file matches, the entire directory is assumed to be an XRY report.
|
||||||
@ -48,7 +106,7 @@ public final class XRYFolder {
|
|||||||
* @return Indicates whether the Path is an XRY report.
|
* @return Indicates whether the Path is an XRY report.
|
||||||
*
|
*
|
||||||
* @throws IOException Error occurred during File I/O.
|
* @throws IOException Error occurred during File I/O.
|
||||||
* @throws SecurityException If the security manager denies access any of
|
* @throws SecurityException If the security manager denies access to any of
|
||||||
* the files.
|
* the files.
|
||||||
*/
|
*/
|
||||||
public static boolean isXRYFolder(Path folder) throws IOException {
|
public static boolean isXRYFolder(Path folder) throws IOException {
|
||||||
|
@ -0,0 +1,507 @@
|
|||||||
|
/*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2019 Basis Technology Corp.
|
||||||
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.sleuthkit.autopsy.datasourceprocessors.xry;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.time.format.DateTimeParseException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
|
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||||
|
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||||
|
import org.sleuthkit.datamodel.Content;
|
||||||
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses Messages-SMS files and creates artifacts.
|
||||||
|
*/
|
||||||
|
final class XRYMessagesFileParser implements XRYFileParser {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(
|
||||||
|
XRYMessagesFileParser.class.getName());
|
||||||
|
|
||||||
|
private static final String PARSER_NAME = "XRY DSP";
|
||||||
|
private static final char KEY_VALUE_DELIMITER = ':';
|
||||||
|
private static final DateTimeFormatter DATE_TIME_PARSER
|
||||||
|
= DateTimeFormatter.ofPattern("M/d/y h:m:s [a][ z]");
|
||||||
|
|
||||||
|
//Meta keys. These describe how the XRY message entites are split
|
||||||
|
//up in the report file.
|
||||||
|
private static final String SEGMENT_COUNT = "segments";
|
||||||
|
private static final String SEGMENT_NUMBER = "segment number";
|
||||||
|
private static final String REFERENCE_NUMBER = "reference number";
|
||||||
|
|
||||||
|
//A more readable version of these values. Referring to if the user
|
||||||
|
//has read the message.
|
||||||
|
private static final int READ = 1;
|
||||||
|
private static final int UNREAD = 0;
|
||||||
|
|
||||||
|
private static final String TEXT_KEY = "text";
|
||||||
|
|
||||||
|
//All known XRY keys for message reports.
|
||||||
|
private static final Set<String> XRY_KEYS = new HashSet<String>() {
|
||||||
|
{
|
||||||
|
add(TEXT_KEY);
|
||||||
|
add("direction");
|
||||||
|
add("time");
|
||||||
|
add("status");
|
||||||
|
add("tel");
|
||||||
|
add("storage");
|
||||||
|
add("index");
|
||||||
|
add("folder");
|
||||||
|
add("service center");
|
||||||
|
add("type");
|
||||||
|
add("name");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//All known XRY namespaces for message reports.
|
||||||
|
private static final Set<String> XRY_NAMESPACES = new HashSet<String>() {
|
||||||
|
{
|
||||||
|
add("to");
|
||||||
|
add("from");
|
||||||
|
add("participant");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//All known meta keys.
|
||||||
|
private static final Set<String> XRY_META_KEYS = new HashSet<String>() {
|
||||||
|
{
|
||||||
|
add(REFERENCE_NUMBER);
|
||||||
|
add(SEGMENT_NUMBER);
|
||||||
|
add(SEGMENT_COUNT);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message-SMS report artifacts can span multiple XRY entities and their
|
||||||
|
* attributes can span multiple lines. The "Text" key is the only known key
|
||||||
|
* value pair that can span multiple lines. Messages can be segmented,
|
||||||
|
* meaning that their "Text" content can appear in multiple XRY entities.
|
||||||
|
* Our goal for a segmented message is to aggregate all of the text pieces and
|
||||||
|
* create 1 artifact.
|
||||||
|
*
|
||||||
|
* This parse implementation assumes that segments are contiguous and that
|
||||||
|
* they ascend incrementally. There are checks in place to verify this
|
||||||
|
* assumption is correct, otherwise an error will appear in the logs.
|
||||||
|
*
|
||||||
|
* @param reader The XRYFileReader that reads XRY entities from the
|
||||||
|
* Message-SMS report.
|
||||||
|
* @param parent The parent Content to create artifacts from.
|
||||||
|
* @throws IOException If an I/O error is encountered during report reading
|
||||||
|
* @throws TskCoreException If an error during artifact creation is
|
||||||
|
* encountered.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void parse(XRYFileReader reader, Content parent) throws IOException, TskCoreException {
|
||||||
|
Path reportPath = reader.getReportPath();
|
||||||
|
logger.log(Level.INFO, String.format("[XRY DSP] Processing report at [ %s ]", reportPath.toString()));
|
||||||
|
|
||||||
|
//Keep track of the reference numbers that have been parsed.
|
||||||
|
Set<Integer> referenceNumbersSeen = new HashSet<>();
|
||||||
|
|
||||||
|
while (reader.hasNextEntity()) {
|
||||||
|
String xryEntity = reader.nextEntity();
|
||||||
|
String[] xryLines = xryEntity.split("\n");
|
||||||
|
|
||||||
|
//First line of the entity is the title.
|
||||||
|
if (xryLines.length > 0) {
|
||||||
|
logger.log(Level.INFO, String.format("[XRY DSP] Processing [ %s ]", xryLines[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<BlackboardAttribute> attributes = new ArrayList<>();
|
||||||
|
|
||||||
|
String namespace = "";
|
||||||
|
for (int i = 1; i < xryLines.length; i++) {
|
||||||
|
String xryLine = xryLines[i];
|
||||||
|
String candidateNamespace = xryLine.trim().toLowerCase();
|
||||||
|
|
||||||
|
if (XRY_NAMESPACES.contains(candidateNamespace)) {
|
||||||
|
namespace = xryLine.trim();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Find the XRY key on this line.
|
||||||
|
int keyDelimiter = xryLine.indexOf(KEY_VALUE_DELIMITER);
|
||||||
|
if (keyDelimiter == -1) {
|
||||||
|
logger.log(Level.SEVERE, String.format("[XRY DSP] Expected a key value "
|
||||||
|
+ "pair on this line (in brackets) [ %s ], but one was not detected."
|
||||||
|
+ " Is this the continuation of a previous line?"
|
||||||
|
+ " Here is the previous line (in brackets) [ %s ]. "
|
||||||
|
+ "What does this key mean?", xryLine, xryLines[i - 1]));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Extract the key value pair
|
||||||
|
String key = xryLine.substring(0, keyDelimiter).trim();
|
||||||
|
String value = xryLine.substring(keyDelimiter + 1).trim();
|
||||||
|
|
||||||
|
String normalizedKey = key.toLowerCase();
|
||||||
|
|
||||||
|
if (XRY_META_KEYS.contains(normalizedKey)) {
|
||||||
|
//Skip meta keys, they are being dealt with seperately.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!XRY_KEYS.contains(normalizedKey)) {
|
||||||
|
logger.log(Level.SEVERE, String.format("[XRY DSP] The following key, "
|
||||||
|
+ "value pair (in brackets, respectively) [ %s ], [ %s ] "
|
||||||
|
+ "was not recognized. Discarding... Here is the previous line "
|
||||||
|
+ "[ %s ] for context. What does this key mean?", key, value, xryLines[i - 1]));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.isEmpty()) {
|
||||||
|
logger.log(Level.SEVERE, String.format("[XRY DSP] The following key "
|
||||||
|
+ "(in brackets) [ %s ] was recognized, but the value "
|
||||||
|
+ "was empty. Discarding... Here is the previous line "
|
||||||
|
+ "for context [ %s ]. Is this a continuation of this line? "
|
||||||
|
+ "What does an empty key mean?", key, xryLines[i - 1]));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Assume text is the only field that can span multiple lines.
|
||||||
|
if (normalizedKey.equals(TEXT_KEY)) {
|
||||||
|
//Build up multiple lines.
|
||||||
|
for (; (i + 1) < xryLines.length
|
||||||
|
&& !hasKey(xryLines[i + 1])
|
||||||
|
&& !hasNamespace(xryLines[i + 1]); i++) {
|
||||||
|
String continuedValue = xryLines[i + 1].trim();
|
||||||
|
//Assume multi lined values are split by word.
|
||||||
|
value = value + " " + continuedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int referenceNumber = getMetaInfo(xryLines, REFERENCE_NUMBER);
|
||||||
|
//Check if there is any segmented text. Min val is used to
|
||||||
|
//signify that no reference number was found.
|
||||||
|
if (referenceNumber != Integer.MIN_VALUE) {
|
||||||
|
logger.log(Level.INFO, String.format("[XRY DSP] Message entity "
|
||||||
|
+ "appears to be segmented with reference number [ %d ]", referenceNumber));
|
||||||
|
|
||||||
|
if (referenceNumbersSeen.contains(referenceNumber)) {
|
||||||
|
logger.log(Level.SEVERE, String.format("[XRY DSP] This reference [ %d ] has already "
|
||||||
|
+ "been seen. This means that the segments are not "
|
||||||
|
+ "contiguous. Any segments contiguous with this "
|
||||||
|
+ "one will be aggregated and another "
|
||||||
|
+ "(otherwise duplicate) artifact will be created.", referenceNumber));
|
||||||
|
}
|
||||||
|
|
||||||
|
referenceNumbersSeen.add(referenceNumber);
|
||||||
|
|
||||||
|
int segmentNumber = getMetaInfo(xryLines, SEGMENT_NUMBER);
|
||||||
|
|
||||||
|
//Unify segmented text, if there is any.
|
||||||
|
String segmentedText = getSegmentedText(referenceNumber,
|
||||||
|
segmentNumber, reader);
|
||||||
|
//Assume it was segmented by word.
|
||||||
|
value = value + " " + segmentedText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BlackboardAttribute attribute = makeAttribute(namespace, key, value);
|
||||||
|
if (attribute != null) {
|
||||||
|
attributes.add(attribute);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Only create artifacts with non-empty attributes.
|
||||||
|
if(!attributes.isEmpty()) {
|
||||||
|
BlackboardArtifact artifact = parent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE);
|
||||||
|
artifact.addAttributes(attributes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds up segmented message entities so that the text is unified in the
|
||||||
|
* artifact.
|
||||||
|
*
|
||||||
|
* @param referenceNumber Reference number that messages are group by
|
||||||
|
* @param segmentNumber Segment number of the starting segment.
|
||||||
|
* @param reader
|
||||||
|
* @return
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
private String getSegmentedText(int referenceNumber, int segmentNumber, XRYFileReader reader) throws IOException {
|
||||||
|
StringBuilder segmentedText = new StringBuilder();
|
||||||
|
|
||||||
|
int currentSegmentNumber = segmentNumber;
|
||||||
|
while (reader.hasNextEntity()) {
|
||||||
|
//Peek at the next to see if it has the same reference number.
|
||||||
|
String nextEntity = reader.peek();
|
||||||
|
String[] nextEntityLines = nextEntity.split("\n");
|
||||||
|
int nextReferenceNumber = getMetaInfo(nextEntityLines, REFERENCE_NUMBER);
|
||||||
|
|
||||||
|
if (nextReferenceNumber != referenceNumber) {
|
||||||
|
//Don't consume the next entity. It is not related
|
||||||
|
//to the current message thread.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Consume the entity.
|
||||||
|
reader.nextEntity();
|
||||||
|
|
||||||
|
int nextSegmentNumber = getMetaInfo(nextEntityLines, SEGMENT_NUMBER);
|
||||||
|
|
||||||
|
//Extract the text key from the entity, which is potentially
|
||||||
|
//multi-lined.
|
||||||
|
if (nextEntityLines.length > 0) {
|
||||||
|
logger.log(Level.INFO, String.format("[XRY DSP] Processing [ %s ] "
|
||||||
|
+ "segment with reference number [ %d ]", nextEntityLines[0], referenceNumber));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(nextSegmentNumber == Integer.MIN_VALUE) {
|
||||||
|
logger.log(Level.SEVERE, String.format("[XRY DSP] Segment with reference"
|
||||||
|
+ " number [ %d ] did not have a segment number associated with it."
|
||||||
|
+ " It cannot be determined if the reconstructed text will be in order.", referenceNumber));
|
||||||
|
} else if (nextSegmentNumber != currentSegmentNumber + 1) {
|
||||||
|
logger.log(Level.SEVERE, String.format("[XRY DSP] Contiguous "
|
||||||
|
+ "segments are not ascending incrementally. Encountered "
|
||||||
|
+ "segment [ %d ] after segment [ %d ]. This means the reconstructed "
|
||||||
|
+ "text will be out of order.", nextSegmentNumber, currentSegmentNumber));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 1; i < nextEntityLines.length; i++) {
|
||||||
|
String xryLine = nextEntityLines[i];
|
||||||
|
//Find the XRY key on this line.
|
||||||
|
int keyDelimiter = xryLine.indexOf(KEY_VALUE_DELIMITER);
|
||||||
|
if (keyDelimiter == -1) {
|
||||||
|
//Skip this line, we are searching only for a text key-value pair.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String key = xryLine.substring(0, keyDelimiter);
|
||||||
|
String normalizedKey = key.trim().toLowerCase();
|
||||||
|
if (normalizedKey.equals(TEXT_KEY)) {
|
||||||
|
String value = xryLine.substring(keyDelimiter + 1).trim();
|
||||||
|
segmentedText.append(value).append(' ');
|
||||||
|
|
||||||
|
//Build up multiple lines.
|
||||||
|
for (; (i + 1) < nextEntityLines.length
|
||||||
|
&& !hasKey(nextEntityLines[i + 1])
|
||||||
|
&& !hasNamespace(nextEntityLines[i + 1]); i++) {
|
||||||
|
String continuedValue = nextEntityLines[i + 1].trim();
|
||||||
|
segmentedText.append(continuedValue).append(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentSegmentNumber = nextSegmentNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Remove the trailing space.
|
||||||
|
if (segmentedText.length() > 0) {
|
||||||
|
segmentedText.setLength(segmentedText.length() - 1);
|
||||||
|
}
|
||||||
|
return segmentedText.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the line has recognized key value on it.
|
||||||
|
*
|
||||||
|
* @param xryLine
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private boolean hasKey(String xryLine) {
|
||||||
|
int delimiter = xryLine.indexOf(':');
|
||||||
|
if (delimiter != -1) {
|
||||||
|
String key = xryLine.substring(0, delimiter);
|
||||||
|
String normalizedKey = key.trim().toLowerCase();
|
||||||
|
return XRY_KEYS.contains(normalizedKey);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the line is a recognized namespace.
|
||||||
|
*
|
||||||
|
* @param xryLine
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private boolean hasNamespace(String xryLine) {
|
||||||
|
String normalizedLine = xryLine.trim().toLowerCase();
|
||||||
|
return XRY_NAMESPACES.contains(normalizedLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts meta keys from the XRY entity. All of the known meta
|
||||||
|
* keys are integers and describe the message segments.
|
||||||
|
*
|
||||||
|
* @param xryLines Current XRY entity
|
||||||
|
* @param expectedKey The meta key to search for
|
||||||
|
* @return The interpreted integer value or Integer.MIN_VALUE if
|
||||||
|
* no meta key was found.
|
||||||
|
*/
|
||||||
|
private int getMetaInfo(String[] xryLines, String metaKey) {
|
||||||
|
for (int i = 0; i < xryLines.length; i++) {
|
||||||
|
String xryLine = xryLines[i];
|
||||||
|
|
||||||
|
String normalizedXryLine = xryLine.trim().toLowerCase();
|
||||||
|
int firstDelimiter = normalizedXryLine.indexOf(KEY_VALUE_DELIMITER);
|
||||||
|
if (firstDelimiter != -1) {
|
||||||
|
String key = normalizedXryLine.substring(0, firstDelimiter);
|
||||||
|
if (key.equals(metaKey)) {
|
||||||
|
String value = normalizedXryLine.substring(firstDelimiter + 1).trim();
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(value);
|
||||||
|
} catch (NumberFormatException ex) {
|
||||||
|
logger.log(Level.SEVERE, String.format("[XRY DSP] Value [ %s ] for "
|
||||||
|
+ "meta key [ %s ] was not an integer.", value, metaKey), ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Integer.MIN_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an attribute from the extracted key value pair.
|
||||||
|
*
|
||||||
|
* @param nameSpace The namespace of this key value pair.
|
||||||
|
* It will have been verified beforehand, otherwise it will be empty.
|
||||||
|
* @param key The key that was verified beforehand
|
||||||
|
* @param value The value associated with that key.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private BlackboardAttribute makeAttribute(String namespace, String key, String value) {
|
||||||
|
String normalizedKey = key.toLowerCase();
|
||||||
|
String normalizedNamespace = namespace.toLowerCase();
|
||||||
|
String normalizedValue = value.toLowerCase();
|
||||||
|
|
||||||
|
switch (normalizedKey) {
|
||||||
|
case "time":
|
||||||
|
//Tranform value to epoch ms
|
||||||
|
try {
|
||||||
|
String dateTime = removeDateTimeLocale(value);
|
||||||
|
String normalizedDateTime = dateTime.trim();
|
||||||
|
long dateTimeInEpoch = calculateSecondsSinceEpoch(normalizedDateTime);
|
||||||
|
return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME, PARSER_NAME, dateTimeInEpoch);
|
||||||
|
} catch (DateTimeParseException ex) {
|
||||||
|
logger.log(Level.SEVERE, String.format("[XRY DSP] Assumption "
|
||||||
|
+ "about the date time formatting of messages is not "
|
||||||
|
+ "right. Here is the value [ %s ].", value), ex);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
case "direction":
|
||||||
|
return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DIRECTION, PARSER_NAME, value);
|
||||||
|
case "text":
|
||||||
|
return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TEXT, PARSER_NAME, value);
|
||||||
|
case "status":
|
||||||
|
switch (normalizedValue) {
|
||||||
|
case "read":
|
||||||
|
return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_READ_STATUS, PARSER_NAME, READ);
|
||||||
|
case "unread":
|
||||||
|
return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_READ_STATUS, PARSER_NAME, UNREAD);
|
||||||
|
case "sending failed":
|
||||||
|
//Ignore for now.
|
||||||
|
return null;
|
||||||
|
case "deleted":
|
||||||
|
//Ignore for now.
|
||||||
|
return null;
|
||||||
|
case "unsent":
|
||||||
|
//Ignore for now.
|
||||||
|
return null;
|
||||||
|
default:
|
||||||
|
logger.log(Level.SEVERE, String.format("[XRY DSP] Unrecognized "
|
||||||
|
+ "status value [ %s ].", value));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
case "type":
|
||||||
|
switch (normalizedValue) {
|
||||||
|
case "deliver":
|
||||||
|
//Ignore for now.
|
||||||
|
return null;
|
||||||
|
case "submit":
|
||||||
|
//Ignore for now.
|
||||||
|
return null;
|
||||||
|
case "status report":
|
||||||
|
//Ignore for now.
|
||||||
|
return null;
|
||||||
|
default:
|
||||||
|
logger.log(Level.SEVERE, String.format("[XRY DSP] Unrecognized "
|
||||||
|
+ "type value [ %s ]", value));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
case "storage":
|
||||||
|
//Ignore for now.
|
||||||
|
return null;
|
||||||
|
case "index":
|
||||||
|
//Ignore for now.
|
||||||
|
return null;
|
||||||
|
case "folder":
|
||||||
|
//Ignore for now.
|
||||||
|
return null;
|
||||||
|
case "name":
|
||||||
|
//Ignore for now.
|
||||||
|
return null;
|
||||||
|
case "service center":
|
||||||
|
//Ignore for now.
|
||||||
|
return null;
|
||||||
|
case "tel":
|
||||||
|
//Apply the namespace
|
||||||
|
if (normalizedNamespace.equals("from")) {
|
||||||
|
return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM, PARSER_NAME, value);
|
||||||
|
} else {
|
||||||
|
//Assume to and participant are both equivalent to TSK_PHONE_NUMBER_TO
|
||||||
|
return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO, PARSER_NAME, value);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException(String.format("key [ %s ] was not recognized.", key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the locale from the date time value.
|
||||||
|
*
|
||||||
|
* Locale in this case being (Device) or (Network).
|
||||||
|
*
|
||||||
|
* @param dateTime XRY datetime value to be sanitized.
|
||||||
|
* @return A purer date time value.
|
||||||
|
*/
|
||||||
|
private String removeDateTimeLocale(String dateTime) {
|
||||||
|
int index = dateTime.indexOf('(');
|
||||||
|
if (index == -1) {
|
||||||
|
return dateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dateTime.substring(0, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the date time value and calculates ms since epoch. The time zone is
|
||||||
|
* assumed to be UTC.
|
||||||
|
*
|
||||||
|
* @param dateTime
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private long calculateSecondsSinceEpoch(String dateTime) {
|
||||||
|
LocalDateTime localDateTime = LocalDateTime.parse(dateTime, DATE_TIME_PARSER);
|
||||||
|
//Assume dates have no offset.
|
||||||
|
return localDateTime.toInstant(ZoneOffset.UTC).getEpochSecond();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2019 Basis Technology Corp.
|
||||||
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.sleuthkit.autopsy.datasourceprocessors.xry;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
|
import org.sleuthkit.datamodel.Content;
|
||||||
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes all XRY files in an XRY folder.
|
||||||
|
*/
|
||||||
|
final class XRYReportProcessor {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(XRYReportProcessor.class.getName());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes all XRY Files and creates artifacts on the given Content
|
||||||
|
* instance.
|
||||||
|
*
|
||||||
|
* All resources will be closed if an exception is encountered.
|
||||||
|
*
|
||||||
|
* @param folder XRY folder to process
|
||||||
|
* @param parent Content instance to hold newly created artifacts.
|
||||||
|
* @throws IOException If an I/O exception occurs.
|
||||||
|
* @throws TskCoreException If an error occurs adding artifacts.
|
||||||
|
*/
|
||||||
|
static void process(XRYFolder folder, Content parent) throws IOException, TskCoreException {
|
||||||
|
//Get all XRY file readers from this folder.
|
||||||
|
List<XRYFileReader> xryFileReaders = folder.getXRYFileReaders();
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (XRYFileReader xryFileReader : xryFileReaders) {
|
||||||
|
String reportType = xryFileReader.getReportType();
|
||||||
|
if (XRYFileParserFactory.supports(reportType)) {
|
||||||
|
XRYFileParser parser = XRYFileParserFactory.get(reportType);
|
||||||
|
parser.parse(xryFileReader, parent);
|
||||||
|
} else {
|
||||||
|
logger.log(Level.SEVERE, String.format("[XRY DSP] XRY File (in brackets) "
|
||||||
|
+ "[ %s ] was found, but no parser to support its report type exists. "
|
||||||
|
+ "Report type is [ %s ]", xryFileReader.getReportPath().toString(), reportType));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
//Try to close all resources
|
||||||
|
for (XRYFileReader xryFileReader : xryFileReaders) {
|
||||||
|
xryFileReader.close();
|
||||||
|
}
|
||||||
|
} catch (IOException ex) {
|
||||||
|
logger.log(Level.WARNING, "[XRY DSP] Encountered I/O exception trying "
|
||||||
|
+ "to close all xry file readers.", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Prevent direct instantiation.
|
||||||
|
private XRYReportProcessor() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2019 Basis Technology Corp.
|
||||||
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.sleuthkit.autopsy.datasourceprocessors.xry;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||||
|
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||||
|
import org.sleuthkit.datamodel.Content;
|
||||||
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses XRY Web-Bookmark files and creates artifacts.
|
||||||
|
*/
|
||||||
|
final class XRYWebBookmarksFileParser extends AbstractSingleKeyValueParser {
|
||||||
|
|
||||||
|
//All known XRY keys for web bookmarks.
|
||||||
|
private static final Map<String, BlackboardAttribute.ATTRIBUTE_TYPE> KEY_TO_TYPE
|
||||||
|
= new HashMap<String, BlackboardAttribute.ATTRIBUTE_TYPE>() {
|
||||||
|
{
|
||||||
|
put("web address", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL);
|
||||||
|
put("domain", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean isKey(String key) {
|
||||||
|
String normalizedKey = key.toLowerCase();
|
||||||
|
return KEY_TO_TYPE.containsKey(normalizedKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean isNamespace(String nameSpace) {
|
||||||
|
//No known namespaces for web reports.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
BlackboardAttribute makeAttribute(String nameSpace, String key, String value) {
|
||||||
|
String normalizedKey = key.toLowerCase();
|
||||||
|
return new BlackboardAttribute(KEY_TO_TYPE.get(normalizedKey), PARSER_NAME, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void makeArtifact(List<BlackboardAttribute> attributes, Content parent) throws TskCoreException {
|
||||||
|
BlackboardArtifact artifact = parent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_BOOKMARK);
|
||||||
|
artifact.addAttributes(attributes);
|
||||||
|
}
|
||||||
|
}
|
@ -7,3 +7,26 @@ RefreshPanel.closeButton.text=
|
|||||||
MapPanel.cordLabel.text=
|
MapPanel.cordLabel.text=
|
||||||
WaypointDetailPanel.closeButton.text=
|
WaypointDetailPanel.closeButton.text=
|
||||||
WaypointDetailPanel.imageLabel.text=
|
WaypointDetailPanel.imageLabel.text=
|
||||||
|
GeoFilterPanel.waypointSettings.border.title=
|
||||||
|
GeoFilterPanel.allButton.text=Show All
|
||||||
|
GeoFilterPanel.mostRecentButton.text=Show only last
|
||||||
|
GeoFilterPanel.applyButton.text=Apply
|
||||||
|
GeoFilterPanel.showWaypointsWOTSCheckBox.text=Include waypoints with no time stamps
|
||||||
|
GeoFilterPanel.daysLabel.text=days of activity
|
||||||
|
CheckBoxListPanel.titleLabel.text=jLabel1
|
||||||
|
CheckBoxListPanel.checkButton.text=Check All
|
||||||
|
CheckBoxListPanel.uncheckButton.text=Uncheck All
|
||||||
|
GeoFilterPanel.optionsLabel.text=Waypoints
|
||||||
|
OptionsCategory_Name_Geolocation=Geolocation
|
||||||
|
OptionsCategory_Keywords_Geolocation=Geolocation Settings
|
||||||
|
GeolocationSettingsPanel.tilePane.border.title=Map Tile Source
|
||||||
|
OptionsCategory_Keywords_Geolocation=Geolocation
|
||||||
|
OptionsCategory_Name_Geolocation=Geolocation
|
||||||
|
GeolocationSettingsPanel.defaultButton.text=Default online tile server (bing.com/maps)
|
||||||
|
GeolocationSettingsPanel.tileServerButton.text=OpenStreetMap tile server
|
||||||
|
GeolocationSettingsPanel.tileServerFiled.text=
|
||||||
|
GeolocationSettingsPanel.osmZipButton.text=OpenStreeMap Tile Zip File
|
||||||
|
GeolocationSettingsPanel.osmZipFileField.text=
|
||||||
|
GeolocationSettingsPanel.osmZipFileBrowseButton.text=Browse
|
||||||
|
GeolocationSettingsPanel.serverTestButton.text=Test
|
||||||
|
GeolocationSettingsPanel.osmZipButton.actionCommand=OpenStreeMap tile ZIP file
|
||||||
|
@ -1,8 +1,27 @@
|
|||||||
CTL_OpenGeolocation=Geolocation
|
CTL_OpenGeolocation=Geolocation
|
||||||
CTL_GeolocationTopComponentAction=GeolocationTopComponent
|
CTL_GeolocationTopComponentAction=GeolocationTopComponent
|
||||||
CTL_GeolocationTopComponent=Geolocation
|
CTL_GeolocationTopComponent=Geolocation
|
||||||
|
GeoFilterPanel_DataSource_List_Title=Data Sources
|
||||||
|
GeoFilterPanel_empty_dataSource=Data Source list is empty.
|
||||||
|
GeolocationSettingsPanel_malformed_url_message=The supplies OSM tile server address is invalid.\nPlease supply a well formed url prefixed with http://
|
||||||
|
GeolocationSettingsPanel_malformed_url_message_tile=Malformed URL
|
||||||
|
GeolocationSettingsPanel_osm_server_test_fail_message=OSM tile server test failed.\nUnable to connect to server.
|
||||||
|
GeolocationSettingsPanel_osm_server_test_fail_message_title=Error
|
||||||
|
GeolocationSettingsPanel_osm_server_test_success_message=The provide OSM tile server address is valid.
|
||||||
|
GeolocationSettingsPanel_osm_server_test_success_message_title=Success
|
||||||
|
GeolocationTC_connection_failure_message=Failed to connect to map title source.\nPlease review map source in Options dialog.
|
||||||
|
GeolocationTC_connection_failure_message_title=Connection Failure
|
||||||
|
GeoTopComponent_filer_data_invalid_msg=Unable to run waypoint filter.\nPlease select one or more data sources.
|
||||||
|
GeoTopComponent_filer_data_invalid_Title=Filter Failure
|
||||||
|
GeoTopComponent_filter_exception_msg=Exception occured during waypoint filtering.
|
||||||
|
GeoTopComponent_filter_exception_Title=Filter Failure
|
||||||
|
GeoTopComponent_no_waypoints_returned_mgs=Applied filter failed to find waypoints that matched criteria.\nRevise filter options and try again.
|
||||||
|
GeoTopComponent_no_waypoints_returned_Title=No Waypoints Found
|
||||||
GLTopComponent_initilzation_error=An error occurred during waypoint initilization. Geolocation data maybe incomplete.
|
GLTopComponent_initilzation_error=An error occurred during waypoint initilization. Geolocation data maybe incomplete.
|
||||||
GLTopComponent_name=Geolocation
|
GLTopComponent_name=Geolocation
|
||||||
|
HidingPane_default_title=Filters
|
||||||
|
MapPanel_connection_failure_message=Failed to connect to new geolocation map tile source.
|
||||||
|
MapPanel_connection_failure_message_title=Connection Failure
|
||||||
MayWaypoint_ExternalViewer_label=Open in ExternalViewer
|
MayWaypoint_ExternalViewer_label=Open in ExternalViewer
|
||||||
OpenGeolocationAction_displayName=Geolocation
|
OpenGeolocationAction_displayName=Geolocation
|
||||||
OpenGeolocationAction_name=Geolocation
|
OpenGeolocationAction_name=Geolocation
|
||||||
@ -12,4 +31,27 @@ RefreshPanel.closeButton.text=
|
|||||||
MapPanel.cordLabel.text=
|
MapPanel.cordLabel.text=
|
||||||
WaypointDetailPanel.closeButton.text=
|
WaypointDetailPanel.closeButton.text=
|
||||||
WaypointDetailPanel.imageLabel.text=
|
WaypointDetailPanel.imageLabel.text=
|
||||||
|
GeoFilterPanel.waypointSettings.border.title=
|
||||||
|
GeoFilterPanel.allButton.text=Show All
|
||||||
|
GeoFilterPanel.mostRecentButton.text=Show only last
|
||||||
|
GeoFilterPanel.applyButton.text=Apply
|
||||||
|
GeoFilterPanel.showWaypointsWOTSCheckBox.text=Include waypoints with no time stamps
|
||||||
|
GeoFilterPanel.daysLabel.text=days of activity
|
||||||
|
CheckBoxListPanel.titleLabel.text=jLabel1
|
||||||
|
CheckBoxListPanel.checkButton.text=Check All
|
||||||
|
CheckBoxListPanel.uncheckButton.text=Uncheck All
|
||||||
|
GeoFilterPanel.optionsLabel.text=Waypoints
|
||||||
|
OptionsCategory_Name_Geolocation=Geolocation
|
||||||
|
OptionsCategory_Keywords_Geolocation=Geolocation Settings
|
||||||
|
GeolocationSettingsPanel.tilePane.border.title=Map Tile Source
|
||||||
|
OptionsCategory_Keywords_Geolocation=Geolocation
|
||||||
|
OptionsCategory_Name_Geolocation=Geolocation
|
||||||
|
GeolocationSettingsPanel.defaultButton.text=Default online tile server (bing.com/maps)
|
||||||
|
GeolocationSettingsPanel.tileServerButton.text=OpenStreetMap tile server
|
||||||
|
GeolocationSettingsPanel.tileServerFiled.text=
|
||||||
|
GeolocationSettingsPanel.osmZipButton.text=OpenStreeMap Tile Zip File
|
||||||
|
GeolocationSettingsPanel.osmZipFileField.text=
|
||||||
|
GeolocationSettingsPanel.osmZipFileBrowseButton.text=Browse
|
||||||
|
GeolocationSettingsPanel.serverTestButton.text=Test
|
||||||
|
GeolocationSettingsPanel.osmZipButton.actionCommand=OpenStreeMap tile ZIP file
|
||||||
WaypointExtractAction_label=Extract Files(s)
|
WaypointExtractAction_label=Extract Files(s)
|
||||||
|
109
Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxJList.java
Executable file
@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2019 Basis Technology Corp.
|
||||||
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.sleuthkit.autopsy.geolocation;
|
||||||
|
|
||||||
|
import java.awt.Component;
|
||||||
|
import java.awt.event.MouseAdapter;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import javax.swing.JCheckBox;
|
||||||
|
import javax.swing.JList;
|
||||||
|
import javax.swing.ListCellRenderer;
|
||||||
|
import javax.swing.ListSelectionModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A JList that renders the list items as check boxes.
|
||||||
|
*/
|
||||||
|
final class CheckBoxJList<T extends CheckBoxJList.CheckboxListItem> extends JList<T> {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple interface that must be implement for an object to be displayed as
|
||||||
|
* a checkbox in CheckBoxJList.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
interface CheckboxListItem {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the checkbox state.
|
||||||
|
*
|
||||||
|
* @return True if the check box should be checked
|
||||||
|
*/
|
||||||
|
boolean isChecked();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the state of the check box.
|
||||||
|
*
|
||||||
|
* @param checked
|
||||||
|
*/
|
||||||
|
void setChecked(boolean checked);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns String to display as the check box label
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
String getDisplayName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new JCheckBoxList.
|
||||||
|
*/
|
||||||
|
CheckBoxJList() {
|
||||||
|
initalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do all of the UI initialization.
|
||||||
|
*/
|
||||||
|
private void initalize() {
|
||||||
|
setCellRenderer(new CellRenderer());
|
||||||
|
addMouseListener(new MouseAdapter() {
|
||||||
|
@Override
|
||||||
|
public void mousePressed(MouseEvent e) {
|
||||||
|
int index = locationToIndex(e.getPoint());
|
||||||
|
if (index != -1) {
|
||||||
|
CheckBoxJList.CheckboxListItem element = getModel().getElementAt(index);
|
||||||
|
element.setChecked(!element.isChecked());
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A ListCellRenderer that renders list elements as check boxes.
|
||||||
|
*/
|
||||||
|
class CellRenderer extends JCheckBox implements ListCellRenderer<CheckBoxJList.CheckboxListItem> {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Component getListCellRendererComponent(
|
||||||
|
JList<? extends CheckBoxJList.CheckboxListItem> list, CheckBoxJList.CheckboxListItem value, int index,
|
||||||
|
boolean isSelected, boolean cellHasFocus) {
|
||||||
|
|
||||||
|
setBackground(list.getBackground());
|
||||||
|
setSelected(value.isChecked());
|
||||||
|
setText(value.getDisplayName());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
79
Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.form
Executable file
@ -0,0 +1,79 @@
|
|||||||
|
<?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"/>
|
||||||
|
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,44,0,0,1,-112"/>
|
||||||
|
</AuxValues>
|
||||||
|
|
||||||
|
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
|
||||||
|
<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/geolocation/Bundle.properties" key="CheckBoxListPanel.titleLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
|
</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="3" gridHeight="1" fill="0" 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.JButton" name="uncheckButton">
|
||||||
|
<Properties>
|
||||||
|
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||||
|
<ResourceString bundle="org/sleuthkit/autopsy/geolocation/Bundle.properties" key="CheckBoxListPanel.uncheckButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
<Events>
|
||||||
|
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="uncheckButtonActionPerformed"/>
|
||||||
|
</Events>
|
||||||
|
<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.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||||
|
<GridBagConstraints gridX="1" gridY="2" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="9" anchor="12" weightX="1.0" weightY="0.0"/>
|
||||||
|
</Constraint>
|
||||||
|
</Constraints>
|
||||||
|
</Component>
|
||||||
|
<Component class="javax.swing.JButton" name="checkButton">
|
||||||
|
<Properties>
|
||||||
|
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||||
|
<ResourceString bundle="org/sleuthkit/autopsy/geolocation/Bundle.properties" key="CheckBoxListPanel.checkButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
<Events>
|
||||||
|
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="checkButtonActionPerformed"/>
|
||||||
|
</Events>
|
||||||
|
<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.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||||
|
<GridBagConstraints gridX="2" gridY="2" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="12" weightX="0.0" weightY="0.0"/>
|
||||||
|
</Constraint>
|
||||||
|
</Constraints>
|
||||||
|
</Component>
|
||||||
|
<Container class="javax.swing.JScrollPane" name="scrollPane">
|
||||||
|
<Constraints>
|
||||||
|
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||||
|
<GridBagConstraints gridX="0" gridY="1" gridWidth="3" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="5" insetsLeft="0" insetsBottom="9" insetsRight="0" anchor="10" weightX="1.0" weightY="1.0"/>
|
||||||
|
</Constraint>
|
||||||
|
</Constraints>
|
||||||
|
|
||||||
|
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
|
||||||
|
</Container>
|
||||||
|
</SubComponents>
|
||||||
|
</Form>
|
239
Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.java
Executable file
@ -0,0 +1,239 @@
|
|||||||
|
/*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2019 Basis Technology Corp.
|
||||||
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.sleuthkit.autopsy.geolocation;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.swing.DefaultListModel;
|
||||||
|
import javax.swing.Icon;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A panel for showing Content objects in a check box list.
|
||||||
|
*/
|
||||||
|
final class CheckBoxListPanel<T> extends javax.swing.JPanel {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private final DefaultListModel<ObjectCheckBox<T>> model = new DefaultListModel<>();
|
||||||
|
private final CheckBoxJList<ObjectCheckBox<T>> checkboxList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates new CheckboxFilterPanel
|
||||||
|
*/
|
||||||
|
CheckBoxListPanel() {
|
||||||
|
initComponents();
|
||||||
|
|
||||||
|
checkboxList = new CheckBoxJList<>();
|
||||||
|
checkboxList.setModel(model);
|
||||||
|
scrollPane.setViewportView(checkboxList);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new element to the check box list.
|
||||||
|
*
|
||||||
|
* @param displayName display name for the checkbox
|
||||||
|
* @param obj Object that the checkbox represents
|
||||||
|
*/
|
||||||
|
void addElement(String displayName, T obj) {
|
||||||
|
model.addElement(new ObjectCheckBox<>(displayName, true, obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove all objects from the checkbox list.
|
||||||
|
*/
|
||||||
|
void clearList() {
|
||||||
|
model.removeAllElements();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of all of the selected elements.
|
||||||
|
*
|
||||||
|
* @return List of selected elements.
|
||||||
|
*/
|
||||||
|
List<T> getSelectedElements() {
|
||||||
|
List<T> selectedElements = new ArrayList<>();
|
||||||
|
Enumeration<ObjectCheckBox<T>> elements = model.elements();
|
||||||
|
|
||||||
|
while (elements.hasMoreElements()) {
|
||||||
|
ObjectCheckBox<T> element = elements.nextElement();
|
||||||
|
if (element.isChecked()) {
|
||||||
|
selectedElements.add(element.getObject());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return selectedElements;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the selection state of the all the check boxes in the list.
|
||||||
|
*
|
||||||
|
* @param selected True to check the boxes, false to unchecked
|
||||||
|
*/
|
||||||
|
void setSetAllSelected(boolean selected) {
|
||||||
|
Enumeration<ObjectCheckBox<T>> enumeration = model.elements();
|
||||||
|
while (enumeration.hasMoreElements()) {
|
||||||
|
ObjectCheckBox<T> element = enumeration.nextElement();
|
||||||
|
element.setChecked(selected);
|
||||||
|
checkboxList.repaint();
|
||||||
|
checkboxList.revalidate();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the panel title.
|
||||||
|
*
|
||||||
|
* @param title Panel title or null for no title.
|
||||||
|
*/
|
||||||
|
void setPanelTitle(String title) {
|
||||||
|
titleLabel.setText(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the panel title icon.
|
||||||
|
*
|
||||||
|
* @param icon Icon to set or null for no icon
|
||||||
|
*/
|
||||||
|
void setPanelTitleIcon(Icon icon) {
|
||||||
|
titleLabel.setIcon(icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
titleLabel = new javax.swing.JLabel();
|
||||||
|
javax.swing.JButton uncheckButton = new javax.swing.JButton();
|
||||||
|
javax.swing.JButton checkButton = new javax.swing.JButton();
|
||||||
|
scrollPane = new javax.swing.JScrollPane();
|
||||||
|
|
||||||
|
setLayout(new java.awt.GridBagLayout());
|
||||||
|
|
||||||
|
org.openide.awt.Mnemonics.setLocalizedText(titleLabel, org.openide.util.NbBundle.getMessage(CheckBoxListPanel.class, "CheckBoxListPanel.titleLabel.text")); // NOI18N
|
||||||
|
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||||
|
gridBagConstraints.gridx = 0;
|
||||||
|
gridBagConstraints.gridy = 0;
|
||||||
|
gridBagConstraints.gridwidth = 3;
|
||||||
|
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
|
||||||
|
add(titleLabel, gridBagConstraints);
|
||||||
|
|
||||||
|
org.openide.awt.Mnemonics.setLocalizedText(uncheckButton, org.openide.util.NbBundle.getMessage(CheckBoxListPanel.class, "CheckBoxListPanel.uncheckButton.text")); // NOI18N
|
||||||
|
uncheckButton.addActionListener(new java.awt.event.ActionListener() {
|
||||||
|
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||||
|
uncheckButtonActionPerformed(evt);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||||
|
gridBagConstraints.gridx = 1;
|
||||||
|
gridBagConstraints.gridy = 2;
|
||||||
|
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHEAST;
|
||||||
|
gridBagConstraints.weightx = 1.0;
|
||||||
|
gridBagConstraints.insets = new java.awt.Insets(0, 0, 0, 9);
|
||||||
|
add(uncheckButton, gridBagConstraints);
|
||||||
|
|
||||||
|
org.openide.awt.Mnemonics.setLocalizedText(checkButton, org.openide.util.NbBundle.getMessage(CheckBoxListPanel.class, "CheckBoxListPanel.checkButton.text")); // NOI18N
|
||||||
|
checkButton.addActionListener(new java.awt.event.ActionListener() {
|
||||||
|
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||||
|
checkButtonActionPerformed(evt);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||||
|
gridBagConstraints.gridx = 2;
|
||||||
|
gridBagConstraints.gridy = 2;
|
||||||
|
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHEAST;
|
||||||
|
add(checkButton, gridBagConstraints);
|
||||||
|
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||||
|
gridBagConstraints.gridx = 0;
|
||||||
|
gridBagConstraints.gridy = 1;
|
||||||
|
gridBagConstraints.gridwidth = 3;
|
||||||
|
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
|
||||||
|
gridBagConstraints.weightx = 1.0;
|
||||||
|
gridBagConstraints.weighty = 1.0;
|
||||||
|
gridBagConstraints.insets = new java.awt.Insets(5, 0, 9, 0);
|
||||||
|
add(scrollPane, gridBagConstraints);
|
||||||
|
}// </editor-fold>//GEN-END:initComponents
|
||||||
|
|
||||||
|
private void uncheckButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_uncheckButtonActionPerformed
|
||||||
|
setSetAllSelected(false);
|
||||||
|
}//GEN-LAST:event_uncheckButtonActionPerformed
|
||||||
|
|
||||||
|
private void checkButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_checkButtonActionPerformed
|
||||||
|
setSetAllSelected(true);
|
||||||
|
}//GEN-LAST:event_checkButtonActionPerformed
|
||||||
|
|
||||||
|
|
||||||
|
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||||
|
private javax.swing.JScrollPane scrollPane;
|
||||||
|
private javax.swing.JLabel titleLabel;
|
||||||
|
// End of variables declaration//GEN-END:variables
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper around T that implements CheckboxListItem
|
||||||
|
*
|
||||||
|
* @param <T>
|
||||||
|
*/
|
||||||
|
final class ObjectCheckBox<T> implements CheckBoxJList.CheckboxListItem {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private final T object;
|
||||||
|
private final String displayName;
|
||||||
|
private boolean checked;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new ObjectCheckBox
|
||||||
|
*
|
||||||
|
* @param displayName String to show as the check box label
|
||||||
|
* @param initialState Sets the initial state of the check box
|
||||||
|
* @param object Object that the check box represents.
|
||||||
|
*/
|
||||||
|
ObjectCheckBox(String displayName, boolean initialState, T object) {
|
||||||
|
this.displayName = displayName;
|
||||||
|
this.object = object;
|
||||||
|
this.checked = initialState;
|
||||||
|
}
|
||||||
|
|
||||||
|
T getObject() {
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isChecked() {
|
||||||
|
return checked;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setChecked(boolean checked) {
|
||||||
|
this.checked = checked;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayName() {
|
||||||
|
return displayName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
180
Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.form
Executable file
@ -0,0 +1,180 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
|
||||||
|
<Form version="1.4" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
|
||||||
|
<NonVisualComponents>
|
||||||
|
<Component class="javax.swing.ButtonGroup" name="buttonGroup">
|
||||||
|
<AuxValues>
|
||||||
|
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
|
||||||
|
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
|
||||||
|
</AuxValues>
|
||||||
|
</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"/>
|
||||||
|
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,44,0,0,3,32"/>
|
||||||
|
</AuxValues>
|
||||||
|
|
||||||
|
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
|
||||||
|
<SubComponents>
|
||||||
|
<Container class="javax.swing.JPanel" name="waypointSettings">
|
||||||
|
<Properties>
|
||||||
|
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
|
||||||
|
<Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo">
|
||||||
|
<TitledBorder title="">
|
||||||
|
<ResourceString PropertyName="titleX" bundle="org/sleuthkit/autopsy/geolocation/Bundle.properties" key="GeoFilterPanel.waypointSettings.border.title" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
|
</TitledBorder>
|
||||||
|
</Border>
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
<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.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||||
|
<GridBagConstraints gridX="0" gridY="2" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="5" insetsLeft="15" insetsBottom="9" insetsRight="15" anchor="18" weightX="1.0" weightY="0.0"/>
|
||||||
|
</Constraint>
|
||||||
|
</Constraints>
|
||||||
|
|
||||||
|
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
|
||||||
|
<SubComponents>
|
||||||
|
<Component class="javax.swing.JRadioButton" name="allButton">
|
||||||
|
<Properties>
|
||||||
|
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
|
||||||
|
<ComponentRef name="buttonGroup"/>
|
||||||
|
</Property>
|
||||||
|
<Property name="selected" type="boolean" value="true"/>
|
||||||
|
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||||
|
<ResourceString bundle="org/sleuthkit/autopsy/geolocation/Bundle.properties" key="GeoFilterPanel.allButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
<Events>
|
||||||
|
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="allButtonActionPerformed"/>
|
||||||
|
</Events>
|
||||||
|
<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="4" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="18" weightX="1.0" weightY="0.0"/>
|
||||||
|
</Constraint>
|
||||||
|
</Constraints>
|
||||||
|
</Component>
|
||||||
|
<Component class="javax.swing.JRadioButton" name="mostRecentButton">
|
||||||
|
<Properties>
|
||||||
|
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
|
||||||
|
<ComponentRef name="buttonGroup"/>
|
||||||
|
</Property>
|
||||||
|
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||||
|
<ResourceString bundle="org/sleuthkit/autopsy/geolocation/Bundle.properties" key="GeoFilterPanel.mostRecentButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
<Events>
|
||||||
|
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="mostRecentButtonActionPerformed"/>
|
||||||
|
</Events>
|
||||||
|
<Constraints>
|
||||||
|
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||||
|
<GridBagConstraints gridX="0" gridY="1" gridWidth="2" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="9" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="18" weightX="0.0" weightY="0.0"/>
|
||||||
|
</Constraint>
|
||||||
|
</Constraints>
|
||||||
|
</Component>
|
||||||
|
<Component class="javax.swing.JCheckBox" name="showWaypointsWOTSCheckBox">
|
||||||
|
<Properties>
|
||||||
|
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||||
|
<ResourceString bundle="org/sleuthkit/autopsy/geolocation/Bundle.properties" key="GeoFilterPanel.showWaypointsWOTSCheckBox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
|
</Property>
|
||||||
|
<Property name="enabled" type="boolean" value="false"/>
|
||||||
|
</Properties>
|
||||||
|
<Constraints>
|
||||||
|
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||||
|
<GridBagConstraints gridX="1" gridY="2" gridWidth="3" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="30" insetsBottom="0" insetsRight="0" anchor="18" weightX="0.0" weightY="0.0"/>
|
||||||
|
</Constraint>
|
||||||
|
</Constraints>
|
||||||
|
</Component>
|
||||||
|
<Component class="javax.swing.JSpinner" name="daysSpinner">
|
||||||
|
<Properties>
|
||||||
|
<Property name="enabled" type="boolean" value="false"/>
|
||||||
|
</Properties>
|
||||||
|
<AuxValues>
|
||||||
|
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="new javax.swing.JSpinner(numberModel)"/>
|
||||||
|
</AuxValues>
|
||||||
|
<Constraints>
|
||||||
|
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||||
|
<GridBagConstraints gridX="2" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="9" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="18" weightX="0.0" weightY="0.0"/>
|
||||||
|
</Constraint>
|
||||||
|
</Constraints>
|
||||||
|
</Component>
|
||||||
|
<Component class="javax.swing.JLabel" name="daysLabel">
|
||||||
|
<Properties>
|
||||||
|
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||||
|
<ResourceString bundle="org/sleuthkit/autopsy/geolocation/Bundle.properties" key="GeoFilterPanel.daysLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
<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.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||||
|
<GridBagConstraints gridX="3" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="9" insetsLeft="5" insetsBottom="0" insetsRight="0" anchor="17" weightX="1.0" weightY="0.0"/>
|
||||||
|
</Constraint>
|
||||||
|
</Constraints>
|
||||||
|
</Component>
|
||||||
|
</SubComponents>
|
||||||
|
</Container>
|
||||||
|
<Container class="javax.swing.JPanel" name="buttonPanel">
|
||||||
|
<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.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="9" insetsLeft="15" insetsBottom="0" insetsRight="15" anchor="18" weightX="1.0" weightY="0.0"/>
|
||||||
|
</Constraint>
|
||||||
|
</Constraints>
|
||||||
|
|
||||||
|
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
|
||||||
|
<SubComponents>
|
||||||
|
<Component class="javax.swing.JButton" name="applyButton">
|
||||||
|
<Properties>
|
||||||
|
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
|
||||||
|
<Image iconType="3" name="/org/sleuthkit/autopsy/images/tick.png"/>
|
||||||
|
</Property>
|
||||||
|
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||||
|
<ResourceString bundle="org/sleuthkit/autopsy/geolocation/Bundle.properties" key="GeoFilterPanel.applyButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
|
</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="-1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="12" weightX="1.0" weightY="0.0"/>
|
||||||
|
</Constraint>
|
||||||
|
</Constraints>
|
||||||
|
</Component>
|
||||||
|
</SubComponents>
|
||||||
|
</Container>
|
||||||
|
<Component class="javax.swing.JLabel" name="optionsLabel">
|
||||||
|
<Properties>
|
||||||
|
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
|
||||||
|
<Image iconType="3" name="/org/sleuthkit/autopsy/images/blueGeo16.png"/>
|
||||||
|
</Property>
|
||||||
|
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||||
|
<ResourceString bundle="org/sleuthkit/autopsy/geolocation/Bundle.properties" key="GeoFilterPanel.optionsLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
<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.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||||
|
<GridBagConstraints gridX="0" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="15" insetsBottom="0" insetsRight="0" anchor="17" weightX="0.0" weightY="0.0"/>
|
||||||
|
</Constraint>
|
||||||
|
</Constraints>
|
||||||
|
</Component>
|
||||||
|
</SubComponents>
|
||||||
|
</Form>
|
359
Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.java
Executable file
@ -0,0 +1,359 @@
|
|||||||
|
/*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2019 Basis Technology Corp.
|
||||||
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.sleuthkit.autopsy.geolocation;
|
||||||
|
|
||||||
|
import java.awt.GridBagConstraints;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import javax.swing.ImageIcon;
|
||||||
|
import javax.swing.SpinnerNumberModel;
|
||||||
|
import org.openide.util.NbBundle.Messages;
|
||||||
|
import org.sleuthkit.autopsy.casemodule.Case;
|
||||||
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
|
import org.sleuthkit.datamodel.DataSource;
|
||||||
|
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||||
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Panel to display the filter options for geolocation waypoints.
|
||||||
|
*/
|
||||||
|
class GeoFilterPanel extends javax.swing.JPanel {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
private static final Logger logger = Logger.getLogger(GeoFilterPanel.class.getName());
|
||||||
|
|
||||||
|
private final SpinnerNumberModel numberModel;
|
||||||
|
private final CheckBoxListPanel<DataSource> checkboxPanel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates new GeoFilterPanel
|
||||||
|
*/
|
||||||
|
@Messages({
|
||||||
|
"GeoFilterPanel_DataSource_List_Title=Data Sources"
|
||||||
|
})
|
||||||
|
GeoFilterPanel() {
|
||||||
|
// numberModel is used in initComponents
|
||||||
|
numberModel = new SpinnerNumberModel(10, 1, Integer.MAX_VALUE, 1);
|
||||||
|
|
||||||
|
initComponents();
|
||||||
|
|
||||||
|
// The gui builder cannot handle using CheckBoxListPanel due to its
|
||||||
|
// use of generics so we will initalize it here.
|
||||||
|
checkboxPanel = new CheckBoxListPanel<>();
|
||||||
|
checkboxPanel.setPanelTitle(Bundle.GeoFilterPanel_DataSource_List_Title());
|
||||||
|
checkboxPanel.setPanelTitleIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/image.png")));
|
||||||
|
checkboxPanel.setSetAllSelected(true);
|
||||||
|
|
||||||
|
GridBagConstraints gridBagConstraints = new GridBagConstraints();
|
||||||
|
gridBagConstraints.gridx = 0;
|
||||||
|
gridBagConstraints.gridy = 3;
|
||||||
|
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
|
||||||
|
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
|
||||||
|
gridBagConstraints.weightx = 1.0;
|
||||||
|
gridBagConstraints.weighty = 1.0;
|
||||||
|
gridBagConstraints.insets = new java.awt.Insets(0, 15, 0, 15);
|
||||||
|
add(checkboxPanel, gridBagConstraints);
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateDataSourceList() {
|
||||||
|
try {
|
||||||
|
initCheckboxList();
|
||||||
|
} catch (TskCoreException ex) {
|
||||||
|
logger.log(Level.WARNING, "Failed to initialize the CheckboxListPane", ex); //NON-NLS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an actionListener to listen for the filter apply action
|
||||||
|
*
|
||||||
|
* @param listener
|
||||||
|
*/
|
||||||
|
void addActionListener(ActionListener listener) {
|
||||||
|
applyButton.addActionListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the selected filter values.
|
||||||
|
*
|
||||||
|
* @return A GeoFilter object with the user selected filter values
|
||||||
|
*
|
||||||
|
* @throws GeoLocationUIException
|
||||||
|
*/
|
||||||
|
@Messages({
|
||||||
|
"GeoFilterPanel_empty_dataSource=Data Source list is empty."
|
||||||
|
})
|
||||||
|
GeoFilter getFilterState() throws GeoLocationUIException {
|
||||||
|
List<DataSource> dataSources = checkboxPanel.getSelectedElements();
|
||||||
|
|
||||||
|
if (dataSources.isEmpty()) {
|
||||||
|
throw new GeoLocationUIException(Bundle.GeoFilterPanel_empty_dataSource());
|
||||||
|
}
|
||||||
|
return new GeoFilter(allButton.isSelected(),
|
||||||
|
showWaypointsWOTSCheckBox.isSelected(),
|
||||||
|
numberModel.getNumber().intValue(),
|
||||||
|
dataSources);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the checkbox list panel
|
||||||
|
*
|
||||||
|
* @throws TskCoreException
|
||||||
|
*/
|
||||||
|
private void initCheckboxList() throws TskCoreException {
|
||||||
|
final SleuthkitCase sleuthkitCase = Case.getCurrentCase().getSleuthkitCase();
|
||||||
|
|
||||||
|
checkboxPanel.clearList();
|
||||||
|
|
||||||
|
for (DataSource dataSource : sleuthkitCase.getDataSources()) {
|
||||||
|
String dsName = sleuthkitCase.getContentById(dataSource.getId()).getName();
|
||||||
|
checkboxPanel.addElement(dsName, dataSource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Based on the state of mostRecent radio button Change the state of the cnt
|
||||||
|
* spinner and the time stamp checkbox.
|
||||||
|
*/
|
||||||
|
private void updateWaypointOptions() {
|
||||||
|
boolean selected = mostRecentButton.isSelected();
|
||||||
|
showWaypointsWOTSCheckBox.setEnabled(selected);
|
||||||
|
daysSpinner.setEnabled(selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
javax.swing.ButtonGroup buttonGroup = new javax.swing.ButtonGroup();
|
||||||
|
javax.swing.JPanel waypointSettings = new javax.swing.JPanel();
|
||||||
|
allButton = new javax.swing.JRadioButton();
|
||||||
|
mostRecentButton = new javax.swing.JRadioButton();
|
||||||
|
showWaypointsWOTSCheckBox = new javax.swing.JCheckBox();
|
||||||
|
daysSpinner = new javax.swing.JSpinner(numberModel);
|
||||||
|
javax.swing.JLabel daysLabel = new javax.swing.JLabel();
|
||||||
|
javax.swing.JPanel buttonPanel = new javax.swing.JPanel();
|
||||||
|
applyButton = new javax.swing.JButton();
|
||||||
|
javax.swing.JLabel optionsLabel = new javax.swing.JLabel();
|
||||||
|
|
||||||
|
setLayout(new java.awt.GridBagLayout());
|
||||||
|
|
||||||
|
waypointSettings.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(GeoFilterPanel.class, "GeoFilterPanel.waypointSettings.border.title"))); // NOI18N
|
||||||
|
waypointSettings.setLayout(new java.awt.GridBagLayout());
|
||||||
|
|
||||||
|
buttonGroup.add(allButton);
|
||||||
|
allButton.setSelected(true);
|
||||||
|
org.openide.awt.Mnemonics.setLocalizedText(allButton, org.openide.util.NbBundle.getMessage(GeoFilterPanel.class, "GeoFilterPanel.allButton.text")); // NOI18N
|
||||||
|
allButton.addActionListener(new java.awt.event.ActionListener() {
|
||||||
|
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||||
|
allButtonActionPerformed(evt);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||||
|
gridBagConstraints.gridx = 0;
|
||||||
|
gridBagConstraints.gridy = 0;
|
||||||
|
gridBagConstraints.gridwidth = 4;
|
||||||
|
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
|
||||||
|
gridBagConstraints.weightx = 1.0;
|
||||||
|
waypointSettings.add(allButton, gridBagConstraints);
|
||||||
|
|
||||||
|
buttonGroup.add(mostRecentButton);
|
||||||
|
org.openide.awt.Mnemonics.setLocalizedText(mostRecentButton, org.openide.util.NbBundle.getMessage(GeoFilterPanel.class, "GeoFilterPanel.mostRecentButton.text")); // NOI18N
|
||||||
|
mostRecentButton.addActionListener(new java.awt.event.ActionListener() {
|
||||||
|
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||||
|
mostRecentButtonActionPerformed(evt);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||||
|
gridBagConstraints.gridx = 0;
|
||||||
|
gridBagConstraints.gridy = 1;
|
||||||
|
gridBagConstraints.gridwidth = 2;
|
||||||
|
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
|
||||||
|
gridBagConstraints.insets = new java.awt.Insets(9, 0, 0, 0);
|
||||||
|
waypointSettings.add(mostRecentButton, gridBagConstraints);
|
||||||
|
|
||||||
|
org.openide.awt.Mnemonics.setLocalizedText(showWaypointsWOTSCheckBox, org.openide.util.NbBundle.getMessage(GeoFilterPanel.class, "GeoFilterPanel.showWaypointsWOTSCheckBox.text")); // NOI18N
|
||||||
|
showWaypointsWOTSCheckBox.setEnabled(false);
|
||||||
|
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||||
|
gridBagConstraints.gridx = 1;
|
||||||
|
gridBagConstraints.gridy = 2;
|
||||||
|
gridBagConstraints.gridwidth = 3;
|
||||||
|
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
|
||||||
|
gridBagConstraints.insets = new java.awt.Insets(0, 30, 0, 0);
|
||||||
|
waypointSettings.add(showWaypointsWOTSCheckBox, gridBagConstraints);
|
||||||
|
|
||||||
|
daysSpinner.setEnabled(false);
|
||||||
|
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||||
|
gridBagConstraints.gridx = 2;
|
||||||
|
gridBagConstraints.gridy = 1;
|
||||||
|
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
|
||||||
|
gridBagConstraints.insets = new java.awt.Insets(9, 0, 0, 0);
|
||||||
|
waypointSettings.add(daysSpinner, gridBagConstraints);
|
||||||
|
|
||||||
|
org.openide.awt.Mnemonics.setLocalizedText(daysLabel, org.openide.util.NbBundle.getMessage(GeoFilterPanel.class, "GeoFilterPanel.daysLabel.text")); // NOI18N
|
||||||
|
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||||
|
gridBagConstraints.gridx = 3;
|
||||||
|
gridBagConstraints.gridy = 1;
|
||||||
|
gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
|
||||||
|
gridBagConstraints.weightx = 1.0;
|
||||||
|
gridBagConstraints.insets = new java.awt.Insets(9, 5, 0, 0);
|
||||||
|
waypointSettings.add(daysLabel, gridBagConstraints);
|
||||||
|
|
||||||
|
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||||
|
gridBagConstraints.gridx = 0;
|
||||||
|
gridBagConstraints.gridy = 2;
|
||||||
|
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
|
||||||
|
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
|
||||||
|
gridBagConstraints.weightx = 1.0;
|
||||||
|
gridBagConstraints.insets = new java.awt.Insets(5, 15, 9, 15);
|
||||||
|
add(waypointSettings, gridBagConstraints);
|
||||||
|
|
||||||
|
buttonPanel.setLayout(new java.awt.GridBagLayout());
|
||||||
|
|
||||||
|
applyButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/tick.png"))); // NOI18N
|
||||||
|
org.openide.awt.Mnemonics.setLocalizedText(applyButton, org.openide.util.NbBundle.getMessage(GeoFilterPanel.class, "GeoFilterPanel.applyButton.text")); // NOI18N
|
||||||
|
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||||
|
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHEAST;
|
||||||
|
gridBagConstraints.weightx = 1.0;
|
||||||
|
buttonPanel.add(applyButton, gridBagConstraints);
|
||||||
|
|
||||||
|
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||||
|
gridBagConstraints.gridx = 0;
|
||||||
|
gridBagConstraints.gridy = 0;
|
||||||
|
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
|
||||||
|
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
|
||||||
|
gridBagConstraints.weightx = 1.0;
|
||||||
|
gridBagConstraints.insets = new java.awt.Insets(9, 15, 0, 15);
|
||||||
|
add(buttonPanel, gridBagConstraints);
|
||||||
|
|
||||||
|
optionsLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/blueGeo16.png"))); // NOI18N
|
||||||
|
org.openide.awt.Mnemonics.setLocalizedText(optionsLabel, org.openide.util.NbBundle.getMessage(GeoFilterPanel.class, "GeoFilterPanel.optionsLabel.text")); // NOI18N
|
||||||
|
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||||
|
gridBagConstraints.gridx = 0;
|
||||||
|
gridBagConstraints.gridy = 1;
|
||||||
|
gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
|
||||||
|
gridBagConstraints.insets = new java.awt.Insets(0, 15, 0, 0);
|
||||||
|
add(optionsLabel, gridBagConstraints);
|
||||||
|
}// </editor-fold>//GEN-END:initComponents
|
||||||
|
|
||||||
|
private void allButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_allButtonActionPerformed
|
||||||
|
updateWaypointOptions();
|
||||||
|
}//GEN-LAST:event_allButtonActionPerformed
|
||||||
|
|
||||||
|
private void mostRecentButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_mostRecentButtonActionPerformed
|
||||||
|
updateWaypointOptions();
|
||||||
|
}//GEN-LAST:event_mostRecentButtonActionPerformed
|
||||||
|
|
||||||
|
|
||||||
|
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||||
|
private javax.swing.JRadioButton allButton;
|
||||||
|
private javax.swing.JButton applyButton;
|
||||||
|
private javax.swing.JSpinner daysSpinner;
|
||||||
|
private javax.swing.JRadioButton mostRecentButton;
|
||||||
|
private javax.swing.JCheckBox showWaypointsWOTSCheckBox;
|
||||||
|
// End of variables declaration//GEN-END:variables
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to store the values of the Geolocation user set filter parameters
|
||||||
|
*/
|
||||||
|
final class GeoFilter {
|
||||||
|
|
||||||
|
private final boolean showAll;
|
||||||
|
private final boolean showWithoutTimeStamp;
|
||||||
|
private final int mostRecentNumDays;
|
||||||
|
private final List<DataSource> dataSources;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a Geolocation filter. showAll and mostRecentNumDays are
|
||||||
|
* exclusive filters, ie they cannot be used together.
|
||||||
|
*
|
||||||
|
* withoutTimeStamp is only applicable if mostRecentNumDays is true.
|
||||||
|
*
|
||||||
|
* When using the filters "most recent days" means to include waypoints
|
||||||
|
* for the numbers of days after the most recent waypoint, not the
|
||||||
|
* current date.
|
||||||
|
*
|
||||||
|
* @param showAll True if all waypoints should be shown
|
||||||
|
* @param withoutTimeStamp True to show waypoints without timeStamps,
|
||||||
|
* this filter is only applicable if
|
||||||
|
* mostRecentNumDays is true
|
||||||
|
* @param mostRecentNumDays Show Waypoint for the most recent given
|
||||||
|
* number of days. This parameter is ignored if
|
||||||
|
* showAll is true.
|
||||||
|
* @param dataSources A list of dataSources to filter waypoint
|
||||||
|
* for.
|
||||||
|
*/
|
||||||
|
GeoFilter(boolean showAll, boolean withoutTimeStamp, int mostRecentNumDays, List<DataSource> dataSources) {
|
||||||
|
this.showAll = showAll;
|
||||||
|
this.showWithoutTimeStamp = withoutTimeStamp;
|
||||||
|
this.mostRecentNumDays = mostRecentNumDays;
|
||||||
|
this.dataSources = dataSources;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether or not to show all waypoints.
|
||||||
|
*
|
||||||
|
* @return True if all waypoints should be shown.
|
||||||
|
*/
|
||||||
|
boolean showAllWaypoints() {
|
||||||
|
return showAll;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether or not to include waypoints with time stamps.
|
||||||
|
*
|
||||||
|
* This filter is only applicable if "showAll" is true.
|
||||||
|
*
|
||||||
|
* @return True if waypoints with time stamps should be shown.
|
||||||
|
*/
|
||||||
|
boolean showWaypointsWithoutTimeStamp() {
|
||||||
|
return showWithoutTimeStamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of most recent days to show waypoints for. This
|
||||||
|
* value should be ignored if showAll is true.
|
||||||
|
*
|
||||||
|
* @return The number of most recent days to show waypoints for
|
||||||
|
*/
|
||||||
|
int getMostRecentNumDays() {
|
||||||
|
return mostRecentNumDays;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of data sources to filter the waypoints by, or null if
|
||||||
|
* all datasources should be include.
|
||||||
|
*
|
||||||
|
* @return A list of dataSources or null if all dataSources should be
|
||||||
|
* included.
|
||||||
|
*/
|
||||||
|
List<DataSource> getDataSources() {
|
||||||
|
return Collections.unmodifiableList(dataSources);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
46
Core/src/org/sleuthkit/autopsy/geolocation/GeoLocationUIException.java
Executable file
@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2019 Basis Technology Corp.
|
||||||
|
* contact: carrier <at> sleuthkit <dot> org
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.sleuthkit.autopsy.geolocation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* An exception call for Exceptions that occure in the geolocation dialog.
|
||||||
|
*/
|
||||||
|
public class GeoLocationUIException extends Exception{
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create exception containing the error message
|
||||||
|
*
|
||||||
|
* @param msg the message
|
||||||
|
*/
|
||||||
|
public GeoLocationUIException(String msg) {
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create exception containing the error message and cause exception
|
||||||
|
*
|
||||||
|
* @param msg the message
|
||||||
|
* @param ex cause exception
|
||||||
|
*/
|
||||||
|
public GeoLocationUIException(String msg, Exception ex) {
|
||||||
|
super(msg, ex);
|
||||||
|
}
|
||||||
|
}
|
124
Core/src/org/sleuthkit/autopsy/geolocation/GeolocationOptionPanelController.java
Executable file
@ -0,0 +1,124 @@
|
|||||||
|
/*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2019 Basis Technology Corp.
|
||||||
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.sleuthkit.autopsy.geolocation;
|
||||||
|
|
||||||
|
import java.beans.PropertyChangeEvent;
|
||||||
|
import java.beans.PropertyChangeListener;
|
||||||
|
import java.beans.PropertyChangeSupport;
|
||||||
|
import javax.swing.JComponent;
|
||||||
|
import org.netbeans.spi.options.OptionsPanelController;
|
||||||
|
import org.openide.util.HelpCtx;
|
||||||
|
import org.openide.util.Lookup;
|
||||||
|
|
||||||
|
@OptionsPanelController.TopLevelRegistration(
|
||||||
|
categoryName = "#OptionsCategory_Name_Geolocation",
|
||||||
|
iconBase = "org/sleuthkit/autopsy/images/blueGeo32.png",
|
||||||
|
keywords = "#OptionsCategory_Keywords_Geolocation",
|
||||||
|
keywordsCategory = "Geolcoation",
|
||||||
|
position = 18
|
||||||
|
)
|
||||||
|
/**
|
||||||
|
* Controller for the Geolocation options pane in the Options dialog.
|
||||||
|
*/
|
||||||
|
public final class GeolocationOptionPanelController extends OptionsPanelController {
|
||||||
|
|
||||||
|
private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
|
||||||
|
private boolean changed;
|
||||||
|
private GeolocationSettingsPanel panel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the GeolcoationSettingPanel.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
GeolocationSettingsPanel getPanel() {
|
||||||
|
if (panel == null) {
|
||||||
|
panel = new GeolocationSettingsPanel();
|
||||||
|
panel.addPropertyChangeListener(new PropertyChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void propertyChange(PropertyChangeEvent evt) {
|
||||||
|
if (evt.getPropertyName().equals(OptionsPanelController.PROP_CHANGED)) {
|
||||||
|
updateChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update() {
|
||||||
|
getPanel().load();
|
||||||
|
changed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void applyChanges() {
|
||||||
|
getPanel().store();
|
||||||
|
changed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isChanged() {
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JComponent getComponent(Lookup masterLookup) {
|
||||||
|
return getPanel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HelpCtx getHelpCtx() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addPropertyChangeListener(PropertyChangeListener listener) {
|
||||||
|
pcs.addPropertyChangeListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removePropertyChangeListener(PropertyChangeListener listener) {
|
||||||
|
pcs.removePropertyChangeListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function for updating the change state.
|
||||||
|
*/
|
||||||
|
void updateChanged() {
|
||||||
|
if (!changed) {
|
||||||
|
changed = true;
|
||||||
|
pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, false, true);
|
||||||
|
}
|
||||||
|
pcs.firePropertyChange(OptionsPanelController.PROP_VALID, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancel() {
|
||||||
|
getPanel().cancelChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
170
Core/src/org/sleuthkit/autopsy/geolocation/GeolocationSettingsPanel.form
Executable file
@ -0,0 +1,170 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
|
||||||
|
<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
|
||||||
|
<NonVisualComponents>
|
||||||
|
<Component class="javax.swing.ButtonGroup" name="buttonGroup">
|
||||||
|
<AuxValues>
|
||||||
|
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
|
||||||
|
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
|
||||||
|
</AuxValues>
|
||||||
|
</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"/>
|
||||||
|
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,51,0,0,2,-33"/>
|
||||||
|
</AuxValues>
|
||||||
|
|
||||||
|
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
|
||||||
|
<SubComponents>
|
||||||
|
<Container class="javax.swing.JPanel" name="tilePane">
|
||||||
|
<Properties>
|
||||||
|
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
|
||||||
|
<Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo">
|
||||||
|
<TitledBorder title="Map Tile Source">
|
||||||
|
<ResourceString PropertyName="titleX" bundle="org/sleuthkit/autopsy/geolocation/Bundle.properties" key="GeolocationSettingsPanel.tilePane.border.title" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
|
</TitledBorder>
|
||||||
|
</Border>
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
<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.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||||
|
<GridBagConstraints gridX="-1" gridY="-1" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="18" weightX="1.0" weightY="1.0"/>
|
||||||
|
</Constraint>
|
||||||
|
</Constraints>
|
||||||
|
|
||||||
|
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
|
||||||
|
<SubComponents>
|
||||||
|
<Component class="javax.swing.JRadioButton" name="defaultButton">
|
||||||
|
<Properties>
|
||||||
|
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
|
||||||
|
<ComponentRef name="buttonGroup"/>
|
||||||
|
</Property>
|
||||||
|
<Property name="selected" type="boolean" value="true"/>
|
||||||
|
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||||
|
<ResourceString bundle="org/sleuthkit/autopsy/geolocation/Bundle.properties" key="GeolocationSettingsPanel.defaultButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
<Events>
|
||||||
|
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="defaultButtonActionPerformed"/>
|
||||||
|
</Events>
|
||||||
|
<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="3" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="5" insetsRight="0" anchor="18" weightX="1.0" weightY="0.0"/>
|
||||||
|
</Constraint>
|
||||||
|
</Constraints>
|
||||||
|
</Component>
|
||||||
|
<Component class="javax.swing.JRadioButton" name="tileServerButton">
|
||||||
|
<Properties>
|
||||||
|
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
|
||||||
|
<ComponentRef name="buttonGroup"/>
|
||||||
|
</Property>
|
||||||
|
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||||
|
<ResourceString bundle="org/sleuthkit/autopsy/geolocation/Bundle.properties" key="GeolocationSettingsPanel.tileServerButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
<Events>
|
||||||
|
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="tileServerButtonActionPerformed"/>
|
||||||
|
</Events>
|
||||||
|
<Constraints>
|
||||||
|
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||||
|
<GridBagConstraints gridX="0" gridY="-1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="5" insetsRight="0" anchor="18" weightX="0.0" weightY="0.0"/>
|
||||||
|
</Constraint>
|
||||||
|
</Constraints>
|
||||||
|
</Component>
|
||||||
|
<Component class="javax.swing.JTextField" name="tileServerFiled">
|
||||||
|
<Properties>
|
||||||
|
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||||
|
<ResourceString bundle="org/sleuthkit/autopsy/geolocation/Bundle.properties" key="GeolocationSettingsPanel.tileServerFiled.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
|
</Property>
|
||||||
|
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||||
|
<Dimension value="[300, 26]"/>
|
||||||
|
</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="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="5" insetsRight="0" anchor="10" weightX="0.0" weightY="0.0"/>
|
||||||
|
</Constraint>
|
||||||
|
</Constraints>
|
||||||
|
</Component>
|
||||||
|
<Component class="javax.swing.JRadioButton" name="osmZipButton">
|
||||||
|
<Properties>
|
||||||
|
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
|
||||||
|
<ComponentRef name="buttonGroup"/>
|
||||||
|
</Property>
|
||||||
|
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||||
|
<ResourceString bundle="org/sleuthkit/autopsy/geolocation/Bundle.properties" key="GeolocationSettingsPanel.osmZipButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
|
</Property>
|
||||||
|
<Property name="actionCommand" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||||
|
<ResourceString bundle="org/sleuthkit/autopsy/geolocation/Bundle.properties" key="GeolocationSettingsPanel.osmZipButton.actionCommand" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
<Events>
|
||||||
|
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="osmZipButtonActionPerformed"/>
|
||||||
|
</Events>
|
||||||
|
<Constraints>
|
||||||
|
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||||
|
<GridBagConstraints gridX="0" gridY="-1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="9" insetsRight="0" anchor="18" weightX="0.0" weightY="0.0"/>
|
||||||
|
</Constraint>
|
||||||
|
</Constraints>
|
||||||
|
</Component>
|
||||||
|
<Component class="javax.swing.JTextField" name="osmZipFileField">
|
||||||
|
<Properties>
|
||||||
|
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||||
|
<ResourceString bundle="org/sleuthkit/autopsy/geolocation/Bundle.properties" key="GeolocationSettingsPanel.osmZipFileField.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
|
</Property>
|
||||||
|
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||||
|
<Dimension value="[300, 26]"/>
|
||||||
|
</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="2" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="9" insetsRight="0" anchor="10" weightX="0.0" weightY="0.0"/>
|
||||||
|
</Constraint>
|
||||||
|
</Constraints>
|
||||||
|
</Component>
|
||||||
|
<Component class="javax.swing.JButton" name="osmZipFileBrowseButton">
|
||||||
|
<Properties>
|
||||||
|
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||||
|
<ResourceString bundle="org/sleuthkit/autopsy/geolocation/Bundle.properties" key="GeolocationSettingsPanel.osmZipFileBrowseButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
<Events>
|
||||||
|
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="osmZipFileBrowseButtonActionPerformed"/>
|
||||||
|
</Events>
|
||||||
|
<Constraints>
|
||||||
|
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||||
|
<GridBagConstraints gridX="2" gridY="2" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="9" insetsBottom="9" insetsRight="9" anchor="18" weightX="1.0" weightY="0.0"/>
|
||||||
|
</Constraint>
|
||||||
|
</Constraints>
|
||||||
|
</Component>
|
||||||
|
<Component class="javax.swing.JButton" name="serverTestButton">
|
||||||
|
<Properties>
|
||||||
|
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||||
|
<ResourceString bundle="org/sleuthkit/autopsy/geolocation/Bundle.properties" key="GeolocationSettingsPanel.serverTestButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
<Events>
|
||||||
|
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="serverTestButtonActionPerformed"/>
|
||||||
|
</Events>
|
||||||
|
<Constraints>
|
||||||
|
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||||
|
<GridBagConstraints gridX="2" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="9" insetsBottom="9" insetsRight="9" anchor="18" weightX="0.0" weightY="0.0"/>
|
||||||
|
</Constraint>
|
||||||
|
</Constraints>
|
||||||
|
</Component>
|
||||||
|
</SubComponents>
|
||||||
|
</Container>
|
||||||
|
</SubComponents>
|
||||||
|
</Form>
|
343
Core/src/org/sleuthkit/autopsy/geolocation/GeolocationSettingsPanel.java
Executable file
@ -0,0 +1,343 @@
|
|||||||
|
/*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2019 Basis Technology Corp.
|
||||||
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.sleuthkit.autopsy.geolocation;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import javax.swing.JFileChooser;
|
||||||
|
import javax.swing.JOptionPane;
|
||||||
|
import org.apache.commons.validator.routines.UrlValidator;
|
||||||
|
import org.jxmapviewer.OSMTileFactoryInfo;
|
||||||
|
import org.jxmapviewer.viewer.TileFactoryInfo;
|
||||||
|
import org.jxmapviewer.viewer.util.GeoUtil;
|
||||||
|
import org.netbeans.spi.options.OptionsPanelController;
|
||||||
|
import org.openide.util.NbBundle.Messages;
|
||||||
|
import org.sleuthkit.autopsy.casemodule.GeneralFilter;
|
||||||
|
import org.sleuthkit.autopsy.core.UserPreferences;
|
||||||
|
import org.sleuthkit.autopsy.corecomponents.OptionsPanel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A panel to allow the user to set the custom properties of the geolocation
|
||||||
|
* window.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
final class GeolocationSettingsPanel extends javax.swing.JPanel implements OptionsPanel {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates new GeolocationSettingsPanel
|
||||||
|
*/
|
||||||
|
GeolocationSettingsPanel() {
|
||||||
|
initComponents();
|
||||||
|
updateControlState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void store() {
|
||||||
|
UserPreferences.setGeolocationTileOption(getServerOption().getValue());
|
||||||
|
UserPreferences.setGeolocationOsmZipPath(osmZipFileField.getText());
|
||||||
|
UserPreferences.setGeolocationOsmServerAddress(tileServerFiled.getText());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void load() {
|
||||||
|
tileServerFiled.setText(UserPreferences.getGeolocationOsmServerAddress());
|
||||||
|
osmZipFileField.setText(UserPreferences.getGeolocationOsmZipPath());
|
||||||
|
switch (GeolocationTileOption.getOptionForValue(UserPreferences.getGeolocationtTileOption())) {
|
||||||
|
case ONLINE_USER_DEFINED_OSM_SERVER:
|
||||||
|
tileServerButton.setSelected(true);
|
||||||
|
break;
|
||||||
|
case OFFLINE_OSM_ZIP:
|
||||||
|
osmZipButton.setSelected(true);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
defaultButton.setSelected(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateControlState();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the state of the tile server options based on the radio button
|
||||||
|
* selection state.
|
||||||
|
*/
|
||||||
|
private void updateControlState() {
|
||||||
|
tileServerFiled.setEnabled(tileServerButton.isSelected());
|
||||||
|
serverTestButton.setEnabled(tileServerButton.isSelected());
|
||||||
|
osmZipFileField.setEnabled(osmZipButton.isSelected());
|
||||||
|
osmZipFileBrowseButton.setEnabled(osmZipButton.isSelected());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the GEOLOCATION_TILE_OPTION based on the selection state of the
|
||||||
|
* option radio buttons.
|
||||||
|
*
|
||||||
|
* @return Current GEOLOCATION_TILE_OPTION
|
||||||
|
*/
|
||||||
|
private GeolocationTileOption getServerOption() {
|
||||||
|
if (tileServerButton.isSelected()) {
|
||||||
|
return GeolocationTileOption.ONLINE_USER_DEFINED_OSM_SERVER;
|
||||||
|
} else if (osmZipButton.isSelected()) {
|
||||||
|
return GeolocationTileOption.OFFLINE_OSM_ZIP;
|
||||||
|
}
|
||||||
|
return GeolocationTileOption.ONLINE_DEFAULT_SERVER;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the validity of the given tile server address.
|
||||||
|
*
|
||||||
|
* This test assumes the server is able to serve up at least one tile, the
|
||||||
|
* tile at x=1, y=1 with a zoom level of 1. This tile should be the whole
|
||||||
|
* global.
|
||||||
|
*
|
||||||
|
* @param url String url to tile server. The string does not have to be
|
||||||
|
* prefixed with http://
|
||||||
|
*
|
||||||
|
* @return True if the server was successfully contacted.
|
||||||
|
*/
|
||||||
|
private boolean testOSMServer(String url) {
|
||||||
|
TileFactoryInfo info = new OSMTileFactoryInfo("User Defined Server", url); //NON-NLS
|
||||||
|
return GeoUtil.isValidTile(1, 1, 1, info);
|
||||||
|
}
|
||||||
|
|
||||||
|
void cancelChanges() {
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
javax.swing.ButtonGroup buttonGroup = new javax.swing.ButtonGroup();
|
||||||
|
javax.swing.JPanel tilePane = new javax.swing.JPanel();
|
||||||
|
defaultButton = new javax.swing.JRadioButton();
|
||||||
|
tileServerButton = new javax.swing.JRadioButton();
|
||||||
|
tileServerFiled = new javax.swing.JTextField();
|
||||||
|
osmZipButton = new javax.swing.JRadioButton();
|
||||||
|
osmZipFileField = new javax.swing.JTextField();
|
||||||
|
osmZipFileBrowseButton = new javax.swing.JButton();
|
||||||
|
serverTestButton = new javax.swing.JButton();
|
||||||
|
|
||||||
|
setLayout(new java.awt.GridBagLayout());
|
||||||
|
|
||||||
|
tilePane.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(GeolocationSettingsPanel.class, "GeolocationSettingsPanel.tilePane.border.title"))); // NOI18N
|
||||||
|
tilePane.setLayout(new java.awt.GridBagLayout());
|
||||||
|
|
||||||
|
buttonGroup.add(defaultButton);
|
||||||
|
defaultButton.setSelected(true);
|
||||||
|
org.openide.awt.Mnemonics.setLocalizedText(defaultButton, org.openide.util.NbBundle.getMessage(GeolocationSettingsPanel.class, "GeolocationSettingsPanel.defaultButton.text")); // NOI18N
|
||||||
|
defaultButton.addActionListener(new java.awt.event.ActionListener() {
|
||||||
|
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||||
|
defaultButtonActionPerformed(evt);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||||
|
gridBagConstraints.gridx = 0;
|
||||||
|
gridBagConstraints.gridy = 0;
|
||||||
|
gridBagConstraints.gridwidth = 3;
|
||||||
|
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
|
||||||
|
gridBagConstraints.weightx = 1.0;
|
||||||
|
gridBagConstraints.insets = new java.awt.Insets(0, 0, 5, 0);
|
||||||
|
tilePane.add(defaultButton, gridBagConstraints);
|
||||||
|
|
||||||
|
buttonGroup.add(tileServerButton);
|
||||||
|
org.openide.awt.Mnemonics.setLocalizedText(tileServerButton, org.openide.util.NbBundle.getMessage(GeolocationSettingsPanel.class, "GeolocationSettingsPanel.tileServerButton.text")); // NOI18N
|
||||||
|
tileServerButton.addActionListener(new java.awt.event.ActionListener() {
|
||||||
|
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||||
|
tileServerButtonActionPerformed(evt);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||||
|
gridBagConstraints.gridx = 0;
|
||||||
|
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
|
||||||
|
gridBagConstraints.insets = new java.awt.Insets(0, 0, 5, 0);
|
||||||
|
tilePane.add(tileServerButton, gridBagConstraints);
|
||||||
|
|
||||||
|
tileServerFiled.setText(org.openide.util.NbBundle.getMessage(GeolocationSettingsPanel.class, "GeolocationSettingsPanel.tileServerFiled.text")); // NOI18N
|
||||||
|
tileServerFiled.setPreferredSize(new java.awt.Dimension(300, 26));
|
||||||
|
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||||
|
gridBagConstraints.gridx = 1;
|
||||||
|
gridBagConstraints.gridy = 1;
|
||||||
|
gridBagConstraints.insets = new java.awt.Insets(0, 0, 5, 0);
|
||||||
|
tilePane.add(tileServerFiled, gridBagConstraints);
|
||||||
|
|
||||||
|
buttonGroup.add(osmZipButton);
|
||||||
|
org.openide.awt.Mnemonics.setLocalizedText(osmZipButton, org.openide.util.NbBundle.getMessage(GeolocationSettingsPanel.class, "GeolocationSettingsPanel.osmZipButton.text")); // NOI18N
|
||||||
|
osmZipButton.setActionCommand(org.openide.util.NbBundle.getMessage(GeolocationSettingsPanel.class, "GeolocationSettingsPanel.osmZipButton.actionCommand")); // NOI18N
|
||||||
|
osmZipButton.addActionListener(new java.awt.event.ActionListener() {
|
||||||
|
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||||
|
osmZipButtonActionPerformed(evt);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||||
|
gridBagConstraints.gridx = 0;
|
||||||
|
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
|
||||||
|
gridBagConstraints.insets = new java.awt.Insets(0, 0, 9, 0);
|
||||||
|
tilePane.add(osmZipButton, gridBagConstraints);
|
||||||
|
|
||||||
|
osmZipFileField.setText(org.openide.util.NbBundle.getMessage(GeolocationSettingsPanel.class, "GeolocationSettingsPanel.osmZipFileField.text")); // NOI18N
|
||||||
|
osmZipFileField.setPreferredSize(new java.awt.Dimension(300, 26));
|
||||||
|
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||||
|
gridBagConstraints.gridx = 1;
|
||||||
|
gridBagConstraints.gridy = 2;
|
||||||
|
gridBagConstraints.insets = new java.awt.Insets(0, 0, 9, 0);
|
||||||
|
tilePane.add(osmZipFileField, gridBagConstraints);
|
||||||
|
|
||||||
|
org.openide.awt.Mnemonics.setLocalizedText(osmZipFileBrowseButton, org.openide.util.NbBundle.getMessage(GeolocationSettingsPanel.class, "GeolocationSettingsPanel.osmZipFileBrowseButton.text")); // NOI18N
|
||||||
|
osmZipFileBrowseButton.addActionListener(new java.awt.event.ActionListener() {
|
||||||
|
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||||
|
osmZipFileBrowseButtonActionPerformed(evt);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||||
|
gridBagConstraints.gridx = 2;
|
||||||
|
gridBagConstraints.gridy = 2;
|
||||||
|
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
|
||||||
|
gridBagConstraints.weightx = 1.0;
|
||||||
|
gridBagConstraints.insets = new java.awt.Insets(0, 9, 9, 9);
|
||||||
|
tilePane.add(osmZipFileBrowseButton, gridBagConstraints);
|
||||||
|
|
||||||
|
org.openide.awt.Mnemonics.setLocalizedText(serverTestButton, org.openide.util.NbBundle.getMessage(GeolocationSettingsPanel.class, "GeolocationSettingsPanel.serverTestButton.text")); // NOI18N
|
||||||
|
serverTestButton.addActionListener(new java.awt.event.ActionListener() {
|
||||||
|
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||||
|
serverTestButtonActionPerformed(evt);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||||
|
gridBagConstraints.gridx = 2;
|
||||||
|
gridBagConstraints.gridy = 1;
|
||||||
|
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
|
||||||
|
gridBagConstraints.insets = new java.awt.Insets(0, 9, 9, 9);
|
||||||
|
tilePane.add(serverTestButton, gridBagConstraints);
|
||||||
|
|
||||||
|
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||||
|
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
|
||||||
|
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
|
||||||
|
gridBagConstraints.weightx = 1.0;
|
||||||
|
gridBagConstraints.weighty = 1.0;
|
||||||
|
add(tilePane, gridBagConstraints);
|
||||||
|
}// </editor-fold>//GEN-END:initComponents
|
||||||
|
|
||||||
|
private void osmZipFileBrowseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_osmZipFileBrowseButtonActionPerformed
|
||||||
|
JFileChooser fileWindow = new JFileChooser();
|
||||||
|
fileWindow.setFileSelectionMode(JFileChooser.FILES_ONLY);
|
||||||
|
GeneralFilter fileFilter = new GeneralFilter(Arrays.asList(".zip"), "Zips (*.zip)"); //NON-NLS
|
||||||
|
fileWindow.setDragEnabled(false);
|
||||||
|
fileWindow.setFileFilter(fileFilter);
|
||||||
|
fileWindow.setMultiSelectionEnabled(false);
|
||||||
|
int returnVal = fileWindow.showSaveDialog(this);
|
||||||
|
if (returnVal == JFileChooser.APPROVE_OPTION) {
|
||||||
|
File zipFile = fileWindow.getSelectedFile();
|
||||||
|
osmZipFileField.setForeground(Color.BLACK);
|
||||||
|
osmZipFileField.setText(zipFile.getAbsolutePath());
|
||||||
|
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
|
||||||
|
}
|
||||||
|
}//GEN-LAST:event_osmZipFileBrowseButtonActionPerformed
|
||||||
|
|
||||||
|
private void defaultButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_defaultButtonActionPerformed
|
||||||
|
updateControlState();
|
||||||
|
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
|
||||||
|
}//GEN-LAST:event_defaultButtonActionPerformed
|
||||||
|
|
||||||
|
private void tileServerButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_tileServerButtonActionPerformed
|
||||||
|
updateControlState();
|
||||||
|
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
|
||||||
|
}//GEN-LAST:event_tileServerButtonActionPerformed
|
||||||
|
|
||||||
|
private void osmZipButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_osmZipButtonActionPerformed
|
||||||
|
updateControlState();
|
||||||
|
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
|
||||||
|
}//GEN-LAST:event_osmZipButtonActionPerformed
|
||||||
|
|
||||||
|
@Messages({
|
||||||
|
"GeolocationSettingsPanel_malformed_url_message=The supplies OSM tile server address is invalid.\nPlease supply a well formed url prefixed with http://",
|
||||||
|
"GeolocationSettingsPanel_malformed_url_message_tile=Malformed URL",
|
||||||
|
"GeolocationSettingsPanel_osm_server_test_fail_message=OSM tile server test failed.\nUnable to connect to server.",
|
||||||
|
"GeolocationSettingsPanel_osm_server_test_fail_message_title=Error",
|
||||||
|
"GeolocationSettingsPanel_osm_server_test_success_message=The provide OSM tile server address is valid.",
|
||||||
|
"GeolocationSettingsPanel_osm_server_test_success_message_title=Success",})
|
||||||
|
private void serverTestButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_serverTestButtonActionPerformed
|
||||||
|
String address = tileServerFiled.getText();
|
||||||
|
String message = Bundle.GeolocationSettingsPanel_osm_server_test_fail_message();
|
||||||
|
String title = Bundle.GeolocationSettingsPanel_osm_server_test_fail_message_title();
|
||||||
|
|
||||||
|
String[] schemes = {"http", "https"}; //NON-NLS
|
||||||
|
UrlValidator urlValidator = new UrlValidator(schemes);
|
||||||
|
if (!urlValidator.isValid(address)) {
|
||||||
|
message = Bundle.GeolocationSettingsPanel_malformed_url_message();
|
||||||
|
title = Bundle.GeolocationSettingsPanel_malformed_url_message_tile();
|
||||||
|
} else if (testOSMServer(address)) {
|
||||||
|
message = Bundle.GeolocationSettingsPanel_osm_server_test_success_message();
|
||||||
|
title = Bundle.GeolocationSettingsPanel_osm_server_test_success_message_title();
|
||||||
|
}
|
||||||
|
|
||||||
|
JOptionPane.showMessageDialog(this, message, title, JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
}//GEN-LAST:event_serverTestButtonActionPerformed
|
||||||
|
|
||||||
|
|
||||||
|
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||||
|
private javax.swing.JRadioButton defaultButton;
|
||||||
|
private javax.swing.JRadioButton osmZipButton;
|
||||||
|
private javax.swing.JButton osmZipFileBrowseButton;
|
||||||
|
private javax.swing.JTextField osmZipFileField;
|
||||||
|
private javax.swing.JButton serverTestButton;
|
||||||
|
private javax.swing.JRadioButton tileServerButton;
|
||||||
|
private javax.swing.JTextField tileServerFiled;
|
||||||
|
// End of variables declaration//GEN-END:variables
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tile server option enum. The enum was given values to simplify the
|
||||||
|
* storing of the user preference for a particular option.
|
||||||
|
*/
|
||||||
|
enum GeolocationTileOption{
|
||||||
|
ONLINE_DEFAULT_SERVER(0),
|
||||||
|
ONLINE_USER_DEFINED_OSM_SERVER(1),
|
||||||
|
OFFLINE_OSM_ZIP(2);
|
||||||
|
|
||||||
|
private final int value;
|
||||||
|
|
||||||
|
GeolocationTileOption(int value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static GeolocationTileOption getOptionForValue(int value) {
|
||||||
|
for (GeolocationTileOption option : GeolocationTileOption.values()) {
|
||||||
|
if (option.getValue() == value) {
|
||||||
|
return option;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ONLINE_DEFAULT_SERVER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -11,6 +11,7 @@
|
|||||||
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
|
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
|
||||||
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
|
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
|
||||||
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
|
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
|
||||||
|
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,44,0,0,1,-112"/>
|
||||||
</AuxValues>
|
</AuxValues>
|
||||||
|
|
||||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
|
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
|
||||||
@ -23,6 +24,17 @@
|
|||||||
</Constraints>
|
</Constraints>
|
||||||
|
|
||||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
|
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
|
||||||
|
<SubComponents>
|
||||||
|
<Container class="org.sleuthkit.autopsy.geolocation.HidingPane" name="filterPane">
|
||||||
|
<Constraints>
|
||||||
|
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
|
||||||
|
<BorderConstraints direction="Before"/>
|
||||||
|
</Constraint>
|
||||||
|
</Constraints>
|
||||||
|
|
||||||
|
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout"/>
|
||||||
|
</Container>
|
||||||
|
</SubComponents>
|
||||||
</Container>
|
</Container>
|
||||||
</SubComponents>
|
</SubComponents>
|
||||||
</Form>
|
</Form>
|
@ -25,9 +25,9 @@ import java.beans.PropertyChangeListener;
|
|||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import javax.swing.SwingWorker;
|
import javax.swing.JOptionPane;
|
||||||
|
import javax.swing.SwingUtilities;
|
||||||
import org.openide.util.NbBundle.Messages;
|
import org.openide.util.NbBundle.Messages;
|
||||||
import org.openide.windows.RetainLocation;
|
import org.openide.windows.RetainLocation;
|
||||||
import org.openide.windows.TopComponent;
|
import org.openide.windows.TopComponent;
|
||||||
@ -37,6 +37,11 @@ import static org.sleuthkit.autopsy.casemodule.Case.Events.CURRENT_CASE;
|
|||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||||
|
import org.sleuthkit.autopsy.geolocation.GeoFilterPanel.GeoFilter;
|
||||||
|
import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationDataException;
|
||||||
|
import org.sleuthkit.autopsy.geolocation.datamodel.Waypoint;
|
||||||
|
import org.sleuthkit.autopsy.geolocation.datamodel.WaypointBuilder;
|
||||||
|
import org.sleuthkit.autopsy.geolocation.datamodel.WaypointBuilder.WaypointFilterQueryCallBack;
|
||||||
import org.sleuthkit.autopsy.ingest.IngestManager;
|
import org.sleuthkit.autopsy.ingest.IngestManager;
|
||||||
import static org.sleuthkit.autopsy.ingest.IngestManager.IngestModuleEvent.DATA_ADDED;
|
import static org.sleuthkit.autopsy.ingest.IngestManager.IngestModuleEvent.DATA_ADDED;
|
||||||
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
|
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
|
||||||
@ -59,6 +64,7 @@ public final class GeolocationTopComponent extends TopComponent {
|
|||||||
private static final Set<IngestManager.IngestModuleEvent> INGEST_MODULE_EVENTS_OF_INTEREST = EnumSet.of(DATA_ADDED);
|
private static final Set<IngestManager.IngestModuleEvent> INGEST_MODULE_EVENTS_OF_INTEREST = EnumSet.of(DATA_ADDED);
|
||||||
|
|
||||||
private final PropertyChangeListener ingestListener;
|
private final PropertyChangeListener ingestListener;
|
||||||
|
private final GeoFilterPanel geoFilterPanel;
|
||||||
|
|
||||||
final RefreshPanel refreshPanel = new RefreshPanel();
|
final RefreshPanel refreshPanel = new RefreshPanel();
|
||||||
|
|
||||||
@ -73,7 +79,7 @@ public final class GeolocationTopComponent extends TopComponent {
|
|||||||
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
|
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
|
||||||
public GeolocationTopComponent() {
|
public GeolocationTopComponent() {
|
||||||
initComponents();
|
initComponents();
|
||||||
initWaypoints();
|
|
||||||
setName(Bundle.GLTopComponent_name());
|
setName(Bundle.GLTopComponent_name());
|
||||||
|
|
||||||
this.ingestListener = pce -> {
|
this.ingestListener = pce -> {
|
||||||
@ -105,10 +111,19 @@ public final class GeolocationTopComponent extends TopComponent {
|
|||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionEvent e) {
|
public void actionPerformed(ActionEvent e) {
|
||||||
mapPanel.clearWaypoints();
|
mapPanel.clearWaypoints();
|
||||||
initWaypoints();
|
updateWaypoints();
|
||||||
showRefreshPanel(false);
|
showRefreshPanel(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
geoFilterPanel = new GeoFilterPanel();
|
||||||
|
filterPane.setPanel(geoFilterPanel);
|
||||||
|
geoFilterPanel.addActionListener(new ActionListener() {
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
updateWaypoints();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -118,7 +133,7 @@ public final class GeolocationTopComponent extends TopComponent {
|
|||||||
Case.addEventTypeSubscriber(EnumSet.of(CURRENT_CASE), evt -> {
|
Case.addEventTypeSubscriber(EnumSet.of(CURRENT_CASE), evt -> {
|
||||||
mapPanel.clearWaypoints();
|
mapPanel.clearWaypoints();
|
||||||
if (evt.getNewValue() != null) {
|
if (evt.getNewValue() != null) {
|
||||||
initWaypoints();
|
updateWaypoints();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -135,6 +150,30 @@ public final class GeolocationTopComponent extends TopComponent {
|
|||||||
WindowManager.getDefault().setTopComponentFloating(this, true);
|
WindowManager.getDefault().setTopComponentFloating(this, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Messages({
|
||||||
|
"GeolocationTC_connection_failure_message=Failed to connect to map title source.\nPlease review map source in Options dialog.",
|
||||||
|
"GeolocationTC_connection_failure_message_title=Connection Failure"
|
||||||
|
})
|
||||||
|
@Override
|
||||||
|
public void open() {
|
||||||
|
super.open();
|
||||||
|
geoFilterPanel.updateDataSourceList();
|
||||||
|
try {
|
||||||
|
mapPanel.initMap();
|
||||||
|
} catch (GeoLocationDataException ex) {
|
||||||
|
JOptionPane.showMessageDialog(this,
|
||||||
|
Bundle.GeolocationTC_connection_failure_message(),
|
||||||
|
Bundle.GeolocationTC_connection_failure_message_title(),
|
||||||
|
JOptionPane.ERROR_MESSAGE);
|
||||||
|
MessageNotifyUtil.Notify.error(
|
||||||
|
Bundle.GeolocationTC_connection_failure_message_title(),
|
||||||
|
Bundle.GeolocationTC_connection_failure_message());
|
||||||
|
logger.log(Level.SEVERE, ex.getMessage(), ex);
|
||||||
|
return; // Doen't set the waypoints.
|
||||||
|
}
|
||||||
|
updateWaypoints();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the state of the refresh panel at the top of the mapPanel.
|
* Set the state of the refresh panel at the top of the mapPanel.
|
||||||
*
|
*
|
||||||
@ -150,43 +189,60 @@ public final class GeolocationTopComponent extends TopComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use a SwingWorker thread to get a list of waypoints.
|
* Filters the list of waypoints based on the user selections in the filter
|
||||||
*
|
* pane.
|
||||||
*/
|
*/
|
||||||
private void initWaypoints() {
|
@Messages({
|
||||||
SwingWorker<List<MapWaypoint>, MapWaypoint> worker = new SwingWorker<List<MapWaypoint>, MapWaypoint>() {
|
"GeoTopComponent_no_waypoints_returned_mgs=Applied filter failed to find waypoints that matched criteria.\nRevise filter options and try again.",
|
||||||
@Override
|
"GeoTopComponent_no_waypoints_returned_Title=No Waypoints Found",
|
||||||
protected List<MapWaypoint> doInBackground() throws Exception {
|
"GeoTopComponent_filter_exception_msg=Exception occured during waypoint filtering.",
|
||||||
Case currentCase = Case.getCurrentCaseThrows();
|
"GeoTopComponent_filter_exception_Title=Filter Failure",
|
||||||
|
"GeoTopComponent_filer_data_invalid_msg=Unable to run waypoint filter.\nPlease select one or more data sources.",
|
||||||
|
"GeoTopComponent_filer_data_invalid_Title=Filter Failure"
|
||||||
|
})
|
||||||
|
private void updateWaypoints() {
|
||||||
|
GeoFilter filters;
|
||||||
|
|
||||||
return MapWaypoint.getWaypoints(currentCase.getSleuthkitCase());
|
// Show a warning message if the user has not selected a data source
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void done() {
|
|
||||||
if (isDone() && !isCancelled()) {
|
|
||||||
try {
|
try {
|
||||||
List<MapWaypoint> waypoints = get();
|
filters = geoFilterPanel.getFilterState();
|
||||||
if (waypoints == null || waypoints.isEmpty()) {
|
} catch (GeoLocationUIException ex) {
|
||||||
|
JOptionPane.showMessageDialog(this,
|
||||||
|
Bundle.GeoTopComponent_filer_data_invalid_msg(),
|
||||||
|
Bundle.GeoTopComponent_filer_data_invalid_Title(),
|
||||||
|
JOptionPane.INFORMATION_MESSAGE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mapPanel.setWaypoints(waypoints);
|
|
||||||
|
|
||||||
// There might be a better way to decide how to center
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
// but for now just use the first way point.
|
public void run() {
|
||||||
mapPanel.setCenterLocation(waypoints.get(0));
|
Case currentCase = Case.getCurrentCase();
|
||||||
|
try {
|
||||||
|
WaypointBuilder.getAllWaypoints(currentCase.getSleuthkitCase(), filters.getDataSources(), filters.showAllWaypoints(), filters.getMostRecentNumDays(), filters.showWaypointsWithoutTimeStamp(), new WaypointFilterQueryCallBack() {
|
||||||
|
@Override
|
||||||
|
public void process(List<Waypoint> waypoints) {
|
||||||
|
// If the list is empty, tell the user and do not change
|
||||||
|
// the visible waypoints.
|
||||||
|
if (waypoints == null || waypoints.isEmpty()) {
|
||||||
|
JOptionPane.showMessageDialog(GeolocationTopComponent.this,
|
||||||
|
Bundle.GeoTopComponent_no_waypoints_returned_Title(),
|
||||||
|
Bundle.GeoTopComponent_no_waypoints_returned_mgs(),
|
||||||
|
JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
|
||||||
} catch (ExecutionException ex) {
|
return;
|
||||||
logger.log(Level.WARNING, "An exception occured while initializing waypoints for geolocation window.", ex); //NON-NLS
|
}
|
||||||
MessageNotifyUtil.Message.error(Bundle.GLTopComponent_initilzation_error());
|
mapPanel.setWaypoints(MapWaypoint.getWaypoints(waypoints));
|
||||||
} catch (InterruptedException ex) {
|
}
|
||||||
logger.log(Level.WARNING, "The initializing thread for geolocation window was interrupted.", ex); //NON-NLS
|
});
|
||||||
|
} catch (GeoLocationDataException ex) {
|
||||||
|
logger.log(Level.SEVERE, "Failed to filter waypoints.", ex);
|
||||||
|
JOptionPane.showMessageDialog(GeolocationTopComponent.this,
|
||||||
|
Bundle.GeoTopComponent_filter_exception_Title(),
|
||||||
|
Bundle.GeoTopComponent_filter_exception_msg(),
|
||||||
|
JOptionPane.ERROR_MESSAGE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
};
|
|
||||||
|
|
||||||
worker.execute();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -199,13 +255,18 @@ public final class GeolocationTopComponent extends TopComponent {
|
|||||||
private void initComponents() {
|
private void initComponents() {
|
||||||
|
|
||||||
mapPanel = new org.sleuthkit.autopsy.geolocation.MapPanel();
|
mapPanel = new org.sleuthkit.autopsy.geolocation.MapPanel();
|
||||||
|
filterPane = new org.sleuthkit.autopsy.geolocation.HidingPane();
|
||||||
|
|
||||||
setLayout(new java.awt.BorderLayout());
|
setLayout(new java.awt.BorderLayout());
|
||||||
|
|
||||||
|
mapPanel.add(filterPane, java.awt.BorderLayout.LINE_START);
|
||||||
|
|
||||||
add(mapPanel, java.awt.BorderLayout.CENTER);
|
add(mapPanel, java.awt.BorderLayout.CENTER);
|
||||||
}// </editor-fold>//GEN-END:initComponents
|
}// </editor-fold>//GEN-END:initComponents
|
||||||
|
|
||||||
|
|
||||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||||
|
private org.sleuthkit.autopsy.geolocation.HidingPane filterPane;
|
||||||
private org.sleuthkit.autopsy.geolocation.MapPanel mapPanel;
|
private org.sleuthkit.autopsy.geolocation.MapPanel mapPanel;
|
||||||
// End of variables declaration//GEN-END:variables
|
// End of variables declaration//GEN-END:variables
|
||||||
}
|
}
|
||||||
|
133
Core/src/org/sleuthkit/autopsy/geolocation/HidingPane.java
Executable file
@ -0,0 +1,133 @@
|
|||||||
|
/*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2019 Basis Technology Corp.
|
||||||
|
* contact: carrier <at> sleuthkit <dot> org
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.sleuthkit.autopsy.geolocation;
|
||||||
|
|
||||||
|
import java.awt.BorderLayout;
|
||||||
|
import java.awt.Font;
|
||||||
|
import java.awt.Point;
|
||||||
|
import java.awt.event.MouseAdapter;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import javax.swing.Icon;
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JScrollPane;
|
||||||
|
import javax.swing.JTabbedPane;
|
||||||
|
import org.openide.util.NbBundle.Messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* A JTabbed pane with one tab that says "Filters". When the user clicks on that
|
||||||
|
* table the content of the tab will be hidden.
|
||||||
|
*
|
||||||
|
* The content pane provides support for scrolling.
|
||||||
|
*/
|
||||||
|
public final class HidingPane extends JTabbedPane {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private final JScrollPane scrollPane;
|
||||||
|
private final JPanel panel;
|
||||||
|
private final JLabel tabLabel;
|
||||||
|
|
||||||
|
private boolean panelVisible = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new HidingFilterPane
|
||||||
|
*/
|
||||||
|
@Messages({
|
||||||
|
"HidingPane_default_title=Filters"
|
||||||
|
})
|
||||||
|
public HidingPane() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
scrollPane = new JScrollPane();
|
||||||
|
panel = new JPanel();
|
||||||
|
panel.setLayout(new BorderLayout());
|
||||||
|
panel.add(scrollPane, BorderLayout.CENTER);
|
||||||
|
tabLabel = new JLabel(Bundle.HidingPane_default_title());
|
||||||
|
tabLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/funnel.png")));
|
||||||
|
tabLabel.setUI(new VerticalLabelUI(true));
|
||||||
|
tabLabel.setOpaque(false);
|
||||||
|
Font font = tabLabel.getFont().deriveFont(18).deriveFont(Font.BOLD);
|
||||||
|
tabLabel.setFont(font);
|
||||||
|
|
||||||
|
addTab(null, panel);
|
||||||
|
setTabComponentAt(0, tabLabel);
|
||||||
|
|
||||||
|
this.addMouseListener(new MouseAdapter() {
|
||||||
|
@Override
|
||||||
|
public void mouseClicked(MouseEvent evt) {
|
||||||
|
handleMouseClick(evt.getPoint());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setTabPlacement(JTabbedPane.RIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the title of the tab.
|
||||||
|
*
|
||||||
|
* @param title
|
||||||
|
*/
|
||||||
|
void setTitle(String title) {
|
||||||
|
tabLabel.setText(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the icon that appears on the tab.
|
||||||
|
*
|
||||||
|
* @param icon
|
||||||
|
*/
|
||||||
|
void setIcon(Icon icon) {
|
||||||
|
tabLabel.setIcon(icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the content for this panel.
|
||||||
|
*
|
||||||
|
* @param panel A panel to display in the tabbed pane.
|
||||||
|
*/
|
||||||
|
void setPanel(JPanel panel) {
|
||||||
|
scrollPane.setViewportView(panel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the mouse click.
|
||||||
|
*
|
||||||
|
* @param point
|
||||||
|
*/
|
||||||
|
private void handleMouseClick(Point point) {
|
||||||
|
int index = indexAtLocation(point.x, point.y);
|
||||||
|
|
||||||
|
if(index == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(panelVisible) {
|
||||||
|
panel.removeAll();
|
||||||
|
panel.revalidate();
|
||||||
|
panelVisible = false;
|
||||||
|
} else {
|
||||||
|
panel.add(scrollPane, BorderLayout.CENTER);
|
||||||
|
panel.revalidate();
|
||||||
|
panelVisible = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -28,13 +28,17 @@ import java.awt.event.ComponentEvent;
|
|||||||
import java.awt.geom.Point2D;
|
import java.awt.geom.Point2D;
|
||||||
import java.beans.PropertyChangeEvent;
|
import java.beans.PropertyChangeEvent;
|
||||||
import java.beans.PropertyChangeListener;
|
import java.beans.PropertyChangeListener;
|
||||||
|
import java.io.File;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
import java.util.prefs.PreferenceChangeEvent;
|
||||||
|
import java.util.prefs.PreferenceChangeListener;
|
||||||
import javax.swing.JMenuItem;
|
import javax.swing.JMenuItem;
|
||||||
|
import javax.swing.JOptionPane;
|
||||||
import javax.swing.JPopupMenu;
|
import javax.swing.JPopupMenu;
|
||||||
import javax.swing.JSeparator;
|
import javax.swing.JSeparator;
|
||||||
import javax.swing.Popup;
|
import javax.swing.Popup;
|
||||||
@ -42,21 +46,28 @@ import javax.swing.PopupFactory;
|
|||||||
import javax.swing.Timer;
|
import javax.swing.Timer;
|
||||||
import javax.swing.event.MouseInputListener;
|
import javax.swing.event.MouseInputListener;
|
||||||
import org.jxmapviewer.OSMTileFactoryInfo;
|
import org.jxmapviewer.OSMTileFactoryInfo;
|
||||||
|
import org.jxmapviewer.VirtualEarthTileFactoryInfo;
|
||||||
import org.jxmapviewer.input.CenterMapListener;
|
import org.jxmapviewer.input.CenterMapListener;
|
||||||
import org.jxmapviewer.input.PanMouseInputListener;
|
import org.jxmapviewer.input.PanMouseInputListener;
|
||||||
import org.jxmapviewer.input.ZoomMouseWheelListenerCursor;
|
import org.jxmapviewer.input.ZoomMouseWheelListenerCursor;
|
||||||
import org.jxmapviewer.viewer.DefaultTileFactory;
|
import org.jxmapviewer.viewer.DefaultTileFactory;
|
||||||
import org.jxmapviewer.viewer.GeoPosition;
|
import org.jxmapviewer.viewer.GeoPosition;
|
||||||
|
import org.jxmapviewer.viewer.TileFactory;
|
||||||
import org.jxmapviewer.viewer.TileFactoryInfo;
|
import org.jxmapviewer.viewer.TileFactoryInfo;
|
||||||
import org.jxmapviewer.viewer.Waypoint;
|
import org.jxmapviewer.viewer.Waypoint;
|
||||||
import org.jxmapviewer.viewer.WaypointPainter;
|
import org.jxmapviewer.viewer.WaypointPainter;
|
||||||
|
import org.jxmapviewer.viewer.util.GeoUtil;
|
||||||
|
import org.openide.util.NbBundle.Messages;
|
||||||
|
import org.sleuthkit.autopsy.core.UserPreferences;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
|
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||||
|
import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationDataException;
|
||||||
import org.sleuthkit.datamodel.TskCoreException;
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The map panel. This panel contains the jxmapviewer MapViewer
|
* The map panel. This panel contains the jxmapviewer MapViewer
|
||||||
*/
|
*/
|
||||||
final class MapPanel extends javax.swing.JPanel {
|
final public class MapPanel extends javax.swing.JPanel {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(MapPanel.class.getName());
|
private static final Logger logger = Logger.getLogger(MapPanel.class.getName());
|
||||||
|
|
||||||
@ -76,9 +87,12 @@ final class MapPanel extends javax.swing.JPanel {
|
|||||||
/**
|
/**
|
||||||
* Creates new form MapPanel
|
* Creates new form MapPanel
|
||||||
*/
|
*/
|
||||||
MapPanel() {
|
@Messages({
|
||||||
|
"MapPanel_connection_failure_message=Failed to connect to new geolocation map tile source.",
|
||||||
|
"MapPanel_connection_failure_message_title=Connection Failure"
|
||||||
|
})
|
||||||
|
public MapPanel() {
|
||||||
initComponents();
|
initComponents();
|
||||||
initMap();
|
|
||||||
|
|
||||||
zoomChanging = false;
|
zoomChanging = false;
|
||||||
currentPopup = null;
|
currentPopup = null;
|
||||||
@ -94,14 +108,33 @@ final class MapPanel extends javax.swing.JPanel {
|
|||||||
showDetailsPopup();
|
showDetailsPopup();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
UserPreferences.addChangeListener(new PreferenceChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void preferenceChange(PreferenceChangeEvent evt) {
|
||||||
|
try {
|
||||||
|
mapViewer.setTileFactory(new DefaultTileFactory(getTileFactoryInfo()));
|
||||||
|
initializeZoomSlider();
|
||||||
|
} catch (GeoLocationDataException ex) {
|
||||||
|
logger.log(Level.SEVERE, "Failed to connect to new geolocation tile server.", ex); //NON-NLS
|
||||||
|
JOptionPane.showMessageDialog(MapPanel.this,
|
||||||
|
Bundle.MapPanel_connection_failure_message(),
|
||||||
|
Bundle.MapPanel_connection_failure_message_title(),
|
||||||
|
JOptionPane.ERROR_MESSAGE);
|
||||||
|
MessageNotifyUtil.Notify.error(
|
||||||
|
Bundle.MapPanel_connection_failure_message_title(),
|
||||||
|
Bundle.MapPanel_connection_failure_message());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the map.
|
* Initialize the map.
|
||||||
*/
|
*/
|
||||||
private void initMap() {
|
void initMap() throws GeoLocationDataException {
|
||||||
|
|
||||||
TileFactoryInfo info = new OSMTileFactoryInfo();
|
TileFactoryInfo info = getTileFactoryInfo();
|
||||||
DefaultTileFactory tileFactory = new DefaultTileFactory(info);
|
DefaultTileFactory tileFactory = new DefaultTileFactory(info);
|
||||||
mapViewer.setTileFactory(tileFactory);
|
mapViewer.setTileFactory(tileFactory);
|
||||||
|
|
||||||
@ -125,7 +158,8 @@ final class MapPanel extends javax.swing.JPanel {
|
|||||||
zoomSlider.setMaximum(tileFactory.getInfo().getMaximumZoomLevel());
|
zoomSlider.setMaximum(tileFactory.getInfo().getMaximumZoomLevel());
|
||||||
|
|
||||||
setZoom(tileFactory.getInfo().getMaximumZoomLevel() - 1);
|
setZoom(tileFactory.getInfo().getMaximumZoomLevel() - 1);
|
||||||
mapViewer.setAddressLocation(new GeoPosition(0, 0));
|
|
||||||
|
mapViewer.setCenterPosition(new GeoPosition(0,0));
|
||||||
|
|
||||||
// Basic painters for the way points.
|
// Basic painters for the way points.
|
||||||
WaypointPainter<Waypoint> waypointPainter = new WaypointPainter<Waypoint>() {
|
WaypointPainter<Waypoint> waypointPainter = new WaypointPainter<Waypoint>() {
|
||||||
@ -145,6 +179,79 @@ final class MapPanel extends javax.swing.JPanel {
|
|||||||
mapViewer.setOverlayPainter(waypointPainter);
|
mapViewer.setOverlayPainter(waypointPainter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup the zoom slider based on the current tileFactory.
|
||||||
|
*/
|
||||||
|
void initializeZoomSlider() {
|
||||||
|
TileFactory tileFactory = mapViewer.getTileFactory();
|
||||||
|
zoomSlider.setMinimum(tileFactory.getInfo().getMinimumZoomLevel());
|
||||||
|
zoomSlider.setMaximum(tileFactory.getInfo().getMaximumZoomLevel());
|
||||||
|
|
||||||
|
zoomSlider.repaint();
|
||||||
|
zoomSlider.revalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the TileFactoryInfo object based on the user preference.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
TileFactoryInfo getTileFactoryInfo() throws GeoLocationDataException {
|
||||||
|
switch (GeolocationSettingsPanel.GeolocationTileOption.getOptionForValue(UserPreferences.getGeolocationtTileOption())) {
|
||||||
|
case ONLINE_USER_DEFINED_OSM_SERVER:
|
||||||
|
return createOnlineOSMFactory(UserPreferences.getGeolocationOsmServerAddress());
|
||||||
|
case OFFLINE_OSM_ZIP:
|
||||||
|
return createOSMZipFactory(UserPreferences.getGeolocationOsmZipPath());
|
||||||
|
default:
|
||||||
|
return new VirtualEarthTileFactoryInfo(VirtualEarthTileFactoryInfo.MAP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the TileFactoryInfo for an online OSM tile server.
|
||||||
|
*
|
||||||
|
* @param address Tile server address
|
||||||
|
*
|
||||||
|
* @return TileFactoryInfo object for server address.
|
||||||
|
*
|
||||||
|
* @throws GeoLocationDataException
|
||||||
|
*/
|
||||||
|
private TileFactoryInfo createOnlineOSMFactory(String address) throws GeoLocationDataException {
|
||||||
|
if (address.isEmpty()) {
|
||||||
|
throw new GeoLocationDataException("Invalid user preference for OSM user define tile server. Address is an empty string.");
|
||||||
|
} else {
|
||||||
|
TileFactoryInfo info = new OSMTileFactoryInfo("User Defined Server", address);
|
||||||
|
if (!GeoUtil.isValidTile(1, 1, 1, info)) {
|
||||||
|
throw new GeoLocationDataException(String.format("Invalid OSM user define tile server: %s", address));
|
||||||
|
}
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the TileFactoryInfo for OSM zip File
|
||||||
|
*
|
||||||
|
* @param zipPath Path to zip file.
|
||||||
|
*
|
||||||
|
* @return TileFactoryInfo for zip file.
|
||||||
|
*
|
||||||
|
* @throws GeoLocationDataException
|
||||||
|
*/
|
||||||
|
private TileFactoryInfo createOSMZipFactory(String path) throws GeoLocationDataException {
|
||||||
|
if (path.isEmpty()) {
|
||||||
|
throw new GeoLocationDataException("Invalid OSM tile Zip file. User preference value is empty string.");
|
||||||
|
} else {
|
||||||
|
File file = new File(path);
|
||||||
|
if (!file.exists() || !file.canRead()) {
|
||||||
|
throw new GeoLocationDataException("Invalid OSM tile zip file. Unable to read file: " + path);
|
||||||
|
}
|
||||||
|
|
||||||
|
String zipPath = path.replaceAll("\\\\", "/");
|
||||||
|
|
||||||
|
return new OSMTileFactoryInfo("ZIP archive", "jar:file:/" + zipPath + "!"); //NON-NLS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores the given List of MapWaypoint in a KdTree object.
|
* Stores the given List of MapWaypoint in a KdTree object.
|
||||||
*
|
*
|
||||||
@ -160,15 +267,6 @@ final class MapPanel extends javax.swing.JPanel {
|
|||||||
mapViewer.repaint();
|
mapViewer.repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Centers the view of the map on the given location.
|
|
||||||
*
|
|
||||||
* @param waypoint Location to center the map
|
|
||||||
*/
|
|
||||||
void setCenterLocation(Waypoint waypoint) {
|
|
||||||
mapViewer.setCenterPosition(waypoint.getPosition());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the current zoom level.
|
* Set the current zoom level.
|
||||||
*
|
*
|
||||||
|
@ -49,6 +49,7 @@ import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationDataException;
|
|||||||
import org.sleuthkit.autopsy.geolocation.datamodel.Route;
|
import org.sleuthkit.autopsy.geolocation.datamodel.Route;
|
||||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||||
import org.sleuthkit.autopsy.geolocation.datamodel.Waypoint;
|
import org.sleuthkit.autopsy.geolocation.datamodel.Waypoint;
|
||||||
|
import org.sleuthkit.autopsy.geolocation.datamodel.WaypointBuilder;
|
||||||
import org.sleuthkit.autopsy.timeline.actions.ViewArtifactInTimelineAction;
|
import org.sleuthkit.autopsy.timeline.actions.ViewArtifactInTimelineAction;
|
||||||
import org.sleuthkit.datamodel.AbstractFile;
|
import org.sleuthkit.datamodel.AbstractFile;
|
||||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||||
@ -79,7 +80,7 @@ final class MapWaypoint extends KdTree.XYZPoint implements org.jxmapviewer.viewe
|
|||||||
* @throws GeoLocationDataException
|
* @throws GeoLocationDataException
|
||||||
*/
|
*/
|
||||||
static List<MapWaypoint> getWaypoints(SleuthkitCase skCase) throws GeoLocationDataException {
|
static List<MapWaypoint> getWaypoints(SleuthkitCase skCase) throws GeoLocationDataException {
|
||||||
List<Waypoint> points = Waypoint.getAllWaypoints(skCase);
|
List<Waypoint> points = WaypointBuilder.getAllWaypoints(skCase);
|
||||||
|
|
||||||
List<Route> routes = Route.getRoutes(skCase);
|
List<Route> routes = Route.getRoutes(skCase);
|
||||||
for (Route route : routes) {
|
for (Route route : routes) {
|
||||||
@ -95,6 +96,28 @@ final class MapWaypoint extends KdTree.XYZPoint implements org.jxmapviewer.viewe
|
|||||||
return mapPoints;
|
return mapPoints;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of of MapWaypoint objects for the given list of
|
||||||
|
* datamodel.Waypoint objects.
|
||||||
|
*
|
||||||
|
* @param dmWaypoints
|
||||||
|
*
|
||||||
|
* @return List of MapWaypoint objects. List will be empty if dmWaypoints was
|
||||||
|
* empty or null.
|
||||||
|
*/
|
||||||
|
static List<MapWaypoint> getWaypoints(List<Waypoint> dmWaypoints) {
|
||||||
|
List<MapWaypoint> mapPoints = new ArrayList<>();
|
||||||
|
|
||||||
|
if (dmWaypoints != null) {
|
||||||
|
|
||||||
|
for (Waypoint point : dmWaypoints) {
|
||||||
|
mapPoints.add(new MapWaypoint(point));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapPoints;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a MapWaypoint without a reference to the datamodel waypoint.
|
* Returns a MapWaypoint without a reference to the datamodel waypoint.
|
||||||
*
|
*
|
||||||
|
@ -42,7 +42,7 @@
|
|||||||
<Component class="javax.swing.JButton" name="refreshButton">
|
<Component class="javax.swing.JButton" name="refreshButton">
|
||||||
<Properties>
|
<Properties>
|
||||||
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
|
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
|
||||||
<Image iconType="3" name="/org/sleuthkit/autopsy/geolocation/images/arrow-circle-double-135.png"/>
|
<Image iconType="3" name="/org/sleuthkit/autopsy/images/arrow-circle-double-135.png"/>
|
||||||
</Property>
|
</Property>
|
||||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||||
<ResourceString bundle="org/sleuthkit/autopsy/geolocation/Bundle.properties" key="RefreshPanel.refreshButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
<ResourceString bundle="org/sleuthkit/autopsy/geolocation/Bundle.properties" key="RefreshPanel.refreshButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
@ -63,7 +63,7 @@
|
|||||||
<Color blue="0" green="0" red="0" type="rgb"/>
|
<Color blue="0" green="0" red="0" type="rgb"/>
|
||||||
</Property>
|
</Property>
|
||||||
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
|
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
|
||||||
<Image iconType="3" name="/org/sleuthkit/autopsy/geolocation/images/cross-script.png"/>
|
<Image iconType="3" name="/org/sleuthkit/autopsy/images/close-icon.png"/>
|
||||||
</Property>
|
</Property>
|
||||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||||
<ResourceString bundle="org/sleuthkit/autopsy/geolocation/Bundle.properties" key="RefreshPanel.closeButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
<ResourceString bundle="org/sleuthkit/autopsy/geolocation/Bundle.properties" key="RefreshPanel.closeButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
|
@ -82,13 +82,13 @@ final class RefreshPanel extends JPanel {
|
|||||||
gridBagConstraints.insets = new java.awt.Insets(15, 10, 15, 10);
|
gridBagConstraints.insets = new java.awt.Insets(15, 10, 15, 10);
|
||||||
add(refreshLabel, gridBagConstraints);
|
add(refreshLabel, gridBagConstraints);
|
||||||
|
|
||||||
refreshButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/geolocation/images/arrow-circle-double-135.png"))); // NOI18N
|
refreshButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/arrow-circle-double-135.png"))); // NOI18N
|
||||||
org.openide.awt.Mnemonics.setLocalizedText(refreshButton, org.openide.util.NbBundle.getMessage(RefreshPanel.class, "RefreshPanel.refreshButton.text")); // NOI18N
|
org.openide.awt.Mnemonics.setLocalizedText(refreshButton, org.openide.util.NbBundle.getMessage(RefreshPanel.class, "RefreshPanel.refreshButton.text")); // NOI18N
|
||||||
refreshButton.setMargin(new java.awt.Insets(2, 5, 2, 5));
|
refreshButton.setMargin(new java.awt.Insets(2, 5, 2, 5));
|
||||||
add(refreshButton, new java.awt.GridBagConstraints());
|
add(refreshButton, new java.awt.GridBagConstraints());
|
||||||
|
|
||||||
closeButton.setBackground(new java.awt.Color(0, 0, 0));
|
closeButton.setBackground(new java.awt.Color(0, 0, 0));
|
||||||
closeButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/geolocation/images/cross-script.png"))); // NOI18N
|
closeButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/close-icon.png"))); // NOI18N
|
||||||
org.openide.awt.Mnemonics.setLocalizedText(closeButton, org.openide.util.NbBundle.getMessage(RefreshPanel.class, "RefreshPanel.closeButton.text")); // NOI18N
|
org.openide.awt.Mnemonics.setLocalizedText(closeButton, org.openide.util.NbBundle.getMessage(RefreshPanel.class, "RefreshPanel.closeButton.text")); // NOI18N
|
||||||
closeButton.setMargin(new java.awt.Insets(0, 0, 0, 0));
|
closeButton.setMargin(new java.awt.Insets(0, 0, 0, 0));
|
||||||
closeButton.setOpaque(false);
|
closeButton.setOpaque(false);
|
||||||
|
124
Core/src/org/sleuthkit/autopsy/geolocation/VerticalLabelUI.java
Executable file
@ -0,0 +1,124 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2019 Basis Technology Corp.
|
||||||
|
* contact: carrier <at> sleuthkit <dot> org
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.sleuthkit.autopsy.geolocation;
|
||||||
|
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.awt.FontMetrics;
|
||||||
|
import java.awt.Graphics;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.Insets;
|
||||||
|
import java.awt.Rectangle;
|
||||||
|
import java.awt.geom.AffineTransform;
|
||||||
|
|
||||||
|
import javax.swing.Icon;
|
||||||
|
import javax.swing.JComponent;
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.plaf.basic.BasicLabelUI;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is an overload of BasicLabelUI to draw labels vertically.
|
||||||
|
*
|
||||||
|
* This code was found at:
|
||||||
|
* https://tech.chitgoks.com/2009/11/13/rotate-jlabel-vertically/
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
final class VerticalLabelUI extends BasicLabelUI {
|
||||||
|
|
||||||
|
private static final Rectangle paintIconR = new Rectangle();
|
||||||
|
private static final Rectangle paintTextR = new Rectangle();
|
||||||
|
private static final Rectangle paintViewR = new Rectangle();
|
||||||
|
private static Insets paintViewInsets = new Insets(0, 0, 0, 0);
|
||||||
|
|
||||||
|
static {
|
||||||
|
labelUI = new VerticalLabelUI(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
final boolean clockwise;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new VerticalLabelUI
|
||||||
|
* @param clockwise
|
||||||
|
*/
|
||||||
|
VerticalLabelUI(boolean clockwise) {
|
||||||
|
super();
|
||||||
|
this.clockwise = clockwise;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Dimension getPreferredSize(JComponent c) {
|
||||||
|
Dimension dim = super.getPreferredSize(c);
|
||||||
|
return new Dimension( dim.height, dim.width );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void paint(Graphics g, JComponent c) {
|
||||||
|
JLabel label = (JLabel)c;
|
||||||
|
String text = label.getText();
|
||||||
|
Icon icon = (label.isEnabled()) ? label.getIcon() : label.getDisabledIcon();
|
||||||
|
|
||||||
|
if ((icon == null) && (text == null)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FontMetrics fm = g.getFontMetrics();
|
||||||
|
paintViewInsets = c.getInsets(paintViewInsets);
|
||||||
|
|
||||||
|
paintViewR.x = paintViewInsets.left;
|
||||||
|
paintViewR.y = paintViewInsets.top;
|
||||||
|
|
||||||
|
// Use inverted height & width
|
||||||
|
paintViewR.height = c.getWidth() - (paintViewInsets.left + paintViewInsets.right);
|
||||||
|
paintViewR.width = c.getHeight() - (paintViewInsets.top + paintViewInsets.bottom);
|
||||||
|
|
||||||
|
paintIconR.x = paintIconR.y = paintIconR.width = paintIconR.height = 0;
|
||||||
|
paintTextR.x = paintTextR.y = paintTextR.width = paintTextR.height = 0;
|
||||||
|
|
||||||
|
String clippedText = layoutCL(label, fm, text, icon, paintViewR, paintIconR, paintTextR);
|
||||||
|
|
||||||
|
Graphics2D g2 = (Graphics2D) g;
|
||||||
|
AffineTransform tr = g2.getTransform();
|
||||||
|
if (clockwise) {
|
||||||
|
g2.rotate( Math.PI / 2 );
|
||||||
|
g2.translate( 0, - c.getWidth() );
|
||||||
|
} else {
|
||||||
|
g2.rotate( - Math.PI / 2 );
|
||||||
|
g2.translate( - c.getHeight(), 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
if (icon != null) {
|
||||||
|
icon.paintIcon(c, g, paintIconR.x, paintIconR.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text != null) {
|
||||||
|
int textX = paintTextR.x;
|
||||||
|
int textY = paintTextR.y + fm.getAscent();
|
||||||
|
|
||||||
|
if (label.isEnabled()) {
|
||||||
|
paintEnabledText(label, g, clippedText, textX, textY);
|
||||||
|
} else {
|
||||||
|
paintDisabledText(label, g, clippedText, textX, textY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g2.setTransform( tr );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -25,12 +25,9 @@ import java.util.HashSet;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
import org.sleuthkit.datamodel.AbstractFile;
|
import org.sleuthkit.datamodel.AbstractFile;
|
||||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
|
||||||
import org.sleuthkit.datamodel.TskCoreException;
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -68,8 +65,6 @@ public class Waypoint {
|
|||||||
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END,
|
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END,
|
||||||
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END,};
|
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END,};
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(Waypoint.class.getName());
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a waypoint with the given artifact.
|
* Construct a waypoint with the given artifact.
|
||||||
*
|
*
|
||||||
@ -263,186 +258,6 @@ public class Waypoint {
|
|||||||
return attributeMap;
|
return attributeMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a list of Waypoints for the artifacts with geolocation
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* List will include artifacts of type: TSK_GPS_TRACKPOINT TSK_GPS_SEARCH
|
|
||||||
* TSK_GPS_LAST_KNOWN_LOCATION TSK_GPS_BOOKMARK TSK_METADATA_EXIF
|
|
||||||
*
|
|
||||||
* @param skCase Currently open SleuthkitCase
|
|
||||||
*
|
|
||||||
* @return List of Waypoint
|
|
||||||
*
|
|
||||||
* @throws GeoLocationDataException
|
|
||||||
*/
|
|
||||||
public static List<Waypoint> getAllWaypoints(SleuthkitCase skCase) throws GeoLocationDataException {
|
|
||||||
List<Waypoint> points = new ArrayList<>();
|
|
||||||
|
|
||||||
points.addAll(getTrackpointWaypoints(skCase));
|
|
||||||
points.addAll(getEXIFWaypoints(skCase));
|
|
||||||
points.addAll(getSearchWaypoints(skCase));
|
|
||||||
points.addAll(getLastKnownWaypoints(skCase));
|
|
||||||
points.addAll(getBookmarkWaypoints(skCase));
|
|
||||||
|
|
||||||
return points;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a list of Waypoints for TSK_GPS_TRACKPOINT artifacts.
|
|
||||||
*
|
|
||||||
* @param skCase Currently open SleuthkitCase
|
|
||||||
*
|
|
||||||
* @return List of Waypoint
|
|
||||||
*
|
|
||||||
* @throws GeoLocationDataException
|
|
||||||
*/
|
|
||||||
public static List<Waypoint> getTrackpointWaypoints(SleuthkitCase skCase) throws GeoLocationDataException {
|
|
||||||
List<BlackboardArtifact> artifacts = null;
|
|
||||||
try {
|
|
||||||
artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT);
|
|
||||||
} catch (TskCoreException ex) {
|
|
||||||
throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_TRACKPOINT", ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Waypoint> points = new ArrayList<>();
|
|
||||||
for (BlackboardArtifact artifact : artifacts) {
|
|
||||||
try {
|
|
||||||
Waypoint point = new TrackpointWaypoint(artifact);
|
|
||||||
points.add(point);
|
|
||||||
} catch (GeoLocationDataException ex) {
|
|
||||||
logger.log(Level.WARNING, String.format("No longitude or latitude available for TSK_GPS_TRACKPOINT artifactID: %d", artifact.getArtifactID()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return points;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a list of Waypoints for TSK_METADATA_EXIF artifacts.
|
|
||||||
*
|
|
||||||
* @param skCase Currently open SleuthkitCase
|
|
||||||
*
|
|
||||||
* @return List of Waypoint
|
|
||||||
*
|
|
||||||
* @throws GeoLocationDataException
|
|
||||||
*/
|
|
||||||
static public List<Waypoint> getEXIFWaypoints(SleuthkitCase skCase) throws GeoLocationDataException {
|
|
||||||
List<BlackboardArtifact> artifacts = null;
|
|
||||||
try {
|
|
||||||
artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF);
|
|
||||||
} catch (TskCoreException ex) {
|
|
||||||
throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_LAST_KNOWN_LOCATION", ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Waypoint> points = new ArrayList<>();
|
|
||||||
if (artifacts != null) {
|
|
||||||
for (BlackboardArtifact artifact : artifacts) {
|
|
||||||
try {
|
|
||||||
Waypoint point = new EXIFWaypoint(artifact);
|
|
||||||
points.add(point);
|
|
||||||
} catch (GeoLocationDataException ex) {
|
|
||||||
// I am a little relucant to log this error because I suspect
|
|
||||||
// this will happen more often than not. It is valid for
|
|
||||||
// METADAT_EXIF to not have longitude and latitude
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return points;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a list of Waypoints for TSK_GPS_SEARCH artifacts.
|
|
||||||
*
|
|
||||||
* @param skCase Currently open SleuthkitCase
|
|
||||||
*
|
|
||||||
* @return List of Waypoint
|
|
||||||
*
|
|
||||||
* @throws GeoLocationDataException
|
|
||||||
*/
|
|
||||||
public static List<Waypoint> getSearchWaypoints(SleuthkitCase skCase) throws GeoLocationDataException {
|
|
||||||
List<BlackboardArtifact> artifacts = null;
|
|
||||||
try {
|
|
||||||
artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH);
|
|
||||||
} catch (TskCoreException ex) {
|
|
||||||
throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_SEARCH", ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Waypoint> points = new ArrayList<>();
|
|
||||||
if (artifacts != null) {
|
|
||||||
for (BlackboardArtifact artifact : artifacts) {
|
|
||||||
try {
|
|
||||||
Waypoint point = new SearchWaypoint(artifact);
|
|
||||||
points.add(point);
|
|
||||||
} catch (GeoLocationDataException ex) {
|
|
||||||
logger.log(Level.WARNING, String.format("No longitude or latitude available for TSK_GPS_SEARCH artifactID: %d", artifact.getArtifactID()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return points;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a list of Waypoints for TSK_GPS_LAST_KNOWN_LOCATION artifacts.
|
|
||||||
*
|
|
||||||
* @param skCase Currently open SleuthkitCase
|
|
||||||
*
|
|
||||||
* @return List of Waypoint
|
|
||||||
*
|
|
||||||
* @throws GeoLocationDataException
|
|
||||||
*/
|
|
||||||
public static List<Waypoint> getLastKnownWaypoints(SleuthkitCase skCase) throws GeoLocationDataException {
|
|
||||||
List<BlackboardArtifact> artifacts = null;
|
|
||||||
try {
|
|
||||||
artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION);
|
|
||||||
} catch (TskCoreException ex) {
|
|
||||||
throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_LAST_KNOWN_LOCATION", ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Waypoint> points = new ArrayList<>();
|
|
||||||
if (artifacts != null) {
|
|
||||||
for (BlackboardArtifact artifact : artifacts) {
|
|
||||||
try {
|
|
||||||
Waypoint point = new LastKnownWaypoint(artifact);
|
|
||||||
points.add(point);
|
|
||||||
} catch (GeoLocationDataException ex) {
|
|
||||||
logger.log(Level.WARNING, String.format("No longitude or latitude available for TSK_GPS_LAST_KNOWN_LOCATION artifactID: %d", artifact.getArtifactID()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return points;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a list of Waypoints for TSK_GPS_BOOKMARK artifacts.
|
|
||||||
*
|
|
||||||
* @param skCase Currently open SleuthkitCase
|
|
||||||
*
|
|
||||||
* @return List of Waypoint
|
|
||||||
*
|
|
||||||
* @throws GeoLocationDataException
|
|
||||||
*/
|
|
||||||
public static List<Waypoint> getBookmarkWaypoints(SleuthkitCase skCase) throws GeoLocationDataException {
|
|
||||||
List<BlackboardArtifact> artifacts = null;
|
|
||||||
try {
|
|
||||||
artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK);
|
|
||||||
} catch (TskCoreException ex) {
|
|
||||||
throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_BOOKMARK", ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Waypoint> points = new ArrayList<>();
|
|
||||||
if (artifacts != null) {
|
|
||||||
for (BlackboardArtifact artifact : artifacts) {
|
|
||||||
try {
|
|
||||||
Waypoint point = new Waypoint(artifact);
|
|
||||||
points.add(point);
|
|
||||||
} catch (GeoLocationDataException ex) {
|
|
||||||
logger.log(Level.WARNING, String.format("No longitude or latitude available for TSK_GPS_BOOKMARK artifactID: %d", artifact.getArtifactID()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return points;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a list of Waypoint.Property objects for the given artifact. This list
|
* Get a list of Waypoint.Property objects for the given artifact. This list
|
||||||
* will not include attributes that the Waypoint interfact has get functions
|
* will not include attributes that the Waypoint interfact has get functions
|
||||||
|
486
Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java
Executable file
@ -0,0 +1,486 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2019 Basis Technology Corp.
|
||||||
|
* contact: carrier <at> sleuthkit <dot> org
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.sleuthkit.autopsy.geolocation.datamodel;
|
||||||
|
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
|
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||||
|
import org.sleuthkit.datamodel.CaseDbAccessManager;
|
||||||
|
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||||
|
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||||
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
|
import org.sleuthkit.datamodel.DataSource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for building lists of waypoints.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public final class WaypointBuilder {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(WaypointBuilder.class.getName());
|
||||||
|
|
||||||
|
// SELECT statement for getting a list of waypoints.
|
||||||
|
final static String GEO_ARTIFACT_QUERY
|
||||||
|
= "SELECT artifact_id, artifact_type_id "
|
||||||
|
+ "FROM blackboard_attributes "
|
||||||
|
+ "WHERE attribute_type_id IN (%d, %d) "; //NON-NLS
|
||||||
|
|
||||||
|
// SELECT statement to get only artifact_ids
|
||||||
|
final static String GEO_ARTIFACT_QUERY_ID_ONLY
|
||||||
|
= "SELECT artifact_id "
|
||||||
|
+ "FROM blackboard_attributes "
|
||||||
|
+ "WHERE attribute_type_id IN (%d, %d) "; //NON-NLS
|
||||||
|
|
||||||
|
// This Query will return a list of waypoint artifacts
|
||||||
|
final static String GEO_ARTIFACT_WITH_DATA_SOURCES_QUERY
|
||||||
|
= "SELECT blackboard_attributes.artifact_id "
|
||||||
|
+ "FROM blackboard_attributes, blackboard_artifacts "
|
||||||
|
+ "WHERE blackboard_attributes.attribute_type_id IN(%d, %d) "
|
||||||
|
+ "AND data_source_obj_id IN (%s)"; //NON-NLS
|
||||||
|
|
||||||
|
// Select will return the "most recent" timestamp from all waypoings
|
||||||
|
final static String MOST_RECENT_TIME
|
||||||
|
= "SELECT MAX(value_int64) - (%d * 86400)" //86400 is the number of seconds in a day.
|
||||||
|
+ "FROM blackboard_attributes "
|
||||||
|
+ "WHERE attribute_type_id IN(%d, %d) "
|
||||||
|
+ "AND artifact_id "
|
||||||
|
+ "IN ( "
|
||||||
|
+ "%s" //GEO_ARTIFACT with or without data source
|
||||||
|
+ " )";
|
||||||
|
|
||||||
|
// Returns a list of artifacts with no time stamp
|
||||||
|
final static String SELECT_WO_TIMESTAMP =
|
||||||
|
"SELECT DISTINCT artifact_id, artifact_type_id "
|
||||||
|
+ "FROM blackboard_attributes "
|
||||||
|
+ "WHERE artifact_id NOT IN (%s) "
|
||||||
|
+ "AND artifact_id IN (%s)"; //NON-NLS
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A callback interface to process the results of waypoint filtering.
|
||||||
|
*/
|
||||||
|
public interface WaypointFilterQueryCallBack {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function will be called after the waypoints have been filtered.
|
||||||
|
*
|
||||||
|
* @param wwaypoints This of waypoints.
|
||||||
|
*/
|
||||||
|
void process(List<Waypoint> wwaypoints);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* private constructor
|
||||||
|
*/
|
||||||
|
private WaypointBuilder() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of Waypoints for the artifacts with geolocation
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* List will include artifacts of type: TSK_GPS_TRACKPOINT TSK_GPS_SEARCH
|
||||||
|
* TSK_GPS_LAST_KNOWN_LOCATION TSK_GPS_BOOKMARK TSK_METADATA_EXIF
|
||||||
|
*
|
||||||
|
* @param skCase Currently open SleuthkitCase
|
||||||
|
*
|
||||||
|
* @return List of Waypoint
|
||||||
|
*
|
||||||
|
* @throws GeoLocationDataException
|
||||||
|
*/
|
||||||
|
public static List<Waypoint> getAllWaypoints(SleuthkitCase skCase) throws GeoLocationDataException {
|
||||||
|
List<Waypoint> points = new ArrayList<>();
|
||||||
|
|
||||||
|
points.addAll(getTrackpointWaypoints(skCase));
|
||||||
|
points.addAll(getEXIFWaypoints(skCase));
|
||||||
|
points.addAll(getSearchWaypoints(skCase));
|
||||||
|
points.addAll(getLastKnownWaypoints(skCase));
|
||||||
|
points.addAll(getBookmarkWaypoints(skCase));
|
||||||
|
|
||||||
|
return points;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a list of Waypoints for TSK_GPS_TRACKPOINT artifacts.
|
||||||
|
*
|
||||||
|
* @param skCase Currently open SleuthkitCase
|
||||||
|
*
|
||||||
|
* @return List of Waypoint
|
||||||
|
*
|
||||||
|
* @throws GeoLocationDataException
|
||||||
|
*/
|
||||||
|
public static List<Waypoint> getTrackpointWaypoints(SleuthkitCase skCase) throws GeoLocationDataException {
|
||||||
|
List<BlackboardArtifact> artifacts = null;
|
||||||
|
try {
|
||||||
|
artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT);
|
||||||
|
} catch (TskCoreException ex) {
|
||||||
|
throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_TRACKPOINT", ex);//NON-NLS
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Waypoint> points = new ArrayList<>();
|
||||||
|
for (BlackboardArtifact artifact : artifacts) {
|
||||||
|
try {
|
||||||
|
Waypoint point = new TrackpointWaypoint(artifact);
|
||||||
|
points.add(point);
|
||||||
|
} catch (GeoLocationDataException ex) {
|
||||||
|
logger.log(Level.WARNING, String.format("No longitude or latitude available for TSK_GPS_TRACKPOINT artifactID: %d", artifact.getArtifactID()));//NON-NLS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return points;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a list of Waypoints for TSK_METADATA_EXIF artifacts.
|
||||||
|
*
|
||||||
|
* @param skCase Currently open SleuthkitCase
|
||||||
|
*
|
||||||
|
* @return List of Waypoint
|
||||||
|
*
|
||||||
|
* @throws GeoLocationDataException
|
||||||
|
*/
|
||||||
|
static public List<Waypoint> getEXIFWaypoints(SleuthkitCase skCase) throws GeoLocationDataException {
|
||||||
|
List<BlackboardArtifact> artifacts = null;
|
||||||
|
try {
|
||||||
|
artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF);
|
||||||
|
} catch (TskCoreException ex) {
|
||||||
|
throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_LAST_KNOWN_LOCATION", ex);//NON-NLS
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Waypoint> points = new ArrayList<>();
|
||||||
|
if (artifacts != null) {
|
||||||
|
for (BlackboardArtifact artifact : artifacts) {
|
||||||
|
try {
|
||||||
|
Waypoint point = new EXIFWaypoint(artifact);
|
||||||
|
points.add(point);
|
||||||
|
} catch (GeoLocationDataException ex) {
|
||||||
|
// I am a little relucant to log this error because I suspect
|
||||||
|
// this will happen more often than not. It is valid for
|
||||||
|
// METADAT_EXIF to not have longitude and latitude
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return points;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a list of Waypoints for TSK_GPS_SEARCH artifacts.
|
||||||
|
*
|
||||||
|
* @param skCase Currently open SleuthkitCase
|
||||||
|
*
|
||||||
|
* @return List of Waypoint
|
||||||
|
*
|
||||||
|
* @throws GeoLocationDataException
|
||||||
|
*/
|
||||||
|
public static List<Waypoint> getSearchWaypoints(SleuthkitCase skCase) throws GeoLocationDataException {
|
||||||
|
List<BlackboardArtifact> artifacts = null;
|
||||||
|
try {
|
||||||
|
artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH);
|
||||||
|
} catch (TskCoreException ex) {
|
||||||
|
throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_SEARCH", ex);//NON-NLS
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Waypoint> points = new ArrayList<>();
|
||||||
|
if (artifacts != null) {
|
||||||
|
for (BlackboardArtifact artifact : artifacts) {
|
||||||
|
try {
|
||||||
|
Waypoint point = new SearchWaypoint(artifact);
|
||||||
|
points.add(point);
|
||||||
|
} catch (GeoLocationDataException ex) {
|
||||||
|
logger.log(Level.WARNING, String.format("No longitude or latitude available for TSK_GPS_SEARCH artifactID: %d", artifact.getArtifactID()));//NON-NLS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return points;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a list of Waypoints for TSK_GPS_LAST_KNOWN_LOCATION artifacts.
|
||||||
|
*
|
||||||
|
* @param skCase Currently open SleuthkitCase
|
||||||
|
*
|
||||||
|
* @return List of Waypoint
|
||||||
|
*
|
||||||
|
* @throws GeoLocationDataException
|
||||||
|
*/
|
||||||
|
public static List<Waypoint> getLastKnownWaypoints(SleuthkitCase skCase) throws GeoLocationDataException {
|
||||||
|
List<BlackboardArtifact> artifacts = null;
|
||||||
|
try {
|
||||||
|
artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION);
|
||||||
|
} catch (TskCoreException ex) {
|
||||||
|
throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_LAST_KNOWN_LOCATION", ex);//NON-NLS
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Waypoint> points = new ArrayList<>();
|
||||||
|
if (artifacts != null) {
|
||||||
|
for (BlackboardArtifact artifact : artifacts) {
|
||||||
|
try {
|
||||||
|
Waypoint point = new LastKnownWaypoint(artifact);
|
||||||
|
points.add(point);
|
||||||
|
} catch (GeoLocationDataException ex) {
|
||||||
|
logger.log(Level.WARNING, String.format("No longitude or latitude available for TSK_GPS_LAST_KNOWN_LOCATION artifactID: %d", artifact.getArtifactID()));//NON-NLS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return points;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a list of Waypoints for TSK_GPS_BOOKMARK artifacts.
|
||||||
|
*
|
||||||
|
* @param skCase Currently open SleuthkitCase
|
||||||
|
*
|
||||||
|
* @return List of Waypoint
|
||||||
|
*
|
||||||
|
* @throws GeoLocationDataException
|
||||||
|
*/
|
||||||
|
public static List<Waypoint> getBookmarkWaypoints(SleuthkitCase skCase) throws GeoLocationDataException {
|
||||||
|
List<BlackboardArtifact> artifacts = null;
|
||||||
|
try {
|
||||||
|
artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK);
|
||||||
|
} catch (TskCoreException ex) {
|
||||||
|
throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_BOOKMARK", ex);//NON-NLS
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Waypoint> points = new ArrayList<>();
|
||||||
|
if (artifacts != null) {
|
||||||
|
for (BlackboardArtifact artifact : artifacts) {
|
||||||
|
try {
|
||||||
|
Waypoint point = new Waypoint(artifact);
|
||||||
|
points.add(point);
|
||||||
|
} catch (GeoLocationDataException ex) {
|
||||||
|
logger.log(Level.WARNING, String.format("No longitude or latitude available for TSK_GPS_BOOKMARK artifactID: %d", artifact.getArtifactID()), ex);//NON-NLS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return points;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a filtered list of waypoints.
|
||||||
|
*
|
||||||
|
* If showAll is true, the values of cntDaysFromRecent and notTimeStamp will
|
||||||
|
* be ignored.
|
||||||
|
*
|
||||||
|
* To include data from all dataSources pass a null or empty dataSource
|
||||||
|
* list.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param skCase Currently open sleuthkit case.
|
||||||
|
* @param dataSources This of data sources to filter the waypoints by.
|
||||||
|
* Pass a null or empty list to show way points for
|
||||||
|
* all dataSources.
|
||||||
|
*
|
||||||
|
* @param showAll True to get all waypoints.
|
||||||
|
*
|
||||||
|
* @param cntDaysFromRecent Number of days from the most recent time stamp
|
||||||
|
* to get waypoints for. This parameter will be
|
||||||
|
* ignored if showAll is true;
|
||||||
|
*
|
||||||
|
* @param noTimeStamp True to include waypoints without timestamp.
|
||||||
|
* This parameter will be ignored if showAll is
|
||||||
|
* true.
|
||||||
|
*
|
||||||
|
* @param queryCallBack Function to call after the DB query has
|
||||||
|
* completed.
|
||||||
|
*
|
||||||
|
* @throws GeoLocationDataException
|
||||||
|
*/
|
||||||
|
static public void getAllWaypoints(SleuthkitCase skCase, List<DataSource> dataSources, boolean showAll, int cntDaysFromRecent, boolean noTimeStamp, WaypointFilterQueryCallBack queryCallBack) throws GeoLocationDataException {
|
||||||
|
String query = buildQuery(dataSources, showAll, cntDaysFromRecent, noTimeStamp);
|
||||||
|
|
||||||
|
logger.log(Level.INFO, query);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// The CaseDBAccessManager.select function will add a SELECT
|
||||||
|
// to the beginning of the query
|
||||||
|
if (query.startsWith("SELECT")) { //NON-NLS
|
||||||
|
query = query.replaceFirst("SELECT", ""); //NON-NLS
|
||||||
|
}
|
||||||
|
|
||||||
|
skCase.getCaseDbAccessManager().select(query, new CaseDbAccessManager.CaseDbAccessQueryCallback() {
|
||||||
|
@Override
|
||||||
|
public void process(ResultSet rs) {
|
||||||
|
List<Waypoint> waypoints = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
while (rs.next()) {
|
||||||
|
int artifact_type_id = rs.getInt("artifact_type_id"); //NON-NLS
|
||||||
|
long artifact_id = rs.getLong("artifact_id"); //NON-NLS
|
||||||
|
|
||||||
|
BlackboardArtifact.ARTIFACT_TYPE type = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact_type_id);
|
||||||
|
|
||||||
|
waypoints.addAll(getWaypointForArtifact(skCase.getBlackboardArtifact(artifact_id), type));
|
||||||
|
|
||||||
|
}
|
||||||
|
queryCallBack.process(waypoints);
|
||||||
|
} catch (GeoLocationDataException | SQLException | TskCoreException ex) {
|
||||||
|
logger.log(Level.WARNING, "Failed to filter waypoint.", ex); //NON-NLS
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (TskCoreException ex) {
|
||||||
|
logger.log(Level.WARNING, "Failed to filter waypoint.", ex); //NON-NLS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the query for getting a list of waypoints that do not have time
|
||||||
|
* stamps.
|
||||||
|
*
|
||||||
|
* @param dataSources List of data Sources to filter by
|
||||||
|
*
|
||||||
|
* @return SQL SELECT statement
|
||||||
|
*/
|
||||||
|
static private String buildQueryForWaypointsWOTimeStamps(List<DataSource> dataSources) {
|
||||||
|
return String.format(SELECT_WO_TIMESTAMP,
|
||||||
|
String.format(GEO_ARTIFACT_QUERY_ID_ONLY,
|
||||||
|
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID(),
|
||||||
|
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED.getTypeID()),
|
||||||
|
getWaypointListQuery(dataSources));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the query to filter the list of waypoints.
|
||||||
|
*
|
||||||
|
* If showAll is true, the values of cntDaysFromRecent and noTimeStamp are
|
||||||
|
* ignored.
|
||||||
|
*
|
||||||
|
* @param dataSources This of data sources to filter the waypoints by.
|
||||||
|
* Pass a null or empty list to show way points for
|
||||||
|
* all dataSources.
|
||||||
|
*
|
||||||
|
* @param showAll True to get all waypoints.
|
||||||
|
*
|
||||||
|
* @param cntDaysFromRecent Number of days from the most recent time stamp
|
||||||
|
* to get waypoints for. This parameter will be
|
||||||
|
* ignored if showAll is true;
|
||||||
|
*
|
||||||
|
* @param noTimeStamp True to include waypoints without timestamp.
|
||||||
|
* This parameter will be ignored if showAll is
|
||||||
|
* true.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
static private String buildQuery(List<DataSource> dataSources, boolean showAll, int cntDaysFromRecent, boolean noTimeStamp) {
|
||||||
|
String mostRecentQuery = "";
|
||||||
|
|
||||||
|
if (!showAll && cntDaysFromRecent > 0) {
|
||||||
|
mostRecentQuery = String.format("AND value_int64 > (%s)", //NON-NLS
|
||||||
|
String.format(MOST_RECENT_TIME,
|
||||||
|
cntDaysFromRecent,
|
||||||
|
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID(),
|
||||||
|
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED.getTypeID(),
|
||||||
|
getWaypointListQuery(dataSources)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// This givens us all artifact_ID that have time stamp
|
||||||
|
String query = String.format(GEO_ARTIFACT_QUERY,
|
||||||
|
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID(),
|
||||||
|
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED.getTypeID());
|
||||||
|
|
||||||
|
// That are in the list of artifacts for the given data Sources
|
||||||
|
query += String.format("AND artifact_id IN(%s)", getWaypointListQuery(dataSources)); //NON-NLS
|
||||||
|
query += mostRecentQuery;
|
||||||
|
|
||||||
|
if (showAll || noTimeStamp) {
|
||||||
|
query = String.format("%s UNION %s", buildQueryForWaypointsWOTimeStamps(dataSources), query); //NON-NLS
|
||||||
|
}
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the query to get a list of waypoints filted by the given data
|
||||||
|
* sources.
|
||||||
|
*
|
||||||
|
* An artifact is assumed to be a "waypoint" if it has the attributes
|
||||||
|
* TSK_GEO_LATITUDE or TSK_GEO_LATITUDE_START
|
||||||
|
*
|
||||||
|
* @param dataSources A list of data sources to filter by. If the list is
|
||||||
|
* null or empty the data source list will be ignored.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
static private String getWaypointListQuery(List<DataSource> dataSources) {
|
||||||
|
|
||||||
|
if (dataSources == null || dataSources.isEmpty()) {
|
||||||
|
return String.format(GEO_ARTIFACT_QUERY,
|
||||||
|
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE.getTypeID(),
|
||||||
|
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START.getTypeID());
|
||||||
|
}
|
||||||
|
|
||||||
|
String dataSourceList = "";
|
||||||
|
for (DataSource source : dataSources) {
|
||||||
|
dataSourceList += Long.toString(source.getId()) + ",";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dataSourceList.isEmpty()) {
|
||||||
|
// Remove the last ,
|
||||||
|
dataSourceList = dataSourceList.substring(0, dataSourceList.length() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return String.format(GEO_ARTIFACT_WITH_DATA_SOURCES_QUERY,
|
||||||
|
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE.getTypeID(),
|
||||||
|
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START.getTypeID(),
|
||||||
|
dataSourceList);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a Waypoint object for the given Blackboard artifact.
|
||||||
|
*
|
||||||
|
* @param artifact The artifact to create the waypoint from
|
||||||
|
* @param type The type of artifact
|
||||||
|
*
|
||||||
|
* @return A new waypoint object
|
||||||
|
*
|
||||||
|
* @throws GeoLocationDataException
|
||||||
|
*/
|
||||||
|
static private List<Waypoint> getWaypointForArtifact(BlackboardArtifact artifact, BlackboardArtifact.ARTIFACT_TYPE type) throws GeoLocationDataException {
|
||||||
|
List<Waypoint> waypoints = new ArrayList<>();
|
||||||
|
switch (type) {
|
||||||
|
case TSK_METADATA_EXIF:
|
||||||
|
waypoints.add(new EXIFWaypoint(artifact));
|
||||||
|
break;
|
||||||
|
case TSK_GPS_BOOKMARK:
|
||||||
|
waypoints.add(new Waypoint(artifact));
|
||||||
|
break;
|
||||||
|
case TSK_GPS_TRACKPOINT:
|
||||||
|
waypoints.add(new TrackpointWaypoint(artifact));
|
||||||
|
break;
|
||||||
|
case TSK_GPS_SEARCH:
|
||||||
|
waypoints.add(new SearchWaypoint(artifact));
|
||||||
|
break;
|
||||||
|
case TSK_GPS_ROUTE:
|
||||||
|
Route route = new Route(artifact);
|
||||||
|
waypoints.addAll(route.getRoute());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
waypoints.add(new Waypoint(artifact));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return waypoints;
|
||||||
|
}
|
||||||
|
}
|
Before Width: | Height: | Size: 623 B |
Before Width: | Height: | Size: 864 B After Width: | Height: | Size: 864 B |
BIN
Core/src/org/sleuthkit/autopsy/images/blueGeo16.png
Executable file
After Width: | Height: | Size: 741 B |
BIN
Core/src/org/sleuthkit/autopsy/images/blueGeo32.png
Executable file
After Width: | Height: | Size: 1.6 KiB |
BIN
Core/src/org/sleuthkit/autopsy/images/blueGeo64.png
Executable file
After Width: | Height: | Size: 3.9 KiB |
BIN
Core/src/org/sleuthkit/autopsy/images/funnel.png
Executable file
After Width: | Height: | Size: 591 B |
@ -47,6 +47,7 @@ import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
|||||||
import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationDataException;
|
import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationDataException;
|
||||||
import org.sleuthkit.autopsy.geolocation.datamodel.Waypoint;
|
import org.sleuthkit.autopsy.geolocation.datamodel.Waypoint;
|
||||||
import org.sleuthkit.autopsy.geolocation.datamodel.Route;
|
import org.sleuthkit.autopsy.geolocation.datamodel.Route;
|
||||||
|
import org.sleuthkit.autopsy.geolocation.datamodel.WaypointBuilder;
|
||||||
import org.sleuthkit.autopsy.report.ReportBranding;
|
import org.sleuthkit.autopsy.report.ReportBranding;
|
||||||
import org.sleuthkit.autopsy.report.ReportProgressPanel;
|
import org.sleuthkit.autopsy.report.ReportProgressPanel;
|
||||||
import org.sleuthkit.datamodel.AbstractFile;
|
import org.sleuthkit.datamodel.AbstractFile;
|
||||||
@ -331,11 +332,11 @@ class KMLReport implements GeneralReportModule {
|
|||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
void addLocationsToReport(SleuthkitCase skCase, String baseReportDir) throws GeoLocationDataException, IOException {
|
void addLocationsToReport(SleuthkitCase skCase, String baseReportDir) throws GeoLocationDataException, IOException {
|
||||||
addExifMetadataContent(Waypoint.getEXIFWaypoints(skCase), baseReportDir);
|
addExifMetadataContent(WaypointBuilder.getEXIFWaypoints(skCase), baseReportDir);
|
||||||
addWaypoints(Waypoint.getBookmarkWaypoints(skCase), gpsBookmarksFolder, FeatureColor.BLUE, Bundle.Waypoint_Bookmark_Display_String());
|
addWaypoints(WaypointBuilder.getBookmarkWaypoints(skCase), gpsBookmarksFolder, FeatureColor.BLUE, Bundle.Waypoint_Bookmark_Display_String());
|
||||||
addWaypoints(Waypoint.getLastKnownWaypoints(skCase), gpsLastKnownLocationFolder, FeatureColor.PURPLE, Bundle.Waypoint_Last_Known_Display_String());
|
addWaypoints(WaypointBuilder.getLastKnownWaypoints(skCase), gpsLastKnownLocationFolder, FeatureColor.PURPLE, Bundle.Waypoint_Last_Known_Display_String());
|
||||||
addWaypoints(Waypoint.getSearchWaypoints(skCase), gpsSearchesFolder, FeatureColor.WHITE, Bundle.Waypoint_Search_Display_String());
|
addWaypoints(WaypointBuilder.getSearchWaypoints(skCase), gpsSearchesFolder, FeatureColor.WHITE, Bundle.Waypoint_Search_Display_String());
|
||||||
addWaypoints(Waypoint.getTrackpointWaypoints(skCase), gpsTrackpointsFolder, FeatureColor.WHITE, Bundle.Waypoint_Trackpoint_Display_String());
|
addWaypoints(WaypointBuilder.getTrackpointWaypoints(skCase), gpsTrackpointsFolder, FeatureColor.WHITE, Bundle.Waypoint_Trackpoint_Display_String());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -171,7 +171,11 @@ abstract class BulkDrawableFilesTask extends DrawableDbTask {
|
|||||||
// Mark to REBUILT_STALE if some files didnt' have MIME (ingest was still ongoing) or
|
// Mark to REBUILT_STALE if some files didnt' have MIME (ingest was still ongoing) or
|
||||||
// if there was cancellation or errors
|
// if there was cancellation or errors
|
||||||
DrawableDB.DrawableDbBuildStatusEnum datasourceDrawableDBStatus = ((hasFilesWithNoMime == true) || (endedEarly == true)) ? DrawableDB.DrawableDbBuildStatusEnum.REBUILT_STALE : DrawableDB.DrawableDbBuildStatusEnum.COMPLETE;
|
DrawableDB.DrawableDbBuildStatusEnum datasourceDrawableDBStatus = ((hasFilesWithNoMime == true) || (endedEarly == true)) ? DrawableDB.DrawableDbBuildStatusEnum.REBUILT_STALE : DrawableDB.DrawableDbBuildStatusEnum.COMPLETE;
|
||||||
|
try {
|
||||||
taskDB.insertOrUpdateDataSource(dataSourceObjId, datasourceDrawableDBStatus);
|
taskDB.insertOrUpdateDataSource(dataSourceObjId, datasourceDrawableDBStatus);
|
||||||
|
} catch (SQLException ex) {
|
||||||
|
logger.log(Level.SEVERE, String.format("Error updating datasources table (data source object ID = %d, status = %s)", dataSourceObjId, datasourceDrawableDBStatus.toString(), ex)); //NON-NLS
|
||||||
|
}
|
||||||
updateMessage("");
|
updateMessage("");
|
||||||
updateProgress(-1.0);
|
updateProgress(-1.0);
|
||||||
}
|
}
|
||||||
|
@ -801,7 +801,11 @@ public final class ImageGalleryController {
|
|||||||
if (((AutopsyEvent) event).getSourceType() == AutopsyEvent.SourceType.LOCAL) {
|
if (((AutopsyEvent) event).getSourceType() == AutopsyEvent.SourceType.LOCAL) {
|
||||||
Content newDataSource = (Content) event.getNewValue();
|
Content newDataSource = (Content) event.getNewValue();
|
||||||
if (isListeningEnabled()) {
|
if (isListeningEnabled()) {
|
||||||
|
try {
|
||||||
drawableDB.insertOrUpdateDataSource(newDataSource.getId(), DrawableDB.DrawableDbBuildStatusEnum.UNKNOWN);
|
drawableDB.insertOrUpdateDataSource(newDataSource.getId(), DrawableDB.DrawableDbBuildStatusEnum.UNKNOWN);
|
||||||
|
} catch (SQLException ex) {
|
||||||
|
logger.log(Level.SEVERE, String.format("Error updating datasources table (data source object ID = %d, status = %s)", newDataSource.getId(), DrawableDB.DrawableDbBuildStatusEnum.UNKNOWN.toString()), ex); //NON-NLS
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -883,7 +887,7 @@ public final class ImageGalleryController {
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} catch (TskCoreException ex) {
|
} catch (TskCoreException | SQLException ex) {
|
||||||
logger.log(Level.SEVERE, String.format("Failed to handle %s event for %s (objId=%d)", dataSourceEvent.getPropertyName(), dataSource.getName(), dataSourceObjId), ex);
|
logger.log(Level.SEVERE, String.format("Failed to handle %s event for %s (objId=%d)", dataSourceEvent.getPropertyName(), dataSource.getName(), dataSourceObjId), ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -898,7 +902,7 @@ public final class ImageGalleryController {
|
|||||||
* @throws TskCoreException If there is an error adding the data source to
|
* @throws TskCoreException If there is an error adding the data source to
|
||||||
* the database.
|
* the database.
|
||||||
*/
|
*/
|
||||||
private void handleDataSourceAnalysisStarted(DataSourceAnalysisEvent event) throws TskCoreException {
|
private void handleDataSourceAnalysisStarted(DataSourceAnalysisEvent event) throws TskCoreException, SQLException {
|
||||||
if (event.getSourceType() == AutopsyEvent.SourceType.LOCAL && isListeningEnabled()) {
|
if (event.getSourceType() == AutopsyEvent.SourceType.LOCAL && isListeningEnabled()) {
|
||||||
Content dataSource = event.getDataSource();
|
Content dataSource = event.getDataSource();
|
||||||
long dataSourceObjId = dataSource.getId();
|
long dataSourceObjId = dataSource.getId();
|
||||||
@ -919,7 +923,7 @@ public final class ImageGalleryController {
|
|||||||
* @throws TskCoreException If there is an error updating the state ot the
|
* @throws TskCoreException If there is an error updating the state ot the
|
||||||
* data source in the database.
|
* data source in the database.
|
||||||
*/
|
*/
|
||||||
private void handleDataSourceAnalysisCompleted(DataSourceAnalysisEvent event) throws TskCoreException {
|
private void handleDataSourceAnalysisCompleted(DataSourceAnalysisEvent event) throws TskCoreException, SQLException {
|
||||||
if (event.getSourceType() == AutopsyEvent.SourceType.LOCAL) {
|
if (event.getSourceType() == AutopsyEvent.SourceType.LOCAL) {
|
||||||
Content dataSource = event.getDataSource();
|
Content dataSource = event.getDataSource();
|
||||||
long dataSourceObjId = dataSource.getId();
|
long dataSourceObjId = dataSource.getId();
|
||||||
|
@ -49,7 +49,6 @@ import java.util.concurrent.TimeUnit;
|
|||||||
import java.util.concurrent.locks.Lock;
|
import java.util.concurrent.locks.Lock;
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.concurrent.GuardedBy;
|
import javax.annotation.concurrent.GuardedBy;
|
||||||
import javax.swing.SortOrder;
|
import javax.swing.SortOrder;
|
||||||
@ -80,130 +79,160 @@ import org.sleuthkit.datamodel.TskData.DbType;
|
|||||||
import org.sleuthkit.datamodel.TskDataException;
|
import org.sleuthkit.datamodel.TskDataException;
|
||||||
import org.sleuthkit.datamodel.VersionNumber;
|
import org.sleuthkit.datamodel.VersionNumber;
|
||||||
import org.sqlite.SQLiteJDBCLoader;
|
import org.sqlite.SQLiteJDBCLoader;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides access to the drawables database and selected tables in the case
|
* Provides access to the image gallery database and selected tables in the case
|
||||||
* database.
|
* database.
|
||||||
*/
|
*/
|
||||||
public final class DrawableDB {
|
public final class DrawableDB {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(DrawableDB.class.getName());
|
private static final Logger logger = Logger.getLogger(DrawableDB.class.getName());
|
||||||
|
|
||||||
//column name constants//////////////////////
|
/*
|
||||||
private static final String ANALYZED = "analyzed"; //NON-NLS
|
* Schema version management constants.
|
||||||
|
*/
|
||||||
private static final String OBJ_ID = "obj_id"; //NON-NLS
|
private static final VersionNumber IG_STARTING_SCHEMA_VERSION = new VersionNumber(1, 0, 0); // Historical - DO NOT CHANGE
|
||||||
|
private static final VersionNumber IG_SCHEMA_VERSION = new VersionNumber(1, 2, 0); // Current schema version
|
||||||
private static final String HASH_SET_NAME = "hash_set_name"; //NON-NLS
|
|
||||||
|
|
||||||
private static final String GROUPS_TABLENAME = "image_gallery_groups"; //NON-NLS
|
|
||||||
private static final String GROUPS_SEEN_TABLENAME = "image_gallery_groups_seen"; //NON-NLS
|
|
||||||
|
|
||||||
private static final String IG_DB_INFO_TABLE = "image_gallery_db_info";
|
|
||||||
|
|
||||||
private static final String IG_SCHEMA_MAJOR_VERSION_KEY = "IG_SCHEMA_MAJOR_VERSION";
|
private static final String IG_SCHEMA_MAJOR_VERSION_KEY = "IG_SCHEMA_MAJOR_VERSION";
|
||||||
private static final String IG_SCHEMA_MINOR_VERSION_KEY = "IG_SCHEMA_MINOR_VERSION";
|
private static final String IG_SCHEMA_MINOR_VERSION_KEY = "IG_SCHEMA_MINOR_VERSION";
|
||||||
private static final String IG_CREATION_SCHEMA_MAJOR_VERSION_KEY = "IG_CREATION_SCHEMA_MAJOR_VERSION";
|
private static final String IG_CREATION_SCHEMA_MAJOR_VERSION_KEY = "IG_CREATION_SCHEMA_MAJOR_VERSION";
|
||||||
private static final String IG_CREATION_SCHEMA_MINOR_VERSION_KEY = "IG_CREATION_SCHEMA_MINOR_VERSION";
|
private static final String IG_CREATION_SCHEMA_MINOR_VERSION_KEY = "IG_CREATION_SCHEMA_MINOR_VERSION";
|
||||||
|
private static final String DB_INFO_TABLE_NAME = "image_gallery_db_info";
|
||||||
|
|
||||||
private static final VersionNumber IG_STARTING_SCHEMA_VERSION = new VersionNumber(1, 0, 0); // IG Schema Starting version - DO NOT CHANGE
|
/*
|
||||||
private static final VersionNumber IG_SCHEMA_VERSION = new VersionNumber(1, 2, 0); // IG Schema Current version
|
* The image gallery stores data in both the case database and the image
|
||||||
|
* gallery database. The use of image gallery tables in the case database
|
||||||
private PreparedStatement insertHashSetStmt;
|
* enables sharing of selected data between users of multi-user cases. This
|
||||||
|
* is necessary because the image gallery database is otherwise private to
|
||||||
private List<PreparedStatement> preparedStatements = new ArrayList<>();
|
* one node/machine.
|
||||||
|
*
|
||||||
private PreparedStatement removeFileStmt;
|
* TODO: Consider refactoring to separate the image gallery database code
|
||||||
|
* from the case database code.
|
||||||
private PreparedStatement selectHashSetStmt;
|
|
||||||
|
|
||||||
private PreparedStatement selectHashSetNamesStmt;
|
|
||||||
|
|
||||||
private PreparedStatement insertHashHitStmt;
|
|
||||||
|
|
||||||
private PreparedStatement removeHashHitStmt;
|
|
||||||
|
|
||||||
private PreparedStatement updateDataSourceStmt;
|
|
||||||
|
|
||||||
private PreparedStatement updateFileStmt;
|
|
||||||
private PreparedStatement insertFileStmt;
|
|
||||||
|
|
||||||
private PreparedStatement pathGroupStmt;
|
|
||||||
|
|
||||||
private PreparedStatement nameGroupStmt;
|
|
||||||
|
|
||||||
private PreparedStatement created_timeGroupStmt;
|
|
||||||
|
|
||||||
private PreparedStatement modified_timeGroupStmt;
|
|
||||||
|
|
||||||
private PreparedStatement makeGroupStmt;
|
|
||||||
|
|
||||||
private PreparedStatement modelGroupStmt;
|
|
||||||
|
|
||||||
private PreparedStatement analyzedGroupStmt;
|
|
||||||
|
|
||||||
private PreparedStatement hashSetGroupStmt;
|
|
||||||
|
|
||||||
private PreparedStatement pathGroupFilterByDataSrcStmt;
|
|
||||||
|
|
||||||
private PreparedStatement deleteDataSourceStmt;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* map from {@link DrawableAttribute} to the {@link PreparedStatement} that
|
|
||||||
* is used to select groups for that attribute
|
|
||||||
*/
|
*/
|
||||||
|
private static final String CASE_DB_GROUPS_TABLENAME = "image_gallery_groups"; //NON-NLS
|
||||||
|
private static final String CASE_DB_GROUPS_SEEN_TABLENAME = "image_gallery_groups_seen"; //NON-NLS
|
||||||
|
private final SleuthkitCase caseDb;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The image gallery database is an SQLite database, so it has a local file
|
||||||
|
* path. For multi-user cases, there is a private image gallery database for
|
||||||
|
* each node/machine.
|
||||||
|
*/
|
||||||
|
private final Path dbPath;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The write lock of a reentrant read-write lock is used to serialize access
|
||||||
|
* to the image gallery database. Empirically, this provides better
|
||||||
|
* performance than relying on internal SQLite locking.
|
||||||
|
*/
|
||||||
|
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(true); //use fairness policy
|
||||||
|
private final Lock dbLock = rwLock.writeLock();
|
||||||
|
@GuardedBy("dbLock")
|
||||||
|
private Connection con;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Prepared statements.
|
||||||
|
*/
|
||||||
|
private List<PreparedStatement> preparedStatements = new ArrayList<>();
|
||||||
|
private PreparedStatement selectCountDataSourceIDs;
|
||||||
|
private PreparedStatement insertDataSourceStmt;
|
||||||
|
private PreparedStatement updateDataSourceStmt;
|
||||||
|
private PreparedStatement deleteDataSourceStmt;
|
||||||
|
private PreparedStatement insertFileStmt;
|
||||||
|
private PreparedStatement updateFileStmt;
|
||||||
|
private PreparedStatement deleteFileStmt;
|
||||||
|
private PreparedStatement insertHashSetStmt;
|
||||||
|
private PreparedStatement selectHashSetStmt;
|
||||||
|
private PreparedStatement selectHashSetNamesStmt;
|
||||||
|
private PreparedStatement insertHashHitStmt;
|
||||||
|
private PreparedStatement deleteHashHitStmt;
|
||||||
|
private PreparedStatement pathGroupStmt; // Not unused, used via collections below
|
||||||
|
private PreparedStatement nameGroupStmt; // Not unused, used via collections below
|
||||||
|
private PreparedStatement createdTimeGroupStmt; // Not unused, used via collections below
|
||||||
|
private PreparedStatement modifiedTimeGroupStmt; // Not unused, used via collections below
|
||||||
|
private PreparedStatement makeGroupStmt; // Not unused, used via collections below
|
||||||
|
private PreparedStatement modelGroupStmt; // Not unused, used via collections below
|
||||||
|
private PreparedStatement analyzedGroupStmt; // Not unused, used via collections below
|
||||||
|
private PreparedStatement hashSetGroupStmt; // Not unused, used via collections below
|
||||||
|
private PreparedStatement pathGroupFilterByDataSrcStmt; // Not unused, used via collections below
|
||||||
private final Map<DrawableAttribute<?>, PreparedStatement> groupStatementMap = new HashMap<>();
|
private final Map<DrawableAttribute<?>, PreparedStatement> groupStatementMap = new HashMap<>();
|
||||||
private final Map<DrawableAttribute<?>, PreparedStatement> groupStatementFilterByDataSrcMap = new HashMap<>();
|
private final Map<DrawableAttribute<?>, PreparedStatement> groupStatementFilterByDataSrcMap = new HashMap<>();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Various caches are used to reduce the need for database queries.
|
||||||
|
*/
|
||||||
|
private final Cache<String, Boolean> groupCache = CacheBuilder.newBuilder().expireAfterWrite(5, TimeUnit.MINUTES).build();
|
||||||
|
private final Cache<GroupKey<?>, Boolean> groupSeenCache = CacheBuilder.newBuilder().expireAfterWrite(30, TimeUnit.SECONDS).build();
|
||||||
|
private final Set<Long> hasTagsCache = new HashSet<>(); // Object IDs of files with tags
|
||||||
|
private final Set<Long> hasHashHitsCache = new HashSet<>(); // Object IDs of files with hash set hits
|
||||||
|
private final Set<Long> hasExifDataCache = new HashSet<>(); // Object IDs of files with EXIF data (make/model)
|
||||||
|
private final Object cacheLock = new Object();
|
||||||
|
private boolean areCachesLoaded = false;
|
||||||
|
private int cacheBuildCount = 0; // Number of tasks that requested the caches be built
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This class is coupled to the image gallery controller and group manager.
|
||||||
|
*
|
||||||
|
* TODO: It would be better to reduce the coupling so that the controller
|
||||||
|
* and group manager call this class, but this class does not call them.
|
||||||
|
*/
|
||||||
|
private final ImageGalleryController controller;
|
||||||
private final GroupManager groupManager;
|
private final GroupManager groupManager;
|
||||||
|
|
||||||
private final Path dbPath;
|
/*
|
||||||
|
* Make sure the SQLite JDBC driver is loaded.
|
||||||
@GuardedBy("DBLock")
|
*/
|
||||||
private Connection con;
|
static {
|
||||||
|
|
||||||
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(true); //use fairness policy
|
|
||||||
|
|
||||||
private final Lock DBLock = rwLock.writeLock(); // Currently serializing everything with one database connection
|
|
||||||
|
|
||||||
// caches to make inserts / updates faster
|
|
||||||
private Cache<String, Boolean> groupCache = CacheBuilder.newBuilder().expireAfterWrite(5, TimeUnit.MINUTES).build();
|
|
||||||
private final Cache<GroupKey<?>, Boolean> groupSeenCache = CacheBuilder.newBuilder().expireAfterWrite(30, TimeUnit.SECONDS).build();
|
|
||||||
private final Object cacheLock = new Object(); // protects access to the below cache-related objects
|
|
||||||
private boolean areCachesLoaded = false; // if true, the below caches contain valid data
|
|
||||||
private Set<Long> hasTagCache = new HashSet<>(); // contains obj id of files with tags
|
|
||||||
private Set<Long> hasHashCache = new HashSet<>(); // obj id of files with hash set hits
|
|
||||||
private Set<Long> hasExifCache = new HashSet<>(); // obj id of files with EXIF (make/model)
|
|
||||||
private int cacheBuildCount = 0; // number of tasks taht requested the caches be built
|
|
||||||
|
|
||||||
static {//make sure sqlite driver is loaded // possibly redundant
|
|
||||||
try {
|
try {
|
||||||
Class.forName("org.sqlite.JDBC");
|
Class.forName("org.sqlite.JDBC");
|
||||||
} catch (ClassNotFoundException ex) {
|
} catch (ClassNotFoundException ex) {
|
||||||
logger.log(Level.SEVERE, "Failed to load sqlite JDBC driver", ex); //NON-NLS
|
logger.log(Level.SEVERE, "Failed to load sqlite JDBC driver", ex); //NON-NLS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private final SleuthkitCase tskCase;
|
|
||||||
private final ImageGalleryController controller;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enum to track Image gallery db rebuild status for a data source
|
* Enum for tracking the status of the image gallery database with respect
|
||||||
|
* to the data sources in the case.
|
||||||
*
|
*
|
||||||
* DO NOT add in the middle.
|
* IMPORTANT: ADD NEW STATUSES TO THE END OF THE LIST
|
||||||
|
*
|
||||||
|
* TODO: I'm (RC) not sure why this is required, it looks like the enum
|
||||||
|
* element names are stored in the image gallery database. Are the raw
|
||||||
|
* cardinal values used somewhere?
|
||||||
*/
|
*/
|
||||||
public enum DrawableDbBuildStatusEnum {
|
public enum DrawableDbBuildStatusEnum {
|
||||||
UNKNOWN, /// no known status - not yet analyzed
|
/**
|
||||||
IN_PROGRESS, /// ingest or db rebuild is in progress
|
* The data source has been added to the database, but no other data
|
||||||
COMPLETE, /// At least one file in the data source had a MIME type. Ingest filters may have been applied.
|
* pertaining to it has been added.
|
||||||
REBUILT_STALE; /// data source was rebuilt, but MIME types were missing during rebuild
|
*/
|
||||||
|
UNKNOWN,
|
||||||
|
/**
|
||||||
|
* Analyis (an ingest job or image gallery database rebuild) for the
|
||||||
|
* data source is in progress.
|
||||||
|
*/
|
||||||
|
IN_PROGRESS,
|
||||||
|
/**
|
||||||
|
* Analyis (an ingest job or image gallery database rebuild) for the
|
||||||
|
* data source has been completed and at least one file in the data
|
||||||
|
* source has a MIME type (ingest filters may have been applied, so some
|
||||||
|
* files may not have been typed).
|
||||||
|
*/
|
||||||
|
COMPLETE,
|
||||||
|
/**
|
||||||
|
* Analyis (an ingest job or image gallery database rebuild) for the
|
||||||
|
* data source has been completed, but the files for the data source
|
||||||
|
* were not assigned a MIME type (file typing was not enabled).
|
||||||
|
*/
|
||||||
|
REBUILT_STALE;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void dbWriteLock() {
|
private void dbWriteLock() {
|
||||||
DBLock.lock();
|
dbLock.lock();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void dbWriteUnlock() {
|
private void dbWriteUnlock() {
|
||||||
DBLock.unlock();
|
dbLock.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -224,13 +253,13 @@ public final class DrawableDB {
|
|||||||
private DrawableDB(Path dbPath, ImageGalleryController controller) throws IOException, SQLException, TskCoreException {
|
private DrawableDB(Path dbPath, ImageGalleryController controller) throws IOException, SQLException, TskCoreException {
|
||||||
this.dbPath = dbPath;
|
this.dbPath = dbPath;
|
||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
tskCase = this.controller.getCaseDatabase();
|
caseDb = this.controller.getCaseDatabase();
|
||||||
groupManager = this.controller.getGroupManager();
|
groupManager = this.controller.getGroupManager();
|
||||||
Files.createDirectories(this.dbPath.getParent());
|
Files.createDirectories(this.dbPath.getParent());
|
||||||
dbWriteLock();
|
dbWriteLock();
|
||||||
try {
|
try {
|
||||||
con = DriverManager.getConnection("jdbc:sqlite:" + dbPath.toString()); //NON-NLS
|
con = DriverManager.getConnection("jdbc:sqlite:" + dbPath.toString()); //NON-NLS
|
||||||
if (!initializeDBSchema() || !upgradeDBSchema() || !prepareStatements() || !initializeStandardGroups() || !initializeImageList()) {
|
if (!initializeDBSchema() || !upgradeDBSchema() || !prepareStatements() || !initializeStandardGroups() || !removeDeletedDataSources() || !initializeImageList()) {
|
||||||
close();
|
close();
|
||||||
throw new TskCoreException("Failed to initialize drawables database for Image Gallery use"); //NON-NLS
|
throw new TskCoreException("Failed to initialize drawables database for Image Gallery use"); //NON-NLS
|
||||||
}
|
}
|
||||||
@ -241,32 +270,29 @@ public final class DrawableDB {
|
|||||||
|
|
||||||
private boolean prepareStatements() {
|
private boolean prepareStatements() {
|
||||||
try {
|
try {
|
||||||
updateFileStmt = prepareStatement(
|
selectCountDataSourceIDs = prepareStatement("SELECT COUNT(*) FROM datasources WHERE ds_obj_id = ?"); //NON-NLS
|
||||||
"INSERT OR REPLACE INTO drawable_files (obj_id, data_source_obj_id, path, name, created_time, modified_time, make, model, analyzed) " //NON-NLS
|
insertDataSourceStmt = prepareStatement("INSERT INTO datasources (ds_obj_id, drawable_db_build_status) VALUES (?,?)"); //NON-NLS
|
||||||
+ "VALUES (?,?,?,?,?,?,?,?,?)"); //NON-NLS
|
updateDataSourceStmt = prepareStatement("UPDATE datasources SET drawable_db_build_status = ? WHERE ds_obj_id = ?"); //NON-NLS
|
||||||
insertFileStmt = prepareStatement(
|
deleteDataSourceStmt = prepareStatement("DELETE FROM datasources where ds_obj_id = ?"); //NON-NLS
|
||||||
"INSERT OR IGNORE INTO drawable_files (obj_id, data_source_obj_id, path, name, created_time, modified_time, make, model, analyzed) " //NON-NLS
|
insertFileStmt = prepareStatement("INSERT OR IGNORE INTO drawable_files (obj_id, data_source_obj_id, path, name, created_time, modified_time, make, model, analyzed) VALUES (?,?,?,?,?,?,?,?,?)"); //NON-NLS
|
||||||
+ "VALUES (?,?,?,?,?,?,?,?,?)"); //NON-NLS
|
updateFileStmt = prepareStatement("INSERT OR REPLACE INTO drawable_files (obj_id, data_source_obj_id, path, name, created_time, modified_time, make, model, analyzed) VALUES (?,?,?,?,?,?,?,?,?)"); //NON-NLS
|
||||||
updateDataSourceStmt = prepareStatement(
|
deleteFileStmt = prepareStatement("DELETE FROM drawable_files WHERE obj_id = ?"); //NON-NLS
|
||||||
"INSERT OR REPLACE INTO datasources (ds_obj_id, drawable_db_build_status) " //NON-NLS
|
insertHashSetStmt = prepareStatement("INSERT OR IGNORE INTO hash_sets (hash_set_name) VALUES (?)"); //NON-NLS
|
||||||
+ " VALUES (?,?)"); //NON-NLS
|
selectHashSetStmt = prepareStatement("SELECT hash_set_id FROM hash_sets WHERE hash_set_name = ?"); //NON-NLS
|
||||||
removeFileStmt = prepareStatement("DELETE FROM drawable_files WHERE obj_id = ?"); //NON-NLS
|
selectHashSetNamesStmt = prepareStatement("SELECT DISTINCT hash_set_name FROM hash_sets"); //NON-NLS
|
||||||
|
insertHashHitStmt = prepareStatement("INSERT OR IGNORE INTO hash_set_hits (hash_set_id, obj_id) VALUES (?,?)"); //NON-NLS
|
||||||
|
deleteHashHitStmt = prepareStatement("DELETE FROM hash_set_hits WHERE obj_id = ?"); //NON-NLS
|
||||||
pathGroupStmt = prepareStatement("SELECT obj_id , analyzed FROM drawable_files WHERE path = ? ", DrawableAttribute.PATH); //NON-NLS
|
pathGroupStmt = prepareStatement("SELECT obj_id , analyzed FROM drawable_files WHERE path = ? ", DrawableAttribute.PATH); //NON-NLS
|
||||||
nameGroupStmt = prepareStatement("SELECT obj_id , analyzed FROM drawable_files WHERE name = ? ", DrawableAttribute.NAME); //NON-NLS
|
nameGroupStmt = prepareStatement("SELECT obj_id , analyzed FROM drawable_files WHERE name = ? ", DrawableAttribute.NAME); //NON-NLS
|
||||||
created_timeGroupStmt = prepareStatement("SELECT obj_id , analyzed FROM drawable_files WHERE created_time = ? ", DrawableAttribute.CREATED_TIME); //NON-NLS
|
createdTimeGroupStmt = prepareStatement("SELECT obj_id , analyzed FROM drawable_files WHERE created_time = ? ", DrawableAttribute.CREATED_TIME); //NON-NLS
|
||||||
modified_timeGroupStmt = prepareStatement("SELECT obj_id , analyzed FROM drawable_files WHERE modified_time = ? ", DrawableAttribute.MODIFIED_TIME); //NON-NLS
|
modifiedTimeGroupStmt = prepareStatement("SELECT obj_id , analyzed FROM drawable_files WHERE modified_time = ? ", DrawableAttribute.MODIFIED_TIME); //NON-NLS
|
||||||
makeGroupStmt = prepareStatement("SELECT obj_id , analyzed FROM drawable_files WHERE make = ? ", DrawableAttribute.MAKE); //NON-NLS
|
makeGroupStmt = prepareStatement("SELECT obj_id , analyzed FROM drawable_files WHERE make = ? ", DrawableAttribute.MAKE); //NON-NLS
|
||||||
modelGroupStmt = prepareStatement("SELECT obj_id , analyzed FROM drawable_files WHERE model = ? ", DrawableAttribute.MODEL); //NON-NLS
|
modelGroupStmt = prepareStatement("SELECT obj_id , analyzed FROM drawable_files WHERE model = ? ", DrawableAttribute.MODEL); //NON-NLS
|
||||||
analyzedGroupStmt = prepareStatement("SELECT obj_id , analyzed FROM drawable_files WHERE analyzed = ?", DrawableAttribute.ANALYZED); //NON-NLS
|
analyzedGroupStmt = prepareStatement("SELECT obj_id , analyzed FROM drawable_files WHERE analyzed = ?", DrawableAttribute.ANALYZED); //NON-NLS
|
||||||
hashSetGroupStmt = prepareStatement("SELECT drawable_files.obj_id AS obj_id, analyzed FROM drawable_files , hash_sets , hash_set_hits WHERE drawable_files.obj_id = hash_set_hits.obj_id AND hash_sets.hash_set_id = hash_set_hits.hash_set_id AND hash_sets.hash_set_name = ?", DrawableAttribute.HASHSET); //NON-NLS
|
hashSetGroupStmt = prepareStatement("SELECT drawable_files.obj_id AS obj_id, analyzed FROM drawable_files , hash_sets , hash_set_hits WHERE drawable_files.obj_id = hash_set_hits.obj_id AND hash_sets.hash_set_id = hash_set_hits.hash_set_id AND hash_sets.hash_set_name = ?", DrawableAttribute.HASHSET); //NON-NLS
|
||||||
pathGroupFilterByDataSrcStmt = prepareFilterByDataSrcStatement("SELECT obj_id , analyzed FROM drawable_files WHERE path = ? AND data_source_obj_id = ?", DrawableAttribute.PATH);
|
pathGroupFilterByDataSrcStmt = prepareFilterByDataSrcStatement("SELECT obj_id , analyzed FROM drawable_files WHERE path = ? AND data_source_obj_id = ?", DrawableAttribute.PATH);
|
||||||
selectHashSetNamesStmt = prepareStatement("SELECT DISTINCT hash_set_name FROM hash_sets"); //NON-NLS
|
|
||||||
insertHashSetStmt = prepareStatement("INSERT OR IGNORE INTO hash_sets (hash_set_name) VALUES (?)"); //NON-NLS
|
|
||||||
selectHashSetStmt = prepareStatement("SELECT hash_set_id FROM hash_sets WHERE hash_set_name = ?"); //NON-NLS
|
|
||||||
insertHashHitStmt = prepareStatement("INSERT OR IGNORE INTO hash_set_hits (hash_set_id, obj_id) VALUES (?,?)"); //NON-NLS
|
|
||||||
removeHashHitStmt = prepareStatement("DELETE FROM hash_set_hits WHERE obj_id = ?"); //NON-NLS
|
|
||||||
deleteDataSourceStmt = prepareStatement("DELETE FROM datasources where ds_obj_id = ?"); //NON-NLS
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
} catch (TskCoreException | SQLException ex) {
|
} catch (TskCoreException | SQLException ex) {
|
||||||
logger.log(Level.SEVERE, "Failed to prepare all statements", ex); //NON-NLS
|
logger.log(Level.SEVERE, "Failed to prepare all statements", ex); //NON-NLS
|
||||||
return false;
|
return false;
|
||||||
@ -276,7 +302,7 @@ public final class DrawableDB {
|
|||||||
private boolean initializeStandardGroups() {
|
private boolean initializeStandardGroups() {
|
||||||
CaseDbTransaction caseDbTransaction = null;
|
CaseDbTransaction caseDbTransaction = null;
|
||||||
try {
|
try {
|
||||||
caseDbTransaction = tskCase.beginTransaction();
|
caseDbTransaction = caseDb.beginTransaction();
|
||||||
for (DhsImageCategory cat : DhsImageCategory.values()) {
|
for (DhsImageCategory cat : DhsImageCategory.values()) {
|
||||||
insertGroup(cat.getDisplayName(), DrawableAttribute.CATEGORY, caseDbTransaction);
|
insertGroup(cat.getDisplayName(), DrawableAttribute.CATEGORY, caseDbTransaction);
|
||||||
}
|
}
|
||||||
@ -374,6 +400,62 @@ public final class DrawableDB {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes any data sources from the local drawables database that have been
|
||||||
|
* deleted from the case database. This is necessary for multi-user cases
|
||||||
|
* where the case database is shared, but each user has his or her own local
|
||||||
|
* drawables database and may not have had the case open when a data source
|
||||||
|
* was deleted.
|
||||||
|
*
|
||||||
|
* @return True on success, false on failure.
|
||||||
|
*/
|
||||||
|
private boolean removeDeletedDataSources() {
|
||||||
|
dbWriteLock();
|
||||||
|
try (SleuthkitCase.CaseDbQuery caseDbQuery = caseDb.executeQuery("SELECT obj_id FROM data_source_info"); //NON-NLS
|
||||||
|
Statement drawablesDbStmt = con.createStatement()) {
|
||||||
|
/*
|
||||||
|
* Get the data source object IDs from the case database.
|
||||||
|
*/
|
||||||
|
ResultSet caseDbResults = caseDbQuery.getResultSet();
|
||||||
|
Set<Long> currentDataSourceObjIDs = new HashSet<>();
|
||||||
|
while (caseDbResults.next()) {
|
||||||
|
currentDataSourceObjIDs.add(caseDbResults.getLong(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the data source object IDs from the drawables database and
|
||||||
|
* determine which ones, if any, have been deleted from the case
|
||||||
|
* database.
|
||||||
|
*/
|
||||||
|
List<Long> staleDataSourceObjIDs = new ArrayList<>();
|
||||||
|
try (ResultSet drawablesDbResults = drawablesDbStmt.executeQuery("SELECT ds_obj_id FROM datasources")) { //NON-NLS
|
||||||
|
while (drawablesDbResults.next()) {
|
||||||
|
long dataSourceObjID = drawablesDbResults.getLong(1);
|
||||||
|
if (!currentDataSourceObjIDs.contains(dataSourceObjID)) {
|
||||||
|
staleDataSourceObjIDs.add(dataSourceObjID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Delete the surplus data sources from this local drawables
|
||||||
|
* database. The delete cascades.
|
||||||
|
*/
|
||||||
|
if (!staleDataSourceObjIDs.isEmpty()) {
|
||||||
|
String deleteCommand = "DELETE FROM datasources where ds_obj_id IN (" + StringUtils.join(staleDataSourceObjIDs, ',') + ")"; //NON-NLS
|
||||||
|
drawablesDbStmt.execute(deleteCommand);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (TskCoreException | SQLException ex) {
|
||||||
|
logger.log(Level.SEVERE, "Failed to remove deleted data sources from drawables database", ex); //NON-NLS
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
dbWriteUnlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Public factory method. Creates and opens a connection to a new database *
|
* Public factory method. Creates and opens a connection to a new database *
|
||||||
* at the given path. If there is already a db at the path, it is checked
|
* at the given path. If there is already a db at the path, it is checked
|
||||||
@ -538,21 +620,21 @@ public final class DrawableDB {
|
|||||||
|
|
||||||
// Check if the database is new or an existing database
|
// Check if the database is new or an existing database
|
||||||
drawableDbTablesExist = doesTableExist("drawable_files");
|
drawableDbTablesExist = doesTableExist("drawable_files");
|
||||||
if (false == doesTableExist(IG_DB_INFO_TABLE)) {
|
if (false == doesTableExist(DB_INFO_TABLE_NAME)) {
|
||||||
try {
|
try {
|
||||||
VersionNumber ig_creation_schema_version = drawableDbTablesExist
|
VersionNumber ig_creation_schema_version = drawableDbTablesExist
|
||||||
? IG_STARTING_SCHEMA_VERSION
|
? IG_STARTING_SCHEMA_VERSION
|
||||||
: IG_SCHEMA_VERSION;
|
: IG_SCHEMA_VERSION;
|
||||||
|
|
||||||
stmt.execute("CREATE TABLE IF NOT EXISTS " + IG_DB_INFO_TABLE + " (name TEXT PRIMARY KEY, value TEXT NOT NULL)");
|
stmt.execute("CREATE TABLE IF NOT EXISTS " + DB_INFO_TABLE_NAME + " (name TEXT PRIMARY KEY, value TEXT NOT NULL)");
|
||||||
|
|
||||||
// backfill creation schema ver
|
// backfill creation schema ver
|
||||||
stmt.execute(String.format("INSERT INTO %s (name, value) VALUES ('%s', '%s')", IG_DB_INFO_TABLE, IG_CREATION_SCHEMA_MAJOR_VERSION_KEY, ig_creation_schema_version.getMajor()));
|
stmt.execute(String.format("INSERT INTO %s (name, value) VALUES ('%s', '%s')", DB_INFO_TABLE_NAME, IG_CREATION_SCHEMA_MAJOR_VERSION_KEY, ig_creation_schema_version.getMajor()));
|
||||||
stmt.execute(String.format("INSERT INTO %s (name, value) VALUES ('%s', '%s')", IG_DB_INFO_TABLE, IG_CREATION_SCHEMA_MINOR_VERSION_KEY, ig_creation_schema_version.getMinor()));
|
stmt.execute(String.format("INSERT INTO %s (name, value) VALUES ('%s', '%s')", DB_INFO_TABLE_NAME, IG_CREATION_SCHEMA_MINOR_VERSION_KEY, ig_creation_schema_version.getMinor()));
|
||||||
|
|
||||||
// set current schema ver: at DB initialization - current version is same as starting version
|
// set current schema ver: at DB initialization - current version is same as starting version
|
||||||
stmt.execute(String.format("INSERT INTO %s (name, value) VALUES ('%s', '%s')", IG_DB_INFO_TABLE, IG_SCHEMA_MAJOR_VERSION_KEY, ig_creation_schema_version.getMajor()));
|
stmt.execute(String.format("INSERT INTO %s (name, value) VALUES ('%s', '%s')", DB_INFO_TABLE_NAME, IG_SCHEMA_MAJOR_VERSION_KEY, ig_creation_schema_version.getMajor()));
|
||||||
stmt.execute(String.format("INSERT INTO %s (name, value) VALUES ('%s', '%s')", IG_DB_INFO_TABLE, IG_SCHEMA_MINOR_VERSION_KEY, ig_creation_schema_version.getMinor()));
|
stmt.execute(String.format("INSERT INTO %s (name, value) VALUES ('%s', '%s')", DB_INFO_TABLE_NAME, IG_SCHEMA_MINOR_VERSION_KEY, ig_creation_schema_version.getMinor()));
|
||||||
|
|
||||||
} catch (SQLException ex) {
|
} catch (SQLException ex) {
|
||||||
logger.log(Level.SEVERE, "Failed to create ig_db_info table", ex); //NON-NLS
|
logger.log(Level.SEVERE, "Failed to create ig_db_info table", ex); //NON-NLS
|
||||||
@ -653,10 +735,10 @@ public final class DrawableDB {
|
|||||||
/*
|
/*
|
||||||
* Create tables in the case database.
|
* Create tables in the case database.
|
||||||
*/
|
*/
|
||||||
String autogenKeyType = (DbType.POSTGRESQL == tskCase.getDatabaseType()) ? "BIGSERIAL" : "INTEGER";
|
String autogenKeyType = (DbType.POSTGRESQL == caseDb.getDatabaseType()) ? "BIGSERIAL" : "INTEGER";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
boolean caseDbTablesExist = tskCase.getCaseDbAccessManager().tableExists(GROUPS_TABLENAME);
|
boolean caseDbTablesExist = caseDb.getCaseDbAccessManager().tableExists(CASE_DB_GROUPS_TABLENAME);
|
||||||
VersionNumber ig_creation_schema_version = caseDbTablesExist
|
VersionNumber ig_creation_schema_version = caseDbTablesExist
|
||||||
? IG_STARTING_SCHEMA_VERSION
|
? IG_STARTING_SCHEMA_VERSION
|
||||||
: IG_SCHEMA_VERSION;
|
: IG_SCHEMA_VERSION;
|
||||||
@ -664,7 +746,7 @@ public final class DrawableDB {
|
|||||||
String tableSchema = "( id " + autogenKeyType + " PRIMARY KEY, "
|
String tableSchema = "( id " + autogenKeyType + " PRIMARY KEY, "
|
||||||
+ " name TEXT UNIQUE NOT NULL,"
|
+ " name TEXT UNIQUE NOT NULL,"
|
||||||
+ " value TEXT NOT NULL )";
|
+ " value TEXT NOT NULL )";
|
||||||
tskCase.getCaseDbAccessManager().createTable(IG_DB_INFO_TABLE, tableSchema);
|
caseDb.getCaseDbAccessManager().createTable(DB_INFO_TABLE_NAME, tableSchema);
|
||||||
|
|
||||||
// backfill creation version
|
// backfill creation version
|
||||||
String creationMajorVerSQL = String.format(" (name, value) VALUES ('%s', '%s')", IG_CREATION_SCHEMA_MAJOR_VERSION_KEY, ig_creation_schema_version.getMajor());
|
String creationMajorVerSQL = String.format(" (name, value) VALUES ('%s', '%s')", IG_CREATION_SCHEMA_MAJOR_VERSION_KEY, ig_creation_schema_version.getMajor());
|
||||||
@ -674,7 +756,7 @@ public final class DrawableDB {
|
|||||||
String currentMajorVerSQL = String.format(" (name, value) VALUES ('%s', '%s')", IG_SCHEMA_MAJOR_VERSION_KEY, ig_creation_schema_version.getMajor());
|
String currentMajorVerSQL = String.format(" (name, value) VALUES ('%s', '%s')", IG_SCHEMA_MAJOR_VERSION_KEY, ig_creation_schema_version.getMajor());
|
||||||
String currentMinorVerSQL = String.format(" (name, value) VALUES ('%s', '%s')", IG_SCHEMA_MINOR_VERSION_KEY, ig_creation_schema_version.getMinor());
|
String currentMinorVerSQL = String.format(" (name, value) VALUES ('%s', '%s')", IG_SCHEMA_MINOR_VERSION_KEY, ig_creation_schema_version.getMinor());
|
||||||
|
|
||||||
if (DbType.POSTGRESQL == tskCase.getDatabaseType()) {
|
if (DbType.POSTGRESQL == caseDb.getDatabaseType()) {
|
||||||
creationMajorVerSQL += " ON CONFLICT DO NOTHING ";
|
creationMajorVerSQL += " ON CONFLICT DO NOTHING ";
|
||||||
creationMinorVerSQL += " ON CONFLICT DO NOTHING ";
|
creationMinorVerSQL += " ON CONFLICT DO NOTHING ";
|
||||||
|
|
||||||
@ -682,11 +764,11 @@ public final class DrawableDB {
|
|||||||
currentMinorVerSQL += " ON CONFLICT DO NOTHING ";
|
currentMinorVerSQL += " ON CONFLICT DO NOTHING ";
|
||||||
}
|
}
|
||||||
|
|
||||||
tskCase.getCaseDbAccessManager().insert(IG_DB_INFO_TABLE, creationMajorVerSQL);
|
caseDb.getCaseDbAccessManager().insert(DB_INFO_TABLE_NAME, creationMajorVerSQL);
|
||||||
tskCase.getCaseDbAccessManager().insert(IG_DB_INFO_TABLE, creationMinorVerSQL);
|
caseDb.getCaseDbAccessManager().insert(DB_INFO_TABLE_NAME, creationMinorVerSQL);
|
||||||
|
|
||||||
tskCase.getCaseDbAccessManager().insert(IG_DB_INFO_TABLE, currentMajorVerSQL);
|
caseDb.getCaseDbAccessManager().insert(DB_INFO_TABLE_NAME, currentMajorVerSQL);
|
||||||
tskCase.getCaseDbAccessManager().insert(IG_DB_INFO_TABLE, currentMinorVerSQL);
|
caseDb.getCaseDbAccessManager().insert(DB_INFO_TABLE_NAME, currentMinorVerSQL);
|
||||||
|
|
||||||
} catch (TskCoreException ex) {
|
} catch (TskCoreException ex) {
|
||||||
logger.log(Level.SEVERE, "Failed to create ig_db_info table in Case database", ex); //NON-NLS
|
logger.log(Level.SEVERE, "Failed to create ig_db_info table in Case database", ex); //NON-NLS
|
||||||
@ -702,9 +784,9 @@ public final class DrawableDB {
|
|||||||
+ " is_analyzed integer DEFAULT 0, "
|
+ " is_analyzed integer DEFAULT 0, "
|
||||||
+ " UNIQUE(data_source_obj_id, value, attribute) )"; //NON-NLS
|
+ " UNIQUE(data_source_obj_id, value, attribute) )"; //NON-NLS
|
||||||
|
|
||||||
tskCase.getCaseDbAccessManager().createTable(GROUPS_TABLENAME, tableSchema);
|
caseDb.getCaseDbAccessManager().createTable(CASE_DB_GROUPS_TABLENAME, tableSchema);
|
||||||
} catch (TskCoreException ex) {
|
} catch (TskCoreException ex) {
|
||||||
logger.log(Level.SEVERE, String.format("Failed to create %s table in case database", GROUPS_TABLENAME), ex); //NON-NLS
|
logger.log(Level.SEVERE, String.format("Failed to create %s table in case database", CASE_DB_GROUPS_TABLENAME), ex); //NON-NLS
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@ -715,13 +797,13 @@ public final class DrawableDB {
|
|||||||
+ " examiner_id integer not null, " //NON-NLS
|
+ " examiner_id integer not null, " //NON-NLS
|
||||||
+ " seen integer DEFAULT 0, " //NON-NLS
|
+ " seen integer DEFAULT 0, " //NON-NLS
|
||||||
+ " UNIQUE(group_id, examiner_id),"
|
+ " UNIQUE(group_id, examiner_id),"
|
||||||
+ " FOREIGN KEY(group_id) REFERENCES " + GROUPS_TABLENAME + "(group_id) ON DELETE CASCADE,"
|
+ " FOREIGN KEY(group_id) REFERENCES " + CASE_DB_GROUPS_TABLENAME + "(group_id) ON DELETE CASCADE,"
|
||||||
+ " FOREIGN KEY(examiner_id) REFERENCES tsk_examiners(examiner_id)"
|
+ " FOREIGN KEY(examiner_id) REFERENCES tsk_examiners(examiner_id)"
|
||||||
+ " )"; //NON-NLS
|
+ " )"; //NON-NLS
|
||||||
|
|
||||||
tskCase.getCaseDbAccessManager().createTable(GROUPS_SEEN_TABLENAME, tableSchema);
|
caseDb.getCaseDbAccessManager().createTable(CASE_DB_GROUPS_SEEN_TABLENAME, tableSchema);
|
||||||
} catch (TskCoreException ex) {
|
} catch (TskCoreException ex) {
|
||||||
logger.log(Level.SEVERE, String.format("Failed to create %s table in case database", GROUPS_SEEN_TABLENAME), ex); //NON-NLS
|
logger.log(Level.SEVERE, String.format("Failed to create %s table in case database", CASE_DB_GROUPS_SEEN_TABLENAME), ex); //NON-NLS
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -748,7 +830,7 @@ public final class DrawableDB {
|
|||||||
try {
|
try {
|
||||||
int majorVersion = -1;
|
int majorVersion = -1;
|
||||||
String majorVersionStr = null;
|
String majorVersionStr = null;
|
||||||
resultSet = statement.executeQuery(String.format("SELECT value FROM %s WHERE name='%s'", IG_DB_INFO_TABLE, IG_SCHEMA_MAJOR_VERSION_KEY));
|
resultSet = statement.executeQuery(String.format("SELECT value FROM %s WHERE name='%s'", DB_INFO_TABLE_NAME, IG_SCHEMA_MAJOR_VERSION_KEY));
|
||||||
if (resultSet.next()) {
|
if (resultSet.next()) {
|
||||||
majorVersionStr = resultSet.getString("value");
|
majorVersionStr = resultSet.getString("value");
|
||||||
try {
|
try {
|
||||||
@ -762,7 +844,7 @@ public final class DrawableDB {
|
|||||||
|
|
||||||
int minorVersion = -1;
|
int minorVersion = -1;
|
||||||
String minorVersionStr = null;
|
String minorVersionStr = null;
|
||||||
resultSet = statement.executeQuery(String.format("SELECT value FROM %s WHERE name='%s'", IG_DB_INFO_TABLE, IG_SCHEMA_MINOR_VERSION_KEY));
|
resultSet = statement.executeQuery(String.format("SELECT value FROM %s WHERE name='%s'", DB_INFO_TABLE_NAME, IG_SCHEMA_MINOR_VERSION_KEY));
|
||||||
if (resultSet.next()) {
|
if (resultSet.next()) {
|
||||||
minorVersionStr = resultSet.getString("value");
|
minorVersionStr = resultSet.getString("value");
|
||||||
try {
|
try {
|
||||||
@ -827,8 +909,8 @@ public final class DrawableDB {
|
|||||||
GetSchemaVersionQueryResultProcessor minorVersionResultProcessor = new GetSchemaVersionQueryResultProcessor();
|
GetSchemaVersionQueryResultProcessor minorVersionResultProcessor = new GetSchemaVersionQueryResultProcessor();
|
||||||
|
|
||||||
String versionQueryTemplate = "value FROM %s WHERE name = \'%s\' ";
|
String versionQueryTemplate = "value FROM %s WHERE name = \'%s\' ";
|
||||||
tskCase.getCaseDbAccessManager().select(String.format(versionQueryTemplate, IG_DB_INFO_TABLE, IG_SCHEMA_MAJOR_VERSION_KEY), majorVersionResultProcessor);
|
caseDb.getCaseDbAccessManager().select(String.format(versionQueryTemplate, DB_INFO_TABLE_NAME, IG_SCHEMA_MAJOR_VERSION_KEY), majorVersionResultProcessor);
|
||||||
tskCase.getCaseDbAccessManager().select(String.format(versionQueryTemplate, IG_DB_INFO_TABLE, IG_SCHEMA_MINOR_VERSION_KEY), minorVersionResultProcessor);
|
caseDb.getCaseDbAccessManager().select(String.format(versionQueryTemplate, DB_INFO_TABLE_NAME, IG_SCHEMA_MINOR_VERSION_KEY), minorVersionResultProcessor);
|
||||||
|
|
||||||
return new VersionNumber(majorVersionResultProcessor.getVersion(), minorVersionResultProcessor.getVersion(), 0);
|
return new VersionNumber(majorVersionResultProcessor.getVersion(), minorVersionResultProcessor.getVersion(), 0);
|
||||||
}
|
}
|
||||||
@ -852,8 +934,8 @@ public final class DrawableDB {
|
|||||||
Statement statement = con.createStatement();
|
Statement statement = con.createStatement();
|
||||||
|
|
||||||
// update schema version
|
// update schema version
|
||||||
statement.execute(String.format("UPDATE %s SET value = '%s' WHERE name = '%s'", IG_DB_INFO_TABLE, version.getMajor(), IG_SCHEMA_MAJOR_VERSION_KEY));
|
statement.execute(String.format("UPDATE %s SET value = '%s' WHERE name = '%s'", DB_INFO_TABLE_NAME, version.getMajor(), IG_SCHEMA_MAJOR_VERSION_KEY));
|
||||||
statement.execute(String.format("UPDATE %s SET value = '%s' WHERE name = '%s'", IG_DB_INFO_TABLE, version.getMinor(), IG_SCHEMA_MINOR_VERSION_KEY));
|
statement.execute(String.format("UPDATE %s SET value = '%s' WHERE name = '%s'", DB_INFO_TABLE_NAME, version.getMinor(), IG_SCHEMA_MINOR_VERSION_KEY));
|
||||||
|
|
||||||
statement.close();
|
statement.close();
|
||||||
} finally {
|
} finally {
|
||||||
@ -872,8 +954,8 @@ public final class DrawableDB {
|
|||||||
private void updateCaseDbIgSchemaVersion(VersionNumber version, CaseDbTransaction caseDbTransaction) throws TskCoreException {
|
private void updateCaseDbIgSchemaVersion(VersionNumber version, CaseDbTransaction caseDbTransaction) throws TskCoreException {
|
||||||
|
|
||||||
String updateSQLTemplate = " SET value = %s WHERE name = '%s' ";
|
String updateSQLTemplate = " SET value = %s WHERE name = '%s' ";
|
||||||
tskCase.getCaseDbAccessManager().update(IG_DB_INFO_TABLE, String.format(updateSQLTemplate, version.getMajor(), IG_SCHEMA_MAJOR_VERSION_KEY), caseDbTransaction);
|
caseDb.getCaseDbAccessManager().update(DB_INFO_TABLE_NAME, String.format(updateSQLTemplate, version.getMajor(), IG_SCHEMA_MAJOR_VERSION_KEY), caseDbTransaction);
|
||||||
tskCase.getCaseDbAccessManager().update(IG_DB_INFO_TABLE, String.format(updateSQLTemplate, version.getMinor(), IG_SCHEMA_MINOR_VERSION_KEY), caseDbTransaction);
|
caseDb.getCaseDbAccessManager().update(DB_INFO_TABLE_NAME, String.format(updateSQLTemplate, version.getMinor(), IG_SCHEMA_MINOR_VERSION_KEY), caseDbTransaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -891,7 +973,7 @@ public final class DrawableDB {
|
|||||||
VersionNumber caseDbIgSchemaVersion = getCaseDbIgSchemaVersion();
|
VersionNumber caseDbIgSchemaVersion = getCaseDbIgSchemaVersion();
|
||||||
|
|
||||||
// Upgrade Schema in both DrawableDB and CaseDB
|
// Upgrade Schema in both DrawableDB and CaseDB
|
||||||
CaseDbTransaction caseDbTransaction = tskCase.beginTransaction();
|
CaseDbTransaction caseDbTransaction = caseDb.beginTransaction();
|
||||||
DrawableTransaction transaction = beginTransaction();
|
DrawableTransaction transaction = beginTransaction();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -948,8 +1030,8 @@ public final class DrawableDB {
|
|||||||
|
|
||||||
// Add a 'is_analyzed' column to groups table in CaseDB
|
// Add a 'is_analyzed' column to groups table in CaseDB
|
||||||
String alterSQL = " ADD COLUMN is_analyzed integer DEFAULT 1 "; //NON-NLS
|
String alterSQL = " ADD COLUMN is_analyzed integer DEFAULT 1 "; //NON-NLS
|
||||||
if (false == tskCase.getCaseDbAccessManager().columnExists(GROUPS_TABLENAME, "is_analyzed", caseDbTransaction)) {
|
if (false == caseDb.getCaseDbAccessManager().columnExists(CASE_DB_GROUPS_TABLENAME, "is_analyzed", caseDbTransaction)) {
|
||||||
tskCase.getCaseDbAccessManager().alterTable(GROUPS_TABLENAME, alterSQL, caseDbTransaction);
|
caseDb.getCaseDbAccessManager().alterTable(CASE_DB_GROUPS_TABLENAME, alterSQL, caseDbTransaction);
|
||||||
}
|
}
|
||||||
return new VersionNumber(1, 1, 0);
|
return new VersionNumber(1, 1, 0);
|
||||||
}
|
}
|
||||||
@ -1047,7 +1129,7 @@ public final class DrawableDB {
|
|||||||
*/
|
*/
|
||||||
Set<String> getHashSetsForFile(long fileID) throws TskCoreException {
|
Set<String> getHashSetsForFile(long fileID) throws TskCoreException {
|
||||||
Set<String> hashNames = new HashSet<>();
|
Set<String> hashNames = new HashSet<>();
|
||||||
ArrayList<BlackboardArtifact> artifacts = tskCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT, fileID);
|
ArrayList<BlackboardArtifact> artifacts = caseDb.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT, fileID);
|
||||||
|
|
||||||
for (BlackboardArtifact a : artifacts) {
|
for (BlackboardArtifact a : artifacts) {
|
||||||
BlackboardAttribute attribute = a.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME));
|
BlackboardAttribute attribute = a.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME));
|
||||||
@ -1069,7 +1151,7 @@ public final class DrawableDB {
|
|||||||
dbWriteLock();
|
dbWriteLock();
|
||||||
try (ResultSet rs = selectHashSetNamesStmt.executeQuery();) {
|
try (ResultSet rs = selectHashSetNamesStmt.executeQuery();) {
|
||||||
while (rs.next()) {
|
while (rs.next()) {
|
||||||
names.add(rs.getString(HASH_SET_NAME));
|
names.add(rs.getString("hash_set_name"));
|
||||||
}
|
}
|
||||||
} catch (SQLException sQLException) {
|
} catch (SQLException sQLException) {
|
||||||
logger.log(Level.WARNING, "failed to get hash set names", sQLException); //NON-NLS
|
logger.log(Level.WARNING, "failed to get hash set names", sQLException); //NON-NLS
|
||||||
@ -1081,7 +1163,7 @@ public final class DrawableDB {
|
|||||||
|
|
||||||
static private String getGroupIdQuery(GroupKey<?> groupKey) {
|
static private String getGroupIdQuery(GroupKey<?> groupKey) {
|
||||||
// query to find the group id from attribute/value
|
// query to find the group id from attribute/value
|
||||||
return String.format(" SELECT group_id FROM " + GROUPS_TABLENAME
|
return String.format(" SELECT group_id FROM " + CASE_DB_GROUPS_TABLENAME
|
||||||
+ " WHERE attribute = \'%s\' AND value = \'%s\' AND data_source_obj_id = %d",
|
+ " WHERE attribute = \'%s\' AND value = \'%s\' AND data_source_obj_id = %d",
|
||||||
SleuthkitCase.escapeSingleQuotes(groupKey.getAttribute().attrName.toString()),
|
SleuthkitCase.escapeSingleQuotes(groupKey.getAttribute().attrName.toString()),
|
||||||
SleuthkitCase.escapeSingleQuotes(groupKey.getValueDisplayName()),
|
SleuthkitCase.escapeSingleQuotes(groupKey.getValueDisplayName()),
|
||||||
@ -1131,12 +1213,12 @@ public final class DrawableDB {
|
|||||||
GroupSeenQueryResultProcessor queryResultProcessor = new GroupSeenQueryResultProcessor();
|
GroupSeenQueryResultProcessor queryResultProcessor = new GroupSeenQueryResultProcessor();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String groupSeenQueryStmt = "COUNT(*) as count FROM " + GROUPS_SEEN_TABLENAME
|
String groupSeenQueryStmt = "COUNT(*) as count FROM " + CASE_DB_GROUPS_SEEN_TABLENAME
|
||||||
+ " WHERE seen = 1 "
|
+ " WHERE seen = 1 "
|
||||||
+ " AND group_id in ( " + getGroupIdQuery(groupKey) + ")"
|
+ " AND group_id in ( " + getGroupIdQuery(groupKey) + ")"
|
||||||
+ (examinerId > 0 ? " AND examiner_id = " + examinerId : "");// query to find the group id from attribute/value
|
+ (examinerId > 0 ? " AND examiner_id = " + examinerId : "");// query to find the group id from attribute/value
|
||||||
|
|
||||||
tskCase.getCaseDbAccessManager().select(groupSeenQueryStmt, queryResultProcessor);
|
caseDb.getCaseDbAccessManager().select(groupSeenQueryStmt, queryResultProcessor);
|
||||||
return queryResultProcessor.get();
|
return queryResultProcessor.get();
|
||||||
} catch (ExecutionException | InterruptedException | TskCoreException ex) {
|
} catch (ExecutionException | InterruptedException | TskCoreException ex) {
|
||||||
String msg = String.format("Failed to get is group seen for group key %s", groupKey.getValueDisplayName()); //NON-NLS
|
String msg = String.format("Failed to get is group seen for group key %s", groupKey.getValueDisplayName()); //NON-NLS
|
||||||
@ -1167,18 +1249,18 @@ public final class DrawableDB {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// query to find the group id from attribute/value
|
// query to find the group id from attribute/value
|
||||||
String innerQuery = String.format("( SELECT group_id FROM " + GROUPS_TABLENAME //NON-NLS
|
String innerQuery = String.format("( SELECT group_id FROM " + CASE_DB_GROUPS_TABLENAME//NON-NLS
|
||||||
+ " WHERE attribute = \'%s\' AND value = \'%s\' and data_source_obj_id = %d )", //NON-NLS
|
+ " WHERE attribute = \'%s\' AND value = \'%s\' and data_source_obj_id = %d )", //NON-NLS
|
||||||
SleuthkitCase.escapeSingleQuotes(groupKey.getAttribute().attrName.toString()),
|
SleuthkitCase.escapeSingleQuotes(groupKey.getAttribute().attrName.toString()),
|
||||||
SleuthkitCase.escapeSingleQuotes(groupKey.getValueDisplayName()),
|
SleuthkitCase.escapeSingleQuotes(groupKey.getValueDisplayName()),
|
||||||
groupKey.getAttribute() == DrawableAttribute.PATH ? groupKey.getDataSourceObjId() : 0);
|
groupKey.getAttribute() == DrawableAttribute.PATH ? groupKey.getDataSourceObjId() : 0);
|
||||||
|
|
||||||
String insertSQL = String.format(" (group_id, examiner_id, seen) VALUES (%s, %d, %d)", innerQuery, examinerID, 1); //NON-NLS
|
String insertSQL = String.format(" (group_id, examiner_id, seen) VALUES (%s, %d, %d)", innerQuery, examinerID, 1); //NON-NLS
|
||||||
if (DbType.POSTGRESQL == tskCase.getDatabaseType()) {
|
if (DbType.POSTGRESQL == caseDb.getDatabaseType()) {
|
||||||
insertSQL += String.format(" ON CONFLICT (group_id, examiner_id) DO UPDATE SET seen = %d", 1); //NON-NLS
|
insertSQL += String.format(" ON CONFLICT (group_id, examiner_id) DO UPDATE SET seen = %d", 1); //NON-NLS
|
||||||
}
|
}
|
||||||
|
|
||||||
tskCase.getCaseDbAccessManager().insertOrUpdate(GROUPS_SEEN_TABLENAME, insertSQL);
|
caseDb.getCaseDbAccessManager().insertOrUpdate(CASE_DB_GROUPS_SEEN_TABLENAME, insertSQL);
|
||||||
|
|
||||||
groupSeenCache.put(groupKey, true);
|
groupSeenCache.put(groupKey, true);
|
||||||
}
|
}
|
||||||
@ -1203,7 +1285,7 @@ public final class DrawableDB {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String updateSQL = String.format(" SET seen = 0 WHERE group_id in ( " + getGroupIdQuery(groupKey) + ")"); //NON-NLS
|
String updateSQL = String.format(" SET seen = 0 WHERE group_id in ( " + getGroupIdQuery(groupKey) + ")"); //NON-NLS
|
||||||
tskCase.getCaseDbAccessManager().update(GROUPS_SEEN_TABLENAME, updateSQL);
|
caseDb.getCaseDbAccessManager().update(CASE_DB_GROUPS_SEEN_TABLENAME, updateSQL);
|
||||||
|
|
||||||
groupSeenCache.put(groupKey, false);
|
groupSeenCache.put(groupKey, false);
|
||||||
}
|
}
|
||||||
@ -1224,7 +1306,7 @@ public final class DrawableDB {
|
|||||||
SleuthkitCase.escapeSingleQuotes(groupKey.getValueDisplayName()),
|
SleuthkitCase.escapeSingleQuotes(groupKey.getValueDisplayName()),
|
||||||
groupKey.getAttribute() == DrawableAttribute.PATH ? groupKey.getDataSourceObjId() : 0);
|
groupKey.getAttribute() == DrawableAttribute.PATH ? groupKey.getDataSourceObjId() : 0);
|
||||||
|
|
||||||
tskCase.getCaseDbAccessManager().update(GROUPS_TABLENAME, updateSQL);
|
caseDb.getCaseDbAccessManager().update(CASE_DB_GROUPS_TABLENAME, updateSQL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1268,7 +1350,7 @@ public final class DrawableDB {
|
|||||||
CaseDbTransaction caseDbTransaction = null;
|
CaseDbTransaction caseDbTransaction = null;
|
||||||
try {
|
try {
|
||||||
trans = beginTransaction();
|
trans = beginTransaction();
|
||||||
caseDbTransaction = tskCase.beginTransaction();
|
caseDbTransaction = caseDb.beginTransaction();
|
||||||
updateFile(f, trans, caseDbTransaction);
|
updateFile(f, trans, caseDbTransaction);
|
||||||
caseDbTransaction.commit();
|
caseDbTransaction.commit();
|
||||||
commitTransaction(trans, true);
|
commitTransaction(trans, true);
|
||||||
@ -1317,11 +1399,11 @@ public final class DrawableDB {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// get tags
|
// get tags
|
||||||
try (SleuthkitCase.CaseDbQuery dbQuery = tskCase.executeQuery("SELECT obj_id FROM content_tags")) {
|
try (SleuthkitCase.CaseDbQuery dbQuery = caseDb.executeQuery("SELECT obj_id FROM content_tags")) {
|
||||||
ResultSet rs = dbQuery.getResultSet();
|
ResultSet rs = dbQuery.getResultSet();
|
||||||
while (rs.next()) {
|
while (rs.next()) {
|
||||||
long id = rs.getLong("obj_id");
|
long id = rs.getLong("obj_id");
|
||||||
hasTagCache.add(id);
|
hasTagsCache.add(id);
|
||||||
}
|
}
|
||||||
} catch (SQLException ex) {
|
} catch (SQLException ex) {
|
||||||
logger.log(Level.SEVERE, "Error getting tags from DB", ex); //NON-NLS
|
logger.log(Level.SEVERE, "Error getting tags from DB", ex); //NON-NLS
|
||||||
@ -1332,11 +1414,11 @@ public final class DrawableDB {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// hash sets
|
// hash sets
|
||||||
try (SleuthkitCase.CaseDbQuery dbQuery = tskCase.executeQuery("SELECT obj_id FROM blackboard_artifacts WHERE artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID())) {
|
try (SleuthkitCase.CaseDbQuery dbQuery = caseDb.executeQuery("SELECT obj_id FROM blackboard_artifacts WHERE artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID())) {
|
||||||
ResultSet rs = dbQuery.getResultSet();
|
ResultSet rs = dbQuery.getResultSet();
|
||||||
while (rs.next()) {
|
while (rs.next()) {
|
||||||
long id = rs.getLong("obj_id");
|
long id = rs.getLong("obj_id");
|
||||||
hasHashCache.add(id);
|
hasHashHitsCache.add(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (SQLException ex) {
|
} catch (SQLException ex) {
|
||||||
@ -1348,11 +1430,11 @@ public final class DrawableDB {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// EXIF
|
// EXIF
|
||||||
try (SleuthkitCase.CaseDbQuery dbQuery = tskCase.executeQuery("SELECT obj_id FROM blackboard_artifacts WHERE artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF.getTypeID())) {
|
try (SleuthkitCase.CaseDbQuery dbQuery = caseDb.executeQuery("SELECT obj_id FROM blackboard_artifacts WHERE artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF.getTypeID())) {
|
||||||
ResultSet rs = dbQuery.getResultSet();
|
ResultSet rs = dbQuery.getResultSet();
|
||||||
while (rs.next()) {
|
while (rs.next()) {
|
||||||
long id = rs.getLong("obj_id");
|
long id = rs.getLong("obj_id");
|
||||||
hasExifCache.add(id);
|
hasExifDataCache.add(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (SQLException ex) {
|
} catch (SQLException ex) {
|
||||||
@ -1377,7 +1459,7 @@ public final class DrawableDB {
|
|||||||
if (cacheBuildCount == 0) {
|
if (cacheBuildCount == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
hasExifCache.add(objectID);
|
hasExifDataCache.add(objectID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1392,7 +1474,7 @@ public final class DrawableDB {
|
|||||||
if (cacheBuildCount == 0) {
|
if (cacheBuildCount == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
hasHashCache.add(objectID);
|
hasHashHitsCache.add(objectID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1407,7 +1489,7 @@ public final class DrawableDB {
|
|||||||
if (cacheBuildCount == 0) {
|
if (cacheBuildCount == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
hasTagCache.add(objectID);
|
hasTagsCache.add(objectID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1422,9 +1504,9 @@ public final class DrawableDB {
|
|||||||
}
|
}
|
||||||
|
|
||||||
areCachesLoaded = false;
|
areCachesLoaded = false;
|
||||||
hasTagCache.clear();
|
hasTagsCache.clear();
|
||||||
hasHashCache.clear();
|
hasHashHitsCache.clear();
|
||||||
hasExifCache.clear();
|
hasExifDataCache.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1464,9 +1546,9 @@ public final class DrawableDB {
|
|||||||
boolean hasTag = true;
|
boolean hasTag = true;
|
||||||
synchronized (cacheLock) {
|
synchronized (cacheLock) {
|
||||||
if (areCachesLoaded) {
|
if (areCachesLoaded) {
|
||||||
hasExif = hasExifCache.contains(f.getId());
|
hasExif = hasExifDataCache.contains(f.getId());
|
||||||
hasHashSet = hasHashCache.contains(f.getId());
|
hasHashSet = hasHashHitsCache.contains(f.getId());
|
||||||
hasTag = hasTagCache.contains(f.getId());
|
hasTag = hasTagsCache.contains(f.getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1623,24 +1705,33 @@ public final class DrawableDB {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Insert/update given data source object id and it's DB rebuild status in
|
* Inserts the given data source object ID and its status into the
|
||||||
* the datasources table.
|
* datasources table. If a record for the data source already exists, an
|
||||||
|
* update of the status is done instead.
|
||||||
*
|
*
|
||||||
* If the object id exists in the table already, it updates the status
|
* @param dataSourceObjectID A data source object ID from the case database.
|
||||||
*
|
* @param status The status of the data source with respect to
|
||||||
* @param dsObjectId data source object id to insert
|
* populating the image gallery database.
|
||||||
* @param status The db build statsus for datasource.
|
|
||||||
*/
|
*/
|
||||||
public void insertOrUpdateDataSource(long dsObjectId, DrawableDbBuildStatusEnum status) {
|
public void insertOrUpdateDataSource(long dataSourceObjectID, DrawableDbBuildStatusEnum status) throws SQLException {
|
||||||
dbWriteLock();
|
dbWriteLock();
|
||||||
try {
|
try {
|
||||||
// "INSERT OR REPLACE INTO datasources (ds_obj_id, drawable_db_build_status) " //NON-NLS
|
// SELECT COUNT(*) FROM datasources WHERE ds_obj_id = ?
|
||||||
updateDataSourceStmt.setLong(1, dsObjectId);
|
selectCountDataSourceIDs.setLong(1, dataSourceObjectID);
|
||||||
updateDataSourceStmt.setString(2, status.name());
|
try (ResultSet resultSet = selectCountDataSourceIDs.executeQuery()) {
|
||||||
|
resultSet.next();
|
||||||
|
if (resultSet.getInt(1) == 0) {
|
||||||
|
// INSERT INTO datasources (ds_obj_id, drawable_db_build_status) VALUES (?,?)
|
||||||
|
insertDataSourceStmt.setLong(1, dataSourceObjectID);
|
||||||
|
insertDataSourceStmt.setString(2, status.name());
|
||||||
|
insertDataSourceStmt.execute();
|
||||||
|
} else {
|
||||||
|
// UPDATE datasources SET drawable_db_build_status = ? WHERE ds_obj_id = ?
|
||||||
|
updateDataSourceStmt.setString(1, status.name());
|
||||||
|
updateDataSourceStmt.setLong(2, dataSourceObjectID);
|
||||||
updateDataSourceStmt.executeUpdate();
|
updateDataSourceStmt.executeUpdate();
|
||||||
} catch (SQLException | NullPointerException ex) {
|
}
|
||||||
logger.log(Level.SEVERE, "failed to insert/update datasources table", ex); //NON-NLS
|
}
|
||||||
} finally {
|
} finally {
|
||||||
dbWriteUnlock();
|
dbWriteUnlock();
|
||||||
}
|
}
|
||||||
@ -1679,7 +1770,7 @@ public final class DrawableDB {
|
|||||||
//Can't make this a preprared statement because of the IN ( ... )
|
//Can't make this a preprared statement because of the IN ( ... )
|
||||||
ResultSet analyzedQuery = stmt.executeQuery("SELECT COUNT(analyzed) AS analyzed FROM drawable_files WHERE analyzed = 1 AND obj_id IN (" + StringUtils.join(fileIds, ", ") + ")"); //NON-NLS
|
ResultSet analyzedQuery = stmt.executeQuery("SELECT COUNT(analyzed) AS analyzed FROM drawable_files WHERE analyzed = 1 AND obj_id IN (" + StringUtils.join(fileIds, ", ") + ")"); //NON-NLS
|
||||||
while (analyzedQuery.next()) {
|
while (analyzedQuery.next()) {
|
||||||
return analyzedQuery.getInt(ANALYZED) == fileIds.size();
|
return analyzedQuery.getInt("analyzed") == fileIds.size();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -1724,13 +1815,13 @@ public final class DrawableDB {
|
|||||||
|
|
||||||
IsGroupAnalyzedQueryResultProcessor queryResultProcessor = new IsGroupAnalyzedQueryResultProcessor();
|
IsGroupAnalyzedQueryResultProcessor queryResultProcessor = new IsGroupAnalyzedQueryResultProcessor();
|
||||||
try {
|
try {
|
||||||
String groupAnalyzedQueryStmt = String.format("is_analyzed FROM " + GROUPS_TABLENAME
|
String groupAnalyzedQueryStmt = String.format("is_analyzed FROM " + CASE_DB_GROUPS_TABLENAME
|
||||||
+ " WHERE attribute = \'%s\' AND value = \'%s\' and data_source_obj_id = %d ",
|
+ " WHERE attribute = \'%s\' AND value = \'%s\' and data_source_obj_id = %d ",
|
||||||
SleuthkitCase.escapeSingleQuotes(groupKey.getAttribute().attrName.toString()),
|
SleuthkitCase.escapeSingleQuotes(groupKey.getAttribute().attrName.toString()),
|
||||||
SleuthkitCase.escapeSingleQuotes(groupKey.getValueDisplayName()),
|
SleuthkitCase.escapeSingleQuotes(groupKey.getValueDisplayName()),
|
||||||
groupKey.getAttribute() == DrawableAttribute.PATH ? groupKey.getDataSourceObjId() : 0);
|
groupKey.getAttribute() == DrawableAttribute.PATH ? groupKey.getDataSourceObjId() : 0);
|
||||||
|
|
||||||
tskCase.getCaseDbAccessManager().select(groupAnalyzedQueryStmt, queryResultProcessor);
|
caseDb.getCaseDbAccessManager().select(groupAnalyzedQueryStmt, queryResultProcessor);
|
||||||
return queryResultProcessor.getIsAnalyzed();
|
return queryResultProcessor.getIsAnalyzed();
|
||||||
} catch (TskCoreException ex) {
|
} catch (TskCoreException ex) {
|
||||||
String msg = String.format("Failed to get group is_analyzed for group key %s", groupKey.getValueDisplayName()); //NON-NLS
|
String msg = String.format("Failed to get group is_analyzed for group key %s", groupKey.getValueDisplayName()); //NON-NLS
|
||||||
@ -1882,7 +1973,7 @@ public final class DrawableDB {
|
|||||||
* wrong, we know this should be of type A even if
|
* wrong, we know this should be of type A even if
|
||||||
* JAVA doesn't
|
* JAVA doesn't
|
||||||
*/
|
*/
|
||||||
values.put(tskCase.getDataSource(results.getLong("data_source_obj_id")),
|
values.put(caseDb.getDataSource(results.getLong("data_source_obj_id")),
|
||||||
(A) results.getObject(groupBy.attrName.toString()));
|
(A) results.getObject(groupBy.attrName.toString()));
|
||||||
}
|
}
|
||||||
return values;
|
return values;
|
||||||
@ -1930,10 +2021,10 @@ public final class DrawableDB {
|
|||||||
int isAnalyzed = (groupBy == DrawableAttribute.PATH) ? 0 : 1;
|
int isAnalyzed = (groupBy == DrawableAttribute.PATH) ? 0 : 1;
|
||||||
String insertSQL = String.format(" (data_source_obj_id, value, attribute, is_analyzed) VALUES (%d, \'%s\', \'%s\', %d)",
|
String insertSQL = String.format(" (data_source_obj_id, value, attribute, is_analyzed) VALUES (%d, \'%s\', \'%s\', %d)",
|
||||||
ds_obj_id, SleuthkitCase.escapeSingleQuotes(value), SleuthkitCase.escapeSingleQuotes(groupBy.attrName.toString()), isAnalyzed);
|
ds_obj_id, SleuthkitCase.escapeSingleQuotes(value), SleuthkitCase.escapeSingleQuotes(groupBy.attrName.toString()), isAnalyzed);
|
||||||
if (DbType.POSTGRESQL == tskCase.getDatabaseType()) {
|
if (DbType.POSTGRESQL == caseDb.getDatabaseType()) {
|
||||||
insertSQL += " ON CONFLICT DO NOTHING";
|
insertSQL += " ON CONFLICT DO NOTHING";
|
||||||
}
|
}
|
||||||
tskCase.getCaseDbAccessManager().insert(GROUPS_TABLENAME, insertSQL, caseDbTransaction);
|
caseDb.getCaseDbAccessManager().insert(CASE_DB_GROUPS_TABLENAME, insertSQL, caseDbTransaction);
|
||||||
groupCache.put(cacheKey, Boolean.TRUE);
|
groupCache.put(cacheKey, Boolean.TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1946,7 +2037,7 @@ public final class DrawableDB {
|
|||||||
* {@link SleuthkitCase}
|
* {@link SleuthkitCase}
|
||||||
*/
|
*/
|
||||||
public DrawableFile getFileFromID(Long id) throws TskCoreException {
|
public DrawableFile getFileFromID(Long id) throws TskCoreException {
|
||||||
AbstractFile f = tskCase.getAbstractFileById(id);
|
AbstractFile f = caseDb.getAbstractFileById(id);
|
||||||
try {
|
try {
|
||||||
return DrawableFile.create(f, areFilesAnalyzed(Collections.singleton(id)), isVideoFile(f));
|
return DrawableFile.create(f, areFilesAnalyzed(Collections.singleton(id)), isVideoFile(f));
|
||||||
} catch (SQLException ex) {
|
} catch (SQLException ex) {
|
||||||
@ -1974,7 +2065,7 @@ public final class DrawableDB {
|
|||||||
|
|
||||||
try (ResultSet valsResults = statement.executeQuery()) {
|
try (ResultSet valsResults = statement.executeQuery()) {
|
||||||
while (valsResults.next()) {
|
while (valsResults.next()) {
|
||||||
files.add(valsResults.getLong(OBJ_ID));
|
files.add(valsResults.getLong("obj_id"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (SQLException ex) {
|
} catch (SQLException ex) {
|
||||||
@ -2015,12 +2106,12 @@ public final class DrawableDB {
|
|||||||
removeImageFileFromList(id);
|
removeImageFileFromList(id);
|
||||||
|
|
||||||
//"delete from hash_set_hits where (obj_id = " + id + ")"
|
//"delete from hash_set_hits where (obj_id = " + id + ")"
|
||||||
removeHashHitStmt.setLong(1, id);
|
deleteHashHitStmt.setLong(1, id);
|
||||||
removeHashHitStmt.executeUpdate();
|
deleteHashHitStmt.executeUpdate();
|
||||||
|
|
||||||
//"delete from drawable_files where (obj_id = " + id + ")"
|
//"delete from drawable_files where (obj_id = " + id + ")"
|
||||||
removeFileStmt.setLong(1, id);
|
deleteFileStmt.setLong(1, id);
|
||||||
removeFileStmt.executeUpdate();
|
deleteFileStmt.executeUpdate();
|
||||||
tr.addRemovedFile(id);
|
tr.addRemovedFile(id);
|
||||||
|
|
||||||
} catch (SQLException ex) {
|
} catch (SQLException ex) {
|
||||||
@ -2111,7 +2202,7 @@ public final class DrawableDB {
|
|||||||
try (Statement stmt = con.createStatement()) {
|
try (Statement stmt = con.createStatement()) {
|
||||||
ResultSet analyzedQuery = stmt.executeQuery("select obj_id from drawable_files");
|
ResultSet analyzedQuery = stmt.executeQuery("select obj_id from drawable_files");
|
||||||
while (analyzedQuery.next()) {
|
while (analyzedQuery.next()) {
|
||||||
addImageFileToList(analyzedQuery.getLong(OBJ_ID));
|
addImageFileToList(analyzedQuery.getLong("obj_id"));
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} catch (SQLException ex) {
|
} catch (SQLException ex) {
|
||||||
@ -2160,7 +2251,7 @@ public final class DrawableDB {
|
|||||||
try {
|
try {
|
||||||
TagName tagName = controller.getTagsManager().getTagName(cat);
|
TagName tagName = controller.getTagsManager().getTagName(cat);
|
||||||
if (nonNull(tagName)) {
|
if (nonNull(tagName)) {
|
||||||
return tskCase.getContentTagsByTagName(tagName).stream()
|
return caseDb.getContentTagsByTagName(tagName).stream()
|
||||||
.map(ContentTag::getContent)
|
.map(ContentTag::getContent)
|
||||||
.map(Content::getId)
|
.map(Content::getId)
|
||||||
.filter(this::isInDB)
|
.filter(this::isInDB)
|
||||||
@ -2212,7 +2303,7 @@ public final class DrawableDB {
|
|||||||
String name
|
String name
|
||||||
= "SELECT COUNT(obj_id) as obj_count FROM tsk_files where obj_id IN " + fileIdsList //NON-NLS
|
= "SELECT COUNT(obj_id) as obj_count FROM tsk_files where obj_id IN " + fileIdsList //NON-NLS
|
||||||
+ " AND obj_id NOT IN (SELECT obj_id FROM content_tags WHERE content_tags.tag_name_id IN " + catTagNameIDs + ")"; //NON-NLS
|
+ " AND obj_id NOT IN (SELECT obj_id FROM content_tags WHERE content_tags.tag_name_id IN " + catTagNameIDs + ")"; //NON-NLS
|
||||||
try (SleuthkitCase.CaseDbQuery executeQuery = tskCase.executeQuery(name);
|
try (SleuthkitCase.CaseDbQuery executeQuery = caseDb.executeQuery(name);
|
||||||
ResultSet resultSet = executeQuery.getResultSet();) {
|
ResultSet resultSet = executeQuery.getResultSet();) {
|
||||||
while (resultSet.next()) {
|
while (resultSet.next()) {
|
||||||
return resultSet.getLong("obj_count"); //NON-NLS
|
return resultSet.getLong("obj_count"); //NON-NLS
|
||||||
|
@ -17,6 +17,11 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import traceback
|
||||||
|
import general
|
||||||
|
import ast
|
||||||
|
|
||||||
from java.io import File
|
from java.io import File
|
||||||
from java.lang import Class
|
from java.lang import Class
|
||||||
from java.lang import ClassNotFoundException
|
from java.lang import ClassNotFoundException
|
||||||
@ -43,14 +48,13 @@ from org.sleuthkit.datamodel import TskCoreException
|
|||||||
from org.sleuthkit.datamodel.Blackboard import BlackboardException
|
from org.sleuthkit.datamodel.Blackboard import BlackboardException
|
||||||
from org.sleuthkit.datamodel import Account
|
from org.sleuthkit.datamodel import Account
|
||||||
from org.sleuthkit.datamodel.blackboardutils import CommunicationArtifactsHelper
|
from org.sleuthkit.datamodel.blackboardutils import CommunicationArtifactsHelper
|
||||||
|
from org.sleuthkit.datamodel.blackboardutils import MessageAttachments
|
||||||
|
from org.sleuthkit.datamodel.blackboardutils import URLAttachment
|
||||||
|
from org.sleuthkit.datamodel.blackboardutils import FileAttachment
|
||||||
from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import MessageReadStatus
|
from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import MessageReadStatus
|
||||||
from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import CommunicationDirection
|
from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import CommunicationDirection
|
||||||
from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import CallMediaType
|
from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import CallMediaType
|
||||||
|
|
||||||
import json
|
|
||||||
import traceback
|
|
||||||
import general
|
|
||||||
|
|
||||||
|
|
||||||
class FBMessengerAnalyzer(general.AndroidComponentAnalyzer):
|
class FBMessengerAnalyzer(general.AndroidComponentAnalyzer):
|
||||||
|
|
||||||
@ -95,6 +99,7 @@ class FBMessengerAnalyzer(general.AndroidComponentAnalyzer):
|
|||||||
* have no text,
|
* have no text,
|
||||||
* admin_text_thread_rtc_event has the specific event
|
* admin_text_thread_rtc_event has the specific event
|
||||||
"group-call-started", "group-call_ended"
|
"group-call-started", "group-call_ended"
|
||||||
|
--- A pending_send_media_attachment - a JSON structure that has details of attachments that may or may not have been sent.
|
||||||
--- A admin_text_thread_rtc_event column - has specific text events such as- "one-on-one-call-ended"
|
--- A admin_text_thread_rtc_event column - has specific text events such as- "one-on-one-call-ended"
|
||||||
--- A thread_key column - identifies the message thread
|
--- A thread_key column - identifies the message thread
|
||||||
--- A timestamp_ms column - date/time message was sent
|
--- A timestamp_ms column - date/time message was sent
|
||||||
@ -211,6 +216,17 @@ class FBMessengerAnalyzer(general.AndroidComponentAnalyzer):
|
|||||||
direction = CommunicationDirection.INCOMING
|
direction = CommunicationDirection.INCOMING
|
||||||
return direction
|
return direction
|
||||||
|
|
||||||
|
## Get the arrayList from the json passed in
|
||||||
|
def getJPGListFromJson(self, jpgJson):
|
||||||
|
jpgArray = ArrayList()
|
||||||
|
# The urls attachment will come across as unicode unless we use ast.literal_eval to change it to a dictionary
|
||||||
|
jpgDict = ast.literal_eval(jpgJson)
|
||||||
|
for jpgPreview in jpgDict.iterkeys():
|
||||||
|
# Need to use ast.literal_eval so that the string can be converted to a dictionary
|
||||||
|
jpgUrlDict = ast.literal_eval(jpgDict[jpgPreview])
|
||||||
|
jpgArray.add(URLAttachment(jpgUrlDict["src"]))
|
||||||
|
return jpgArray
|
||||||
|
|
||||||
## Analyzes messages
|
## Analyzes messages
|
||||||
def analyzeMessages(self, threadsDb, threadsDBHelper):
|
def analyzeMessages(self, threadsDb, threadsDBHelper):
|
||||||
try:
|
try:
|
||||||
@ -223,7 +239,8 @@ class FBMessengerAnalyzer(general.AndroidComponentAnalyzer):
|
|||||||
## The result set is processed to collect the multiple recipients for a given message.
|
## The result set is processed to collect the multiple recipients for a given message.
|
||||||
sqlString = """
|
sqlString = """
|
||||||
SELECT msg_id, text, sender, timestamp_ms, msg_type, messages.thread_key as thread_key,
|
SELECT msg_id, text, sender, timestamp_ms, msg_type, messages.thread_key as thread_key,
|
||||||
snippet, thread_participants.user_key as user_key, thread_users.name as name
|
snippet, thread_participants.user_key as user_key, thread_users.name as name,
|
||||||
|
attachments, pending_send_media_attachment
|
||||||
FROM messages
|
FROM messages
|
||||||
JOIN thread_participants ON messages.thread_key = thread_participants.thread_key
|
JOIN thread_participants ON messages.thread_key = thread_participants.thread_key
|
||||||
JOIN thread_users ON thread_participants.user_key = thread_users.user_key
|
JOIN thread_users ON thread_participants.user_key = thread_users.user_key
|
||||||
@ -241,6 +258,8 @@ class FBMessengerAnalyzer(general.AndroidComponentAnalyzer):
|
|||||||
timeStamp = -1
|
timeStamp = -1
|
||||||
msgText = ""
|
msgText = ""
|
||||||
threadId = ""
|
threadId = ""
|
||||||
|
messageAttachments = None
|
||||||
|
currentCase = Case.getCurrentCaseThrows()
|
||||||
|
|
||||||
while messagesResultSet.next():
|
while messagesResultSet.next():
|
||||||
msgId = messagesResultSet.getString("msg_id")
|
msgId = messagesResultSet.getString("msg_id")
|
||||||
@ -260,6 +279,10 @@ class FBMessengerAnalyzer(general.AndroidComponentAnalyzer):
|
|||||||
msgText,
|
msgText,
|
||||||
threadId)
|
threadId)
|
||||||
|
|
||||||
|
if (messageAttachments is not None):
|
||||||
|
threadsDBHelper.addAttachments(messageArtifact, messageAttachments)
|
||||||
|
messageAttachments = None
|
||||||
|
|
||||||
oldMsgId = msgId
|
oldMsgId = msgId
|
||||||
|
|
||||||
# New message - collect all attributes
|
# New message - collect all attributes
|
||||||
@ -282,7 +305,41 @@ class FBMessengerAnalyzer(general.AndroidComponentAnalyzer):
|
|||||||
if not msgText:
|
if not msgText:
|
||||||
msgText = messagesResultSet.getString("snippet")
|
msgText = messagesResultSet.getString("snippet")
|
||||||
|
|
||||||
# TBD: get attachment
|
# Get attachments and pending attachments if they exist
|
||||||
|
attachment = messagesResultSet.getString("attachments")
|
||||||
|
pendingAttachment = messagesResultSet.getString("pending_send_media_attachment")
|
||||||
|
|
||||||
|
urlAttachments = ArrayList()
|
||||||
|
fileAttachments = ArrayList()
|
||||||
|
|
||||||
|
if ((attachment is not None) or (pendingAttachment is not None)):
|
||||||
|
if (attachment is not None):
|
||||||
|
attachmentDict = json.loads(attachment)[0]
|
||||||
|
if (attachmentDict["mime_type"] == "image/jpeg"):
|
||||||
|
urlAttachments = self.getJPGListFromJson(attachmentDict["urls"])
|
||||||
|
|
||||||
|
elif (attachmentDict["mime_type"] == "video/mp4"):
|
||||||
|
# filename does not have an associated path with it so it will be ignored
|
||||||
|
urlAttachments = self.getJPGListFromJson(attachmentDict["urls"])
|
||||||
|
urlAttachments.add(URLAttachment(attachmentDict["video_data_url"]))
|
||||||
|
urlAttachments.add(URLAttachment(attachmentDict["video_data_thumbnail_url"]))
|
||||||
|
|
||||||
|
elif (attachmentDict["mime_type"] == "audio/mpeg"):
|
||||||
|
if (attachmentDict["audio_uri"] == ""):
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
audioUri = attachmentDict["audio_uri"]
|
||||||
|
fileAttachments.add(FileAttachment(currentCase.getSleuthkitCase(), threadsDb.getDBFile().getDataSource(), audioUri.replace("file://","")))
|
||||||
|
|
||||||
|
else:
|
||||||
|
self._logger.log(Level.INFO, "Attachment type not handled: " + attachmentDict["mime_type"])
|
||||||
|
|
||||||
|
if (pendingAttachment is not None):
|
||||||
|
pendingAttachmentDict = json.loads(pendingAttachment)[0]
|
||||||
|
pendingAttachmentUri = pendingAttachmentDict["uri"]
|
||||||
|
fileAttachments.add(FileAttachment(currentCase.getSleuthkitCase(), threadsDb.getDBFile().getDataSource(), pendingAttachmentUri.replace("file://","")))
|
||||||
|
|
||||||
|
messageAttachments = MessageAttachments(fileAttachments, urlAttachments)
|
||||||
|
|
||||||
threadId = messagesResultSet.getString("thread_key")
|
threadId = messagesResultSet.getString("thread_key")
|
||||||
|
|
||||||
|
@ -43,6 +43,8 @@ from org.sleuthkit.datamodel import TskCoreException
|
|||||||
from org.sleuthkit.datamodel.Blackboard import BlackboardException
|
from org.sleuthkit.datamodel.Blackboard import BlackboardException
|
||||||
from org.sleuthkit.datamodel import Account
|
from org.sleuthkit.datamodel import Account
|
||||||
from org.sleuthkit.datamodel.blackboardutils import CommunicationArtifactsHelper
|
from org.sleuthkit.datamodel.blackboardutils import CommunicationArtifactsHelper
|
||||||
|
from org.sleuthkit.datamodel.blackboardutils import FileAttachment
|
||||||
|
from org.sleuthkit.datamodel.blackboardutils import MessageAttachments
|
||||||
from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import MessageReadStatus
|
from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import MessageReadStatus
|
||||||
from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import CommunicationDirection
|
from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import CommunicationDirection
|
||||||
|
|
||||||
@ -96,6 +98,7 @@ class ShareItAnalyzer(general.AndroidComponentAnalyzer):
|
|||||||
direction = ""
|
direction = ""
|
||||||
fromId = None
|
fromId = None
|
||||||
toId = None
|
toId = None
|
||||||
|
fileAttachments = ArrayList()
|
||||||
|
|
||||||
if (historyResultSet.getInt("history_type") == 1):
|
if (historyResultSet.getInt("history_type") == 1):
|
||||||
direction = CommunicationDirection.INCOMING
|
direction = CommunicationDirection.INCOMING
|
||||||
@ -104,10 +107,6 @@ class ShareItAnalyzer(general.AndroidComponentAnalyzer):
|
|||||||
direction = CommunicationDirection.OUTGOING
|
direction = CommunicationDirection.OUTGOING
|
||||||
toId = historyResultSet.getString("device_id")
|
toId = historyResultSet.getString("device_id")
|
||||||
|
|
||||||
msgBody = "" # there is no body.
|
|
||||||
attachments = [historyResultSet.getString("file_path")]
|
|
||||||
msgBody = general.appendAttachmentList(msgBody, attachments)
|
|
||||||
|
|
||||||
timeStamp = historyResultSet.getLong("timestamp") / 1000
|
timeStamp = historyResultSet.getLong("timestamp") / 1000
|
||||||
messageArtifact = historyDbHelper.addMessage(
|
messageArtifact = historyDbHelper.addMessage(
|
||||||
self._MESSAGE_TYPE,
|
self._MESSAGE_TYPE,
|
||||||
@ -117,10 +116,14 @@ class ShareItAnalyzer(general.AndroidComponentAnalyzer):
|
|||||||
timeStamp,
|
timeStamp,
|
||||||
MessageReadStatus.UNKNOWN,
|
MessageReadStatus.UNKNOWN,
|
||||||
None, # subject
|
None, # subject
|
||||||
msgBody,
|
None, # message text
|
||||||
None ) # thread id
|
None ) # thread id
|
||||||
|
|
||||||
# TBD: add the file as attachment ??
|
# add the file as attachment
|
||||||
|
fileAttachments.add(FileAttachment(current_case.getSleuthkitCase(), historyDb.getDBFile().getDataSource(), historyResultSet.getString("file_path")))
|
||||||
|
messageAttachments = MessageAttachments(fileAttachments, [])
|
||||||
|
historyDbHelper.addAttachments(messageArtifact, messageAttachments)
|
||||||
|
|
||||||
|
|
||||||
except SQLException as ex:
|
except SQLException as ex:
|
||||||
self._logger.log(Level.WARNING, "Error processing query result for ShareIt history.", ex)
|
self._logger.log(Level.WARNING, "Error processing query result for ShareIt history.", ex)
|
||||||
|
@ -44,6 +44,8 @@ from org.sleuthkit.datamodel.Blackboard import BlackboardException
|
|||||||
from org.sleuthkit.autopsy.casemodule import NoCurrentCaseException
|
from org.sleuthkit.autopsy.casemodule import NoCurrentCaseException
|
||||||
from org.sleuthkit.datamodel import Account
|
from org.sleuthkit.datamodel import Account
|
||||||
from org.sleuthkit.datamodel.blackboardutils import CommunicationArtifactsHelper
|
from org.sleuthkit.datamodel.blackboardutils import CommunicationArtifactsHelper
|
||||||
|
from org.sleuthkit.datamodel.blackboardutils import FileAttachment
|
||||||
|
from org.sleuthkit.datamodel.blackboardutils import MessageAttachments
|
||||||
from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import MessageReadStatus
|
from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import MessageReadStatus
|
||||||
from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import CommunicationDirection
|
from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import CommunicationDirection
|
||||||
|
|
||||||
@ -93,7 +95,7 @@ class TextNowAnalyzer(general.AndroidComponentAnalyzer):
|
|||||||
)
|
)
|
||||||
self.parse_contacts(textnow_db, helper)
|
self.parse_contacts(textnow_db, helper)
|
||||||
self.parse_calllogs(textnow_db, helper)
|
self.parse_calllogs(textnow_db, helper)
|
||||||
self.parse_messages(textnow_db, helper)
|
self.parse_messages(textnow_db, helper, current_case)
|
||||||
except NoCurrentCaseException as ex:
|
except NoCurrentCaseException as ex:
|
||||||
self._logger.log(Level.WARNING, "No case currently open.", ex)
|
self._logger.log(Level.WARNING, "No case currently open.", ex)
|
||||||
self._logger.log(Level.WARNING, traceback.format_exc())
|
self._logger.log(Level.WARNING, traceback.format_exc())
|
||||||
@ -159,13 +161,13 @@ class TextNowAnalyzer(general.AndroidComponentAnalyzer):
|
|||||||
"Error posting TextNow call log artifact to the blackboard", ex)
|
"Error posting TextNow call log artifact to the blackboard", ex)
|
||||||
self._logger.log(Level.WARNING, traceback.format_exc())
|
self._logger.log(Level.WARNING, traceback.format_exc())
|
||||||
|
|
||||||
def parse_messages(self, textnow_db, helper):
|
def parse_messages(self, textnow_db, helper, current_case):
|
||||||
#Query for messages and iterate row by row adding
|
#Query for messages and iterate row by row adding
|
||||||
#each message artifact
|
#each message artifact
|
||||||
try:
|
try:
|
||||||
messages_parser = TextNowMessagesParser(textnow_db)
|
messages_parser = TextNowMessagesParser(textnow_db)
|
||||||
while messages_parser.next():
|
while messages_parser.next():
|
||||||
helper.addMessage(
|
message_artifact = helper.addMessage(
|
||||||
messages_parser.get_message_type(),
|
messages_parser.get_message_type(),
|
||||||
messages_parser.get_message_direction(),
|
messages_parser.get_message_direction(),
|
||||||
messages_parser.get_phone_number_from(),
|
messages_parser.get_phone_number_from(),
|
||||||
@ -176,6 +178,13 @@ class TextNowAnalyzer(general.AndroidComponentAnalyzer):
|
|||||||
messages_parser.get_message_text(),
|
messages_parser.get_message_text(),
|
||||||
messages_parser.get_thread_id()
|
messages_parser.get_thread_id()
|
||||||
)
|
)
|
||||||
|
if (len(messages_parser.get_file_attachment()) > 0):
|
||||||
|
file_attachments = ArrayList()
|
||||||
|
self._logger.log(Level.INFO, "SHow Attachment ==> " + str(len(messages_parser.get_file_attachment())) + " <> " + str(messages_parser.get_file_attachment()))
|
||||||
|
file_attachments.add(FileAttachment(current_case.getSleuthkitCase(), textnow_db.getDBFile().getDataSource(), messages_parser.get_file_attachment()))
|
||||||
|
message_attachments = MessageAttachments(file_attachments, [])
|
||||||
|
helper.addAttachments(message_artifact, message_attachments)
|
||||||
|
|
||||||
messages_parser.close()
|
messages_parser.close()
|
||||||
except SQLException as ex:
|
except SQLException as ex:
|
||||||
#Error parsing TextNow db
|
#Error parsing TextNow db
|
||||||
@ -364,9 +373,6 @@ class TextNowMessagesParser(TskMessagesParser):
|
|||||||
|
|
||||||
def get_message_text(self):
|
def get_message_text(self):
|
||||||
text = self.result_set.getString("message_text")
|
text = self.result_set.getString("message_text")
|
||||||
attachment = self.result_set.getString("attach")
|
|
||||||
if attachment != "":
|
|
||||||
text = general.appendAttachmentList(text, [attachment])
|
|
||||||
return text
|
return text
|
||||||
|
|
||||||
def get_thread_id(self):
|
def get_thread_id(self):
|
||||||
@ -374,3 +380,9 @@ class TextNowMessagesParser(TskMessagesParser):
|
|||||||
if thread_id is None:
|
if thread_id is None:
|
||||||
return super(TextNowMessagesParser, self).get_thread_id()
|
return super(TextNowMessagesParser, self).get_thread_id()
|
||||||
return thread_id
|
return thread_id
|
||||||
|
|
||||||
|
def get_file_attachment(self):
|
||||||
|
attachment = self.result_set.getString("attach")
|
||||||
|
if attachment is None:
|
||||||
|
return None
|
||||||
|
return self.result_set.getString("attach")
|
||||||
|
@ -43,6 +43,8 @@ from org.sleuthkit.datamodel import TskCoreException
|
|||||||
from org.sleuthkit.datamodel.Blackboard import BlackboardException
|
from org.sleuthkit.datamodel.Blackboard import BlackboardException
|
||||||
from org.sleuthkit.datamodel import Account
|
from org.sleuthkit.datamodel import Account
|
||||||
from org.sleuthkit.datamodel.blackboardutils import CommunicationArtifactsHelper
|
from org.sleuthkit.datamodel.blackboardutils import CommunicationArtifactsHelper
|
||||||
|
from org.sleuthkit.datamodel.blackboardutils import FileAttachment
|
||||||
|
from org.sleuthkit.datamodel.blackboardutils import MessageAttachments
|
||||||
from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import MessageReadStatus
|
from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import MessageReadStatus
|
||||||
from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import CommunicationDirection
|
from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import CommunicationDirection
|
||||||
import traceback
|
import traceback
|
||||||
@ -107,6 +109,8 @@ class XenderAnalyzer(general.AndroidComponentAnalyzer):
|
|||||||
fromId = None
|
fromId = None
|
||||||
toId = None
|
toId = None
|
||||||
|
|
||||||
|
fileAttachments = ArrayList()
|
||||||
|
|
||||||
if (messagesResultSet.getInt("c_direction") == 1):
|
if (messagesResultSet.getInt("c_direction") == 1):
|
||||||
direction = CommunicationDirection.OUTGOING
|
direction = CommunicationDirection.OUTGOING
|
||||||
toId = messagesResultSet.getString("r_device_id")
|
toId = messagesResultSet.getString("r_device_id")
|
||||||
@ -114,10 +118,6 @@ class XenderAnalyzer(general.AndroidComponentAnalyzer):
|
|||||||
direction = CommunicationDirection.INCOMING
|
direction = CommunicationDirection.INCOMING
|
||||||
fromId = messagesResultSet.getString("s_device_id")
|
fromId = messagesResultSet.getString("s_device_id")
|
||||||
|
|
||||||
msgBody = "" # there is no body.
|
|
||||||
attachments = [messagesResultSet.getString("f_path")]
|
|
||||||
msgBody = general.appendAttachmentList(msgBody, attachments)
|
|
||||||
|
|
||||||
timeStamp = messagesResultSet.getLong("f_create_time") / 1000
|
timeStamp = messagesResultSet.getLong("f_create_time") / 1000
|
||||||
messageArtifact = transactionDbHelper.addMessage(
|
messageArtifact = transactionDbHelper.addMessage(
|
||||||
self._MESSAGE_TYPE,
|
self._MESSAGE_TYPE,
|
||||||
@ -127,10 +127,13 @@ class XenderAnalyzer(general.AndroidComponentAnalyzer):
|
|||||||
timeStamp,
|
timeStamp,
|
||||||
MessageReadStatus.UNKNOWN,
|
MessageReadStatus.UNKNOWN,
|
||||||
None, # subject
|
None, # subject
|
||||||
msgBody,
|
None, # message text
|
||||||
messagesResultSet.getString("c_session_id") )
|
messagesResultSet.getString("c_session_id") )
|
||||||
|
|
||||||
# TBD: add the file as attachment ??
|
# add the file as attachment
|
||||||
|
fileAttachments.add(FileAttachment(current_case.getSleuthkitCase(), transactionDb.getDBFile().getDataSource(), messagesResultSet.getString("f_path")))
|
||||||
|
messageAttachments = MessageAttachments(fileAttachments, [])
|
||||||
|
transactionDbHelper.addAttachments(messageArtifact, messageAttachments)
|
||||||
|
|
||||||
except SQLException as ex:
|
except SQLException as ex:
|
||||||
self._logger.log(Level.WARNING, "Error processing query result for profiles.", ex)
|
self._logger.log(Level.WARNING, "Error processing query result for profiles.", ex)
|
||||||
|
@ -43,6 +43,8 @@ from org.sleuthkit.datamodel import TskCoreException
|
|||||||
from org.sleuthkit.datamodel.Blackboard import BlackboardException
|
from org.sleuthkit.datamodel.Blackboard import BlackboardException
|
||||||
from org.sleuthkit.datamodel import Account
|
from org.sleuthkit.datamodel import Account
|
||||||
from org.sleuthkit.datamodel.blackboardutils import CommunicationArtifactsHelper
|
from org.sleuthkit.datamodel.blackboardutils import CommunicationArtifactsHelper
|
||||||
|
from org.sleuthkit.datamodel.blackboardutils import FileAttachment
|
||||||
|
from org.sleuthkit.datamodel.blackboardutils import MessageAttachments
|
||||||
from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import MessageReadStatus
|
from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import MessageReadStatus
|
||||||
from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import CommunicationDirection
|
from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import CommunicationDirection
|
||||||
|
|
||||||
@ -88,6 +90,7 @@ class ZapyaAnalyzer(general.AndroidComponentAnalyzer):
|
|||||||
direction = CommunicationDirection.UNKNOWN
|
direction = CommunicationDirection.UNKNOWN
|
||||||
fromId = None
|
fromId = None
|
||||||
toId = None
|
toId = None
|
||||||
|
fileAttachments = ArrayList()
|
||||||
|
|
||||||
if (transfersResultSet.getInt("direction") == 1):
|
if (transfersResultSet.getInt("direction") == 1):
|
||||||
direction = CommunicationDirection.OUTGOING
|
direction = CommunicationDirection.OUTGOING
|
||||||
@ -96,10 +99,6 @@ class ZapyaAnalyzer(general.AndroidComponentAnalyzer):
|
|||||||
direction = CommunicationDirection.INCOMING
|
direction = CommunicationDirection.INCOMING
|
||||||
fromId = transfersResultSet.getString("device")
|
fromId = transfersResultSet.getString("device")
|
||||||
|
|
||||||
msgBody = "" # there is no body.
|
|
||||||
attachments = [transfersResultSet.getString("path")]
|
|
||||||
msgBody = general.appendAttachmentList(msgBody, attachments)
|
|
||||||
|
|
||||||
timeStamp = transfersResultSet.getLong("createtime") / 1000
|
timeStamp = transfersResultSet.getLong("createtime") / 1000
|
||||||
messageArtifact = transferDbHelper.addMessage(
|
messageArtifact = transferDbHelper.addMessage(
|
||||||
self._MESSAGE_TYPE,
|
self._MESSAGE_TYPE,
|
||||||
@ -109,10 +108,13 @@ class ZapyaAnalyzer(general.AndroidComponentAnalyzer):
|
|||||||
timeStamp,
|
timeStamp,
|
||||||
MessageReadStatus.UNKNOWN,
|
MessageReadStatus.UNKNOWN,
|
||||||
None, # subject
|
None, # subject
|
||||||
msgBody,
|
None, # message Text
|
||||||
None ) # thread id
|
None ) # thread id
|
||||||
|
|
||||||
# TBD: add the file as attachment ??
|
# add the file as attachment
|
||||||
|
fileAttachments.add(FileAttachment(current_case.getSleuthkitCase(), transferDb.getDBFile().getDataSource(), transfersResultSet.getString("path")))
|
||||||
|
messageAttachments = MessageAttachments(fileAttachments, [])
|
||||||
|
transferDbHelper.addAttachments(messageArtifact, messageAttachments)
|
||||||
|
|
||||||
except SQLException as ex:
|
except SQLException as ex:
|
||||||
self._logger.log(Level.WARNING, "Error processing query result for transfer.", ex)
|
self._logger.log(Level.WARNING, "Error processing query result for transfer.", ex)
|
||||||
|
@ -9,33 +9,41 @@ The following need to be done at least once. They do not need to be repeated for
|
|||||||
-- Linux: % sudo apt-get install testdisk
|
-- Linux: % sudo apt-get install testdisk
|
||||||
-- OS X: % brew install testdisk
|
-- OS X: % brew install testdisk
|
||||||
|
|
||||||
- Install a Java 8 JRE and JavaFX 8 and set JAVA_HOME.
|
- Install the BellSoft Java 8 JRE and JavaFX 8 distribution and set JAVA_HOME.
|
||||||
-- Linux: Any Java 8 version of OpenJDK/OpenJFX distribution should suffice. The following instructions use the Zulu Community distribution.
|
* The BellSoft distribution bundles OpenJDK and OpenJFX. Other distributions we have tried either don't
|
||||||
1. Download a 64 bit Java 8 JRE for your specific platform from https://www.azul.com/downloads/zulu-community
|
bundle OpenJFX (AdoptOpenJDK) or don't include all necessary binaries (Amazon Corretto).
|
||||||
2. Install the JRE. e.g. % sudo apt install ./zulu8.40.0.25-ca-jre8.0.222-linux_amd64.deb
|
-- Linux:
|
||||||
3. Download a 64 bit Java 8 JavaFX for your specific platform from the same location.
|
1. Install BellSoft Java 8
|
||||||
- Note that you may need to select "Older Zulu versions" for FX to become available in the "Java Package" dropdown.
|
% wget -q -O - https://download.bell-sw.com/pki/GPG-KEY-bellsoft | sudo apt-key add -
|
||||||
4. Extract the contents of the JavaFX archive into the folder where the JRE was installed.
|
% echo "deb [arch=amd64] https://apt.bell-sw.com/ stable main" | sudo tee /etc/apt/sources.list.d/bellsoft.list
|
||||||
e.g. % sudo tar xzf ~/Downloads/zulu8.40.0.25-ca-fx-jre8.0.222-linux_x64.tar.gz -C /usr/lib/jvm/zre-8-amd64 --strip-components=1
|
% sudo apt-get update
|
||||||
|
% sudo apt-get install bellsoft-java8
|
||||||
|
2. Set JAVA_HOME
|
||||||
|
% export JAVA_HOME=/usr/lib/jvm/bellsoft-java8-amd64
|
||||||
|
|
||||||
NOTE: You may need to log out and back in again after setting JAVA_HOME before the Autopsy
|
NOTE: You may need to log out and back in again after setting JAVA_HOME before the Autopsy
|
||||||
unix_setup.sh script can see the value.
|
unix_setup.sh script can see the value.
|
||||||
|
|
||||||
-- OS X: Any Java 8 version of OpenJDK/OpenJFX distribution should suffice. The following instructions use the AdoptOpenJDK distribution.
|
-- OS X:
|
||||||
1. Install a 64 bit Java 8 JRE.
|
1. Install BellSoft Java 8.
|
||||||
% brew cask install adoptopenjdk8
|
% brew tap bell-sw/liberica
|
||||||
|
% brew cask install liberica-jdk8
|
||||||
2. Set JAVA_HOME environment variable to location of JRE installation.
|
2. Set JAVA_HOME environment variable to location of JRE installation.
|
||||||
e.g. add the following to ~/.bashrc
|
e.g. add the following to ~/.bashrc
|
||||||
export JAVA_HOME=$(/usr/libexec/java_home -v 1.8)
|
export JAVA_HOME=$(/usr/libexec/java_home -v 1.8)
|
||||||
3. Confirm your version of Java by running
|
|
||||||
|
- Confirm your version of Java by running
|
||||||
% java -version
|
% java -version
|
||||||
|
openjdk version "1.8.0.232"
|
||||||
|
OpenJDK Runtime Environment (build 1.8.0_232-BellSoft-b10)
|
||||||
|
OpenJDK 64-Bit Server VM (build 25.232-b10, mixed mode)
|
||||||
|
|
||||||
* Install The Sleuth Kit Java Bindings *
|
* Install The Sleuth Kit Java Bindings *
|
||||||
|
|
||||||
Autopsy depends on a specific version of The Sleuth Kit. You need the Java libraries of The Sleuth Kit installed, which is not part of all packages.
|
Autopsy depends on a specific version of The Sleuth Kit. You need the Java libraries of The Sleuth Kit installed, which is not part of all packages.
|
||||||
|
|
||||||
- Linux: Install the sleuthkit-java.deb file that you can download from github.com/sleuthkit/sleuthkit/releases. This will install libewf, etc.
|
- Linux: Install the sleuthkit-java.deb file that you can download from github.com/sleuthkit/sleuthkit/releases. This will install libewf, etc.
|
||||||
-- % sudo apt install ./sleuthkit-java_4.6.0-1_amd64.deb
|
-- % sudo apt install ./sleuthkit-java_4.7.0-1_amd64.deb
|
||||||
|
|
||||||
- OS X: Install The Sleuth Kit from brew.
|
- OS X: Install The Sleuth Kit from brew.
|
||||||
-- % brew install sleuthkit
|
-- % brew install sleuthkit
|
||||||
@ -55,6 +63,24 @@ Autopsy depends on a specific version of The Sleuth Kit. You need the Java libr
|
|||||||
- Run Autopsy
|
- Run Autopsy
|
||||||
% ./autopsy
|
% ./autopsy
|
||||||
|
|
||||||
|
* Troubleshooting *
|
||||||
|
|
||||||
|
- If you see something like "Cannot create case: javafx/scene/paint/Color" it is an indication that Java FX
|
||||||
|
is not being found.
|
||||||
|
Confirm that the file $JAVA_HOME/jre/lib/ext/jfxrt.jar exists. If it does not exist, return to the Java
|
||||||
|
setup steps above.
|
||||||
|
- If you see something like "An illegal reflective access operation has occurred" it is an indication that
|
||||||
|
the wrong version of Java is being used to run Autopsy.
|
||||||
|
Check the version of Java reported in the ~/.autopsy/dev/var/log/messages.log file. It should contain lines that looks like:
|
||||||
|
Java; VM; Vendor = 1.8.0_232; OpenJDK 64-Bit Server V 25.232-b10; BellSoft
|
||||||
|
Runtime = OpenJDK Runtime Environment 1.8.0_232-BellSoft-b10
|
||||||
|
Java Home = /usr/lib/jvm/bellsoft-java8-amd64/jre
|
||||||
|
|
||||||
|
If your messages.log file indicates that Java 8 is not being used:
|
||||||
|
(a) confirm that you have a version of Java 8 installed and
|
||||||
|
(b) confirm that your JAVA_HOME environment variable is set correctly:
|
||||||
|
% echo $JAVA_HOME
|
||||||
|
|
||||||
* Limitations (Updated May 2018) *
|
* Limitations (Updated May 2018) *
|
||||||
- Timeline does not work on OS X
|
- Timeline does not work on OS X
|
||||||
- Video thumbnails are not generated (need to get a consistent version of OpenCV)
|
- Video thumbnails are not generated (need to get a consistent version of OpenCV)
|
||||||
|
@ -503,6 +503,9 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule {
|
|||||||
DerivedFile df = fileManager.addDerivedFile(filename, relPath,
|
DerivedFile df = fileManager.addDerivedFile(filename, relPath,
|
||||||
size, cTime, crTime, aTime, mTime, true, messageArtifact, "",
|
size, cTime, crTime, aTime, mTime, true, messageArtifact, "",
|
||||||
EmailParserModuleFactory.getModuleName(), EmailParserModuleFactory.getModuleVersion(), "", encodingType);
|
EmailParserModuleFactory.getModuleName(), EmailParserModuleFactory.getModuleVersion(), "", encodingType);
|
||||||
|
|
||||||
|
associateAttachmentWithMesssge(messageArtifact, df);
|
||||||
|
|
||||||
files.add(df);
|
files.add(df);
|
||||||
} catch (TskCoreException ex) {
|
} catch (TskCoreException ex) {
|
||||||
postErrorMessage(
|
postErrorMessage(
|
||||||
@ -516,6 +519,19 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule {
|
|||||||
return files;
|
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
|
* Finds and returns a set of unique email addresses found in the input string
|
||||||
*
|
*
|
||||||
|