Merge branch 'solr-8-upgrade' of github.com:sleuthkit/autopsy into solr8_libraries

This commit is contained in:
Eugene Livis 2019-11-26 15:09:33 -05:00
commit 4d368a970d
57 changed files with 5413 additions and 519 deletions

View File

@ -2300,7 +2300,6 @@ public class Case {
} else {
throw new CaseActionException(Bundle.Case_open_exception_multiUserCaseNotEnabled());
}
caseDb.registerForEvents(sleuthkitEventListener);
} catch (TskUnsupportedSchemaVersionException ex) {
throw new CaseActionException(Bundle.Case_exceptionMessage_unsupportedSchemaVersionMessage(ex.getLocalizedMessage()), ex);
} catch (UserPreferencesException ex) {
@ -2321,6 +2320,12 @@ public class Case {
private void openCaseLevelServices(ProgressIndicator progressIndicator) {
progressIndicator.progress(Bundle.Case_progressMessage_openingCaseLevelServices());
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);
}
/**

View File

@ -78,6 +78,9 @@ public final class UserPreferences {
public static final String EXTERNAL_HEX_EDITOR_PATH = "ExternalHexEditorPath";
public static final String SOLR_MAX_JVM_SIZE = "SolrMaxJVMSize";
public static final String RESULTS_TABLE_PAGE_SIZE = "ResultsTablePageSize";
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.
private UserPreferences() {
@ -534,4 +537,59 @@ public final class UserPreferences {
public static String getExternalHexEditorPath() {
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, "");
}
}

View File

@ -42,6 +42,7 @@ import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
import org.sleuthkit.datamodel.Blackboard;
import org.sleuthkit.datamodel.BlackboardArtifact;
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_EMAIL_MSG;
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_DATA_SOURCE_USAGE));
doNotShow.add(new BlackboardArtifact.Type(TSK_DOWNLOAD_SOURCE));
doNotShow.add(new BlackboardArtifact.Type(TSK_ASSOCIATED_OBJECT));
}
private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> {

View File

@ -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;
}

View File

@ -0,0 +1,4 @@
XRYDataSourceProcessorConfigPanel.fileBrowserButton.text=Browse
XRYDataSourceProcessorConfigPanel.filePathTextField.text=
XRYDataSourceProcessorConfigPanel.xrySelectFolderLabel.text=Select an XRY Folder
XRYDataSourceProcessorConfigPanel.errorLabel.text=

View File

@ -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=

View 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();
}
}

View File

@ -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);
}
}

View File

@ -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()));
}
}
}
}

View File

@ -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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
</SubComponents>
</Form>

View File

@ -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
}

View File

@ -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);
}
}

View 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;
}

View File

@ -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() {
}
}

View File

@ -45,36 +45,43 @@ import org.apache.commons.io.FilenameUtils;
* From
* Tel: 12345678
*/
public final class XRYFileReader implements AutoCloseable {
final class XRYFileReader implements AutoCloseable {
private static final Logger logger = Logger.getLogger(XRYFileReader.class.getName());
//Assume 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.
private static final int LINE_WITH_REPORT_TYPE = 3;
//Assume all headers are 5 lines in length.
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.
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
* and the reader is advanced past the header. This leaves the reader
* 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
* access to the path.
@ -82,6 +89,7 @@ public final class XRYFileReader implements AutoCloseable {
*/
public XRYFileReader(Path xryFile) throws IOException {
reader = Files.newBufferedReader(xryFile, CHARSET);
xryFilePath = xryFile;
//Advance the reader to the start of the first XRY entity.
for (int i = 0; i < HEADER_LENGTH_IN_LINES; i++) {
@ -91,6 +99,35 @@ public final class XRYFileReader implements AutoCloseable {
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
* reached.
@ -113,7 +150,7 @@ public final class XRYFileReader implements AutoCloseable {
return true;
}
} 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.
* Clients should test for another entity by calling hasNextEntity().
*
* @return A non-empty XRY entity.
* @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.
*

View File

@ -24,18 +24,76 @@ import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Stream;
/**
* 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
//children of their parent folder.
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
* 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.
*
* @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.
*/
public static boolean isXRYFolder(Path folder) throws IOException {

View File

@ -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();
}
}

View File

@ -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() {
}
}

View File

@ -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);
}
}

View File

@ -7,3 +7,26 @@ RefreshPanel.closeButton.text=
MapPanel.cordLabel.text=
WaypointDetailPanel.closeButton.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

View File

@ -1,8 +1,27 @@
CTL_OpenGeolocation=Geolocation
CTL_GeolocationTopComponentAction=GeolocationTopComponent
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_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
OpenGeolocationAction_displayName=Geolocation
OpenGeolocationAction_name=Geolocation
@ -12,4 +31,27 @@ RefreshPanel.closeButton.text=
MapPanel.cordLabel.text=
WaypointDetailPanel.closeButton.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)

View 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;
}
}
}

View 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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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>

View 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;
}
}
}

View 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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
<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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
<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>

View 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);
}
}
}

View 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);
}
}

View 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();
}
}

View 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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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>

View 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;
}
}
}

View File

@ -11,6 +11,7 @@
<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.DesignBorderLayout"/>
@ -23,6 +24,17 @@
</Constraints>
<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>
</SubComponents>
</Form>
</Form>

View File

@ -25,9 +25,9 @@ import java.beans.PropertyChangeListener;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
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.windows.RetainLocation;
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.MessageNotifyUtil;
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 static org.sleuthkit.autopsy.ingest.IngestManager.IngestModuleEvent.DATA_ADDED;
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 final PropertyChangeListener ingestListener;
private final GeoFilterPanel geoFilterPanel;
final RefreshPanel refreshPanel = new RefreshPanel();
@ -73,7 +79,7 @@ public final class GeolocationTopComponent extends TopComponent {
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
public GeolocationTopComponent() {
initComponents();
initWaypoints();
setName(Bundle.GLTopComponent_name());
this.ingestListener = pce -> {
@ -105,10 +111,19 @@ public final class GeolocationTopComponent extends TopComponent {
@Override
public void actionPerformed(ActionEvent e) {
mapPanel.clearWaypoints();
initWaypoints();
updateWaypoints();
showRefreshPanel(false);
}
});
geoFilterPanel = new GeoFilterPanel();
filterPane.setPanel(geoFilterPanel);
geoFilterPanel.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
updateWaypoints();
}
});
}
@Override
@ -118,7 +133,7 @@ public final class GeolocationTopComponent extends TopComponent {
Case.addEventTypeSubscriber(EnumSet.of(CURRENT_CASE), evt -> {
mapPanel.clearWaypoints();
if (evt.getNewValue() != null) {
initWaypoints();
updateWaypoints();
}
});
}
@ -134,6 +149,30 @@ public final class GeolocationTopComponent extends TopComponent {
super.componentOpened();
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.
@ -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() {
SwingWorker<List<MapWaypoint>, MapWaypoint> worker = new SwingWorker<List<MapWaypoint>, MapWaypoint>() {
@Override
protected List<MapWaypoint> doInBackground() throws Exception {
Case currentCase = Case.getCurrentCaseThrows();
@Messages({
"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",
"GeoTopComponent_filter_exception_msg=Exception occured during waypoint filtering.",
"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
try {
filters = geoFilterPanel.getFilterState();
} catch (GeoLocationUIException ex) {
JOptionPane.showMessageDialog(this,
Bundle.GeoTopComponent_filer_data_invalid_msg(),
Bundle.GeoTopComponent_filer_data_invalid_Title(),
JOptionPane.INFORMATION_MESSAGE);
return;
}
@Override
protected void done() {
if (isDone() && !isCancelled()) {
try {
List<MapWaypoint> waypoints = get();
if (waypoints == null || waypoints.isEmpty()) {
return;
SwingUtilities.invokeLater(new Runnable() {
public void run() {
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);
return;
}
mapPanel.setWaypoints(MapWaypoint.getWaypoints(waypoints));
}
mapPanel.setWaypoints(waypoints);
// There might be a better way to decide how to center
// but for now just use the first way point.
mapPanel.setCenterLocation(waypoints.get(0));
} catch (ExecutionException ex) {
logger.log(Level.WARNING, "An exception occured while initializing waypoints for geolocation window.", ex); //NON-NLS
MessageNotifyUtil.Message.error(Bundle.GLTopComponent_initilzation_error());
} 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() {
mapPanel = new org.sleuthkit.autopsy.geolocation.MapPanel();
filterPane = new org.sleuthkit.autopsy.geolocation.HidingPane();
setLayout(new java.awt.BorderLayout());
mapPanel.add(filterPane, java.awt.BorderLayout.LINE_START);
add(mapPanel, java.awt.BorderLayout.CENTER);
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
private org.sleuthkit.autopsy.geolocation.HidingPane filterPane;
private org.sleuthkit.autopsy.geolocation.MapPanel mapPanel;
// End of variables declaration//GEN-END:variables
}

View 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;
}
}
}

View File

@ -28,13 +28,17 @@ import java.awt.event.ComponentEvent;
import java.awt.geom.Point2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.prefs.PreferenceChangeEvent;
import java.util.prefs.PreferenceChangeListener;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.JSeparator;
import javax.swing.Popup;
@ -42,21 +46,28 @@ import javax.swing.PopupFactory;
import javax.swing.Timer;
import javax.swing.event.MouseInputListener;
import org.jxmapviewer.OSMTileFactoryInfo;
import org.jxmapviewer.VirtualEarthTileFactoryInfo;
import org.jxmapviewer.input.CenterMapListener;
import org.jxmapviewer.input.PanMouseInputListener;
import org.jxmapviewer.input.ZoomMouseWheelListenerCursor;
import org.jxmapviewer.viewer.DefaultTileFactory;
import org.jxmapviewer.viewer.GeoPosition;
import org.jxmapviewer.viewer.TileFactory;
import org.jxmapviewer.viewer.TileFactoryInfo;
import org.jxmapviewer.viewer.Waypoint;
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.MessageNotifyUtil;
import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationDataException;
import org.sleuthkit.datamodel.TskCoreException;
/**
* 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());
@ -76,9 +87,12 @@ final class MapPanel extends javax.swing.JPanel {
/**
* 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();
initMap();
zoomChanging = false;
currentPopup = null;
@ -94,14 +108,33 @@ final class MapPanel extends javax.swing.JPanel {
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.
*/
private void initMap() {
void initMap() throws GeoLocationDataException {
TileFactoryInfo info = new OSMTileFactoryInfo();
TileFactoryInfo info = getTileFactoryInfo();
DefaultTileFactory tileFactory = new DefaultTileFactory(info);
mapViewer.setTileFactory(tileFactory);
@ -125,7 +158,8 @@ final class MapPanel extends javax.swing.JPanel {
zoomSlider.setMaximum(tileFactory.getInfo().getMaximumZoomLevel());
setZoom(tileFactory.getInfo().getMaximumZoomLevel() - 1);
mapViewer.setAddressLocation(new GeoPosition(0, 0));
mapViewer.setCenterPosition(new GeoPosition(0,0));
// Basic painters for the way points.
WaypointPainter<Waypoint> waypointPainter = new WaypointPainter<Waypoint>() {
@ -145,6 +179,79 @@ final class MapPanel extends javax.swing.JPanel {
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.
*
@ -160,15 +267,6 @@ final class MapPanel extends javax.swing.JPanel {
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.
*

View File

@ -49,6 +49,7 @@ import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationDataException;
import org.sleuthkit.autopsy.geolocation.datamodel.Route;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.autopsy.geolocation.datamodel.Waypoint;
import org.sleuthkit.autopsy.geolocation.datamodel.WaypointBuilder;
import org.sleuthkit.autopsy.timeline.actions.ViewArtifactInTimelineAction;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
@ -79,7 +80,7 @@ final class MapWaypoint extends KdTree.XYZPoint implements org.jxmapviewer.viewe
* @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);
for (Route route : routes) {
@ -94,6 +95,28 @@ final class MapWaypoint extends KdTree.XYZPoint implements org.jxmapviewer.viewe
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.

View File

@ -42,7 +42,7 @@
<Component class="javax.swing.JButton" name="refreshButton">
<Properties>
<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 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, &quot;{key}&quot;)"/>
@ -63,7 +63,7 @@
<Color blue="0" green="0" red="0" type="rgb"/>
</Property>
<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 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, &quot;{key}&quot;)"/>

View File

@ -82,13 +82,13 @@ final class RefreshPanel extends JPanel {
gridBagConstraints.insets = new java.awt.Insets(15, 10, 15, 10);
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
refreshButton.setMargin(new java.awt.Insets(2, 5, 2, 5));
add(refreshButton, new java.awt.GridBagConstraints());
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
closeButton.setMargin(new java.awt.Insets(0, 0, 0, 0));
closeButton.setOpaque(false);

View 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 &amp; 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 );
}
}

View File

@ -25,12 +25,9 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
/**
@ -68,8 +65,6 @@ public class Waypoint {
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_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.
*
@ -262,186 +257,6 @@ public class Waypoint {
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

View 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;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 623 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 741 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 591 B

View File

@ -47,6 +47,7 @@ import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationDataException;
import org.sleuthkit.autopsy.geolocation.datamodel.Waypoint;
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.ReportProgressPanel;
import org.sleuthkit.datamodel.AbstractFile;
@ -331,11 +332,11 @@ class KMLReport implements GeneralReportModule {
* @throws IOException
*/
void addLocationsToReport(SleuthkitCase skCase, String baseReportDir) throws GeoLocationDataException, IOException {
addExifMetadataContent(Waypoint.getEXIFWaypoints(skCase), baseReportDir);
addWaypoints(Waypoint.getBookmarkWaypoints(skCase), gpsBookmarksFolder, FeatureColor.BLUE, Bundle.Waypoint_Bookmark_Display_String());
addWaypoints(Waypoint.getLastKnownWaypoints(skCase), gpsLastKnownLocationFolder, FeatureColor.PURPLE, Bundle.Waypoint_Last_Known_Display_String());
addWaypoints(Waypoint.getSearchWaypoints(skCase), gpsSearchesFolder, FeatureColor.WHITE, Bundle.Waypoint_Search_Display_String());
addWaypoints(Waypoint.getTrackpointWaypoints(skCase), gpsTrackpointsFolder, FeatureColor.WHITE, Bundle.Waypoint_Trackpoint_Display_String());
addExifMetadataContent(WaypointBuilder.getEXIFWaypoints(skCase), baseReportDir);
addWaypoints(WaypointBuilder.getBookmarkWaypoints(skCase), gpsBookmarksFolder, FeatureColor.BLUE, Bundle.Waypoint_Bookmark_Display_String());
addWaypoints(WaypointBuilder.getLastKnownWaypoints(skCase), gpsLastKnownLocationFolder, FeatureColor.PURPLE, Bundle.Waypoint_Last_Known_Display_String());
addWaypoints(WaypointBuilder.getSearchWaypoints(skCase), gpsSearchesFolder, FeatureColor.WHITE, Bundle.Waypoint_Search_Display_String());
addWaypoints(WaypointBuilder.getTrackpointWaypoints(skCase), gpsTrackpointsFolder, FeatureColor.WHITE, Bundle.Waypoint_Trackpoint_Display_String());
}
/**

View File

@ -36,8 +36,8 @@ import org.sleuthkit.datamodel.TskData;
* records for multiple drawable files.
*/
@NbBundle.Messages({
"BulkDrawableFilesTask.committingDb.status=committing image/video database",
"BulkDrawableFilesTask.stopCopy.status=Stopping copy to drawable db task.",
"BulkDrawableFilesTask.committingDb.status=committing image/video database",
"BulkDrawableFilesTask.stopCopy.status=Stopping copy to drawable db task.",
"BulkDrawableFilesTask.errPopulating.errMsg=There was an error populating Image Gallery database."
})
abstract class BulkDrawableFilesTask extends DrawableDbTask {
@ -58,7 +58,7 @@ abstract class BulkDrawableFilesTask extends DrawableDbTask {
this.taskDB = controller.getDrawablesDatabase();
this.tskCase = controller.getCaseDatabase();
this.dataSourceObjId = dataSourceObjId;
drawableQuery = " (data_source_obj_id = " + dataSourceObjId + ") "
drawableQuery = " (data_source_obj_id = " + dataSourceObjId + ") "
+ " AND ( meta_type = " + TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_REG.getValue() + ")" + " AND ( " + MIMETYPE_CLAUSE //NON-NLS
+ " OR mime_type LIKE 'video/%' OR mime_type LIKE 'image/%' )" //NON-NLS
+ " ORDER BY parent_path ";
@ -171,7 +171,11 @@ abstract class BulkDrawableFilesTask extends DrawableDbTask {
// Mark to REBUILT_STALE if some files didnt' have MIME (ingest was still ongoing) or
// if there was cancellation or errors
DrawableDB.DrawableDbBuildStatusEnum datasourceDrawableDBStatus = ((hasFilesWithNoMime == true) || (endedEarly == true)) ? DrawableDB.DrawableDbBuildStatusEnum.REBUILT_STALE : DrawableDB.DrawableDbBuildStatusEnum.COMPLETE;
taskDB.insertOrUpdateDataSource(dataSourceObjId, datasourceDrawableDBStatus);
try {
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("");
updateProgress(-1.0);
}

View File

@ -801,7 +801,11 @@ public final class ImageGalleryController {
if (((AutopsyEvent) event).getSourceType() == AutopsyEvent.SourceType.LOCAL) {
Content newDataSource = (Content) event.getNewValue();
if (isListeningEnabled()) {
drawableDB.insertOrUpdateDataSource(newDataSource.getId(), DrawableDB.DrawableDbBuildStatusEnum.UNKNOWN);
try {
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;
@ -883,7 +887,7 @@ public final class ImageGalleryController {
default:
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);
}
}
@ -898,7 +902,7 @@ public final class ImageGalleryController {
* @throws TskCoreException If there is an error adding the data source to
* the database.
*/
private void handleDataSourceAnalysisStarted(DataSourceAnalysisEvent event) throws TskCoreException {
private void handleDataSourceAnalysisStarted(DataSourceAnalysisEvent event) throws TskCoreException, SQLException {
if (event.getSourceType() == AutopsyEvent.SourceType.LOCAL && isListeningEnabled()) {
Content dataSource = event.getDataSource();
long dataSourceObjId = dataSource.getId();
@ -919,7 +923,7 @@ public final class ImageGalleryController {
* @throws TskCoreException If there is an error updating the state ot the
* 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) {
Content dataSource = event.getDataSource();
long dataSourceObjId = dataSource.getId();

View File

@ -49,7 +49,6 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Level;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.GuardedBy;
import javax.swing.SortOrder;
@ -80,130 +79,160 @@ import org.sleuthkit.datamodel.TskData.DbType;
import org.sleuthkit.datamodel.TskDataException;
import org.sleuthkit.datamodel.VersionNumber;
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.
*/
public final class DrawableDB {
private static final Logger logger = Logger.getLogger(DrawableDB.class.getName());
//column name constants//////////////////////
private static final String ANALYZED = "analyzed"; //NON-NLS
private static final String OBJ_ID = "obj_id"; //NON-NLS
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";
/*
* Schema version management constants.
*/
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 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_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 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
private PreparedStatement insertHashSetStmt;
private List<PreparedStatement> preparedStatements = new ArrayList<>();
private PreparedStatement removeFileStmt;
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
/*
* 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
* enables sharing of selected data between users of multi-user cases. This
* is necessary because the image gallery database is otherwise private to
* one node/machine.
*
* TODO: Consider refactoring to separate the image gallery database code
* from the case database code.
*/
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> 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 Path dbPath;
@GuardedBy("DBLock")
private Connection con;
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
/*
* Make sure the SQLite JDBC driver is loaded.
*/
static {
try {
Class.forName("org.sqlite.JDBC");
} catch (ClassNotFoundException ex) {
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 {
UNKNOWN, /// no known status - not yet analyzed
IN_PROGRESS, /// ingest or db rebuild is in progress
COMPLETE, /// At least one file in the data source had a MIME type. Ingest filters may have been applied.
REBUILT_STALE; /// data source was rebuilt, but MIME types were missing during rebuild
/**
* The data source has been added to the database, but no other data
* pertaining to it has been added.
*/
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() {
DBLock.lock();
dbLock.lock();
}
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 {
this.dbPath = dbPath;
this.controller = controller;
tskCase = this.controller.getCaseDatabase();
caseDb = this.controller.getCaseDatabase();
groupManager = this.controller.getGroupManager();
Files.createDirectories(this.dbPath.getParent());
dbWriteLock();
try {
con = DriverManager.getConnection("jdbc:sqlite:" + dbPath.toString()); //NON-NLS
if (!initializeDBSchema() || !upgradeDBSchema() || !prepareStatements() || !initializeStandardGroups() || !initializeImageList()) {
if (!initializeDBSchema() || !upgradeDBSchema() || !prepareStatements() || !initializeStandardGroups() || !removeDeletedDataSources() || !initializeImageList()) {
close();
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() {
try {
updateFileStmt = prepareStatement(
"INSERT OR REPLACE INTO drawable_files (obj_id, data_source_obj_id, path, name, created_time, modified_time, make, model, analyzed) " //NON-NLS
+ "VALUES (?,?,?,?,?,?,?,?,?)"); //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) " //NON-NLS
+ "VALUES (?,?,?,?,?,?,?,?,?)"); //NON-NLS
updateDataSourceStmt = prepareStatement(
"INSERT OR REPLACE INTO datasources (ds_obj_id, drawable_db_build_status) " //NON-NLS
+ " VALUES (?,?)"); //NON-NLS
removeFileStmt = prepareStatement("DELETE FROM drawable_files WHERE obj_id = ?"); //NON-NLS
selectCountDataSourceIDs = prepareStatement("SELECT COUNT(*) FROM datasources WHERE ds_obj_id = ?"); //NON-NLS
insertDataSourceStmt = prepareStatement("INSERT INTO datasources (ds_obj_id, drawable_db_build_status) VALUES (?,?)"); //NON-NLS
updateDataSourceStmt = prepareStatement("UPDATE datasources SET drawable_db_build_status = ? WHERE ds_obj_id = ?"); //NON-NLS
deleteDataSourceStmt = prepareStatement("DELETE FROM datasources where ds_obj_id = ?"); //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
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
deleteFileStmt = prepareStatement("DELETE FROM drawable_files WHERE obj_id = ?"); //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
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
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
modified_timeGroupStmt = prepareStatement("SELECT obj_id , analyzed FROM drawable_files WHERE modified_time = ? ", DrawableAttribute.MODIFIED_TIME); //NON-NLS
createdTimeGroupStmt = prepareStatement("SELECT obj_id , analyzed FROM drawable_files WHERE created_time = ? ", DrawableAttribute.CREATED_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
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
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);
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;
} catch (TskCoreException | SQLException ex) {
logger.log(Level.SEVERE, "Failed to prepare all statements", ex); //NON-NLS
return false;
@ -276,7 +302,7 @@ public final class DrawableDB {
private boolean initializeStandardGroups() {
CaseDbTransaction caseDbTransaction = null;
try {
caseDbTransaction = tskCase.beginTransaction();
caseDbTransaction = caseDb.beginTransaction();
for (DhsImageCategory cat : DhsImageCategory.values()) {
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 *
* 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
drawableDbTablesExist = doesTableExist("drawable_files");
if (false == doesTableExist(IG_DB_INFO_TABLE)) {
if (false == doesTableExist(DB_INFO_TABLE_NAME)) {
try {
VersionNumber ig_creation_schema_version = drawableDbTablesExist
? IG_STARTING_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
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')", 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_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_MINOR_VERSION_KEY, ig_creation_schema_version.getMinor()));
// 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')", 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_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_MINOR_VERSION_KEY, ig_creation_schema_version.getMinor()));
} catch (SQLException ex) {
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.
*/
String autogenKeyType = (DbType.POSTGRESQL == tskCase.getDatabaseType()) ? "BIGSERIAL" : "INTEGER";
String autogenKeyType = (DbType.POSTGRESQL == caseDb.getDatabaseType()) ? "BIGSERIAL" : "INTEGER";
try {
boolean caseDbTablesExist = tskCase.getCaseDbAccessManager().tableExists(GROUPS_TABLENAME);
boolean caseDbTablesExist = caseDb.getCaseDbAccessManager().tableExists(CASE_DB_GROUPS_TABLENAME);
VersionNumber ig_creation_schema_version = caseDbTablesExist
? IG_STARTING_SCHEMA_VERSION
: IG_SCHEMA_VERSION;
@ -664,7 +746,7 @@ public final class DrawableDB {
String tableSchema = "( id " + autogenKeyType + " PRIMARY KEY, "
+ " name TEXT UNIQUE 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
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 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 ";
creationMinorVerSQL += " ON CONFLICT DO NOTHING ";
@ -682,11 +764,11 @@ public final class DrawableDB {
currentMinorVerSQL += " ON CONFLICT DO NOTHING ";
}
tskCase.getCaseDbAccessManager().insert(IG_DB_INFO_TABLE, creationMajorVerSQL);
tskCase.getCaseDbAccessManager().insert(IG_DB_INFO_TABLE, creationMinorVerSQL);
caseDb.getCaseDbAccessManager().insert(DB_INFO_TABLE_NAME, creationMajorVerSQL);
caseDb.getCaseDbAccessManager().insert(DB_INFO_TABLE_NAME, creationMinorVerSQL);
tskCase.getCaseDbAccessManager().insert(IG_DB_INFO_TABLE, currentMajorVerSQL);
tskCase.getCaseDbAccessManager().insert(IG_DB_INFO_TABLE, currentMinorVerSQL);
caseDb.getCaseDbAccessManager().insert(DB_INFO_TABLE_NAME, currentMajorVerSQL);
caseDb.getCaseDbAccessManager().insert(DB_INFO_TABLE_NAME, currentMinorVerSQL);
} catch (TskCoreException ex) {
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, "
+ " 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) {
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;
}
try {
@ -715,13 +797,13 @@ public final class DrawableDB {
+ " examiner_id integer not null, " //NON-NLS
+ " seen integer DEFAULT 0, " //NON-NLS
+ " 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)"
+ " )"; //NON-NLS
tskCase.getCaseDbAccessManager().createTable(GROUPS_SEEN_TABLENAME, tableSchema);
caseDb.getCaseDbAccessManager().createTable(CASE_DB_GROUPS_SEEN_TABLENAME, tableSchema);
} 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;
}
@ -748,7 +830,7 @@ public final class DrawableDB {
try {
int majorVersion = -1;
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()) {
majorVersionStr = resultSet.getString("value");
try {
@ -762,7 +844,7 @@ public final class DrawableDB {
int minorVersion = -1;
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()) {
minorVersionStr = resultSet.getString("value");
try {
@ -827,8 +909,8 @@ public final class DrawableDB {
GetSchemaVersionQueryResultProcessor minorVersionResultProcessor = new GetSchemaVersionQueryResultProcessor();
String versionQueryTemplate = "value FROM %s WHERE name = \'%s\' ";
tskCase.getCaseDbAccessManager().select(String.format(versionQueryTemplate, IG_DB_INFO_TABLE, 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_MAJOR_VERSION_KEY), majorVersionResultProcessor);
caseDb.getCaseDbAccessManager().select(String.format(versionQueryTemplate, DB_INFO_TABLE_NAME, IG_SCHEMA_MINOR_VERSION_KEY), minorVersionResultProcessor);
return new VersionNumber(majorVersionResultProcessor.getVersion(), minorVersionResultProcessor.getVersion(), 0);
}
@ -852,8 +934,8 @@ public final class DrawableDB {
Statement statement = con.createStatement();
// 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'", 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.getMajor(), IG_SCHEMA_MAJOR_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();
} finally {
@ -872,8 +954,8 @@ public final class DrawableDB {
private void updateCaseDbIgSchemaVersion(VersionNumber version, CaseDbTransaction caseDbTransaction) throws TskCoreException {
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);
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.getMajor(), IG_SCHEMA_MAJOR_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();
// Upgrade Schema in both DrawableDB and CaseDB
CaseDbTransaction caseDbTransaction = tskCase.beginTransaction();
CaseDbTransaction caseDbTransaction = caseDb.beginTransaction();
DrawableTransaction transaction = beginTransaction();
try {
@ -948,8 +1030,8 @@ public final class DrawableDB {
// Add a 'is_analyzed' column to groups table in CaseDB
String alterSQL = " ADD COLUMN is_analyzed integer DEFAULT 1 "; //NON-NLS
if (false == tskCase.getCaseDbAccessManager().columnExists(GROUPS_TABLENAME, "is_analyzed", caseDbTransaction)) {
tskCase.getCaseDbAccessManager().alterTable(GROUPS_TABLENAME, alterSQL, caseDbTransaction);
if (false == caseDb.getCaseDbAccessManager().columnExists(CASE_DB_GROUPS_TABLENAME, "is_analyzed", caseDbTransaction)) {
caseDb.getCaseDbAccessManager().alterTable(CASE_DB_GROUPS_TABLENAME, alterSQL, caseDbTransaction);
}
return new VersionNumber(1, 1, 0);
}
@ -1047,7 +1129,7 @@ public final class DrawableDB {
*/
Set<String> getHashSetsForFile(long fileID) throws TskCoreException {
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) {
BlackboardAttribute attribute = a.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME));
@ -1069,7 +1151,7 @@ public final class DrawableDB {
dbWriteLock();
try (ResultSet rs = selectHashSetNamesStmt.executeQuery();) {
while (rs.next()) {
names.add(rs.getString(HASH_SET_NAME));
names.add(rs.getString("hash_set_name"));
}
} catch (SQLException sQLException) {
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) {
// 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",
SleuthkitCase.escapeSingleQuotes(groupKey.getAttribute().attrName.toString()),
SleuthkitCase.escapeSingleQuotes(groupKey.getValueDisplayName()),
@ -1131,12 +1213,12 @@ public final class DrawableDB {
GroupSeenQueryResultProcessor queryResultProcessor = new GroupSeenQueryResultProcessor();
try {
String groupSeenQueryStmt = "COUNT(*) as count FROM " + GROUPS_SEEN_TABLENAME
String groupSeenQueryStmt = "COUNT(*) as count FROM " + CASE_DB_GROUPS_SEEN_TABLENAME
+ " WHERE seen = 1 "
+ " AND group_id in ( " + getGroupIdQuery(groupKey) + ")"
+ (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();
} catch (ExecutionException | InterruptedException | TskCoreException ex) {
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
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
SleuthkitCase.escapeSingleQuotes(groupKey.getAttribute().attrName.toString()),
SleuthkitCase.escapeSingleQuotes(groupKey.getValueDisplayName()),
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
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
}
tskCase.getCaseDbAccessManager().insertOrUpdate(GROUPS_SEEN_TABLENAME, insertSQL);
caseDb.getCaseDbAccessManager().insertOrUpdate(CASE_DB_GROUPS_SEEN_TABLENAME, insertSQL);
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
tskCase.getCaseDbAccessManager().update(GROUPS_SEEN_TABLENAME, updateSQL);
caseDb.getCaseDbAccessManager().update(CASE_DB_GROUPS_SEEN_TABLENAME, updateSQL);
groupSeenCache.put(groupKey, false);
}
@ -1224,7 +1306,7 @@ public final class DrawableDB {
SleuthkitCase.escapeSingleQuotes(groupKey.getValueDisplayName()),
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;
try {
trans = beginTransaction();
caseDbTransaction = tskCase.beginTransaction();
caseDbTransaction = caseDb.beginTransaction();
updateFile(f, trans, caseDbTransaction);
caseDbTransaction.commit();
commitTransaction(trans, true);
@ -1317,11 +1399,11 @@ public final class DrawableDB {
try {
// 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();
while (rs.next()) {
long id = rs.getLong("obj_id");
hasTagCache.add(id);
hasTagsCache.add(id);
}
} catch (SQLException ex) {
logger.log(Level.SEVERE, "Error getting tags from DB", ex); //NON-NLS
@ -1332,11 +1414,11 @@ public final class DrawableDB {
try {
// 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();
while (rs.next()) {
long id = rs.getLong("obj_id");
hasHashCache.add(id);
hasHashHitsCache.add(id);
}
} catch (SQLException ex) {
@ -1348,11 +1430,11 @@ public final class DrawableDB {
try {
// 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();
while (rs.next()) {
long id = rs.getLong("obj_id");
hasExifCache.add(id);
hasExifDataCache.add(id);
}
} catch (SQLException ex) {
@ -1377,7 +1459,7 @@ public final class DrawableDB {
if (cacheBuildCount == 0) {
return;
}
hasExifCache.add(objectID);
hasExifDataCache.add(objectID);
}
}
@ -1392,7 +1474,7 @@ public final class DrawableDB {
if (cacheBuildCount == 0) {
return;
}
hasHashCache.add(objectID);
hasHashHitsCache.add(objectID);
}
}
@ -1407,7 +1489,7 @@ public final class DrawableDB {
if (cacheBuildCount == 0) {
return;
}
hasTagCache.add(objectID);
hasTagsCache.add(objectID);
}
}
@ -1422,9 +1504,9 @@ public final class DrawableDB {
}
areCachesLoaded = false;
hasTagCache.clear();
hasHashCache.clear();
hasExifCache.clear();
hasTagsCache.clear();
hasHashHitsCache.clear();
hasExifDataCache.clear();
}
}
@ -1464,9 +1546,9 @@ public final class DrawableDB {
boolean hasTag = true;
synchronized (cacheLock) {
if (areCachesLoaded) {
hasExif = hasExifCache.contains(f.getId());
hasHashSet = hasHashCache.contains(f.getId());
hasTag = hasTagCache.contains(f.getId());
hasExif = hasExifDataCache.contains(f.getId());
hasHashSet = hasHashHitsCache.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
* the datasources table.
* Inserts the given data source object ID and its status into the
* 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 dsObjectId data source object id to insert
* @param status The db build statsus for datasource.
* @param dataSourceObjectID A data source object ID from the case database.
* @param status The status of the data source with respect to
* populating the image gallery database.
*/
public void insertOrUpdateDataSource(long dsObjectId, DrawableDbBuildStatusEnum status) {
public void insertOrUpdateDataSource(long dataSourceObjectID, DrawableDbBuildStatusEnum status) throws SQLException {
dbWriteLock();
try {
// "INSERT OR REPLACE INTO datasources (ds_obj_id, drawable_db_build_status) " //NON-NLS
updateDataSourceStmt.setLong(1, dsObjectId);
updateDataSourceStmt.setString(2, status.name());
updateDataSourceStmt.executeUpdate();
} catch (SQLException | NullPointerException ex) {
logger.log(Level.SEVERE, "failed to insert/update datasources table", ex); //NON-NLS
// SELECT COUNT(*) FROM datasources WHERE ds_obj_id = ?
selectCountDataSourceIDs.setLong(1, dataSourceObjectID);
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();
}
}
} finally {
dbWriteUnlock();
}
@ -1679,7 +1770,7 @@ public final class DrawableDB {
//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
while (analyzedQuery.next()) {
return analyzedQuery.getInt(ANALYZED) == fileIds.size();
return analyzedQuery.getInt("analyzed") == fileIds.size();
}
return false;
}
@ -1724,13 +1815,13 @@ public final class DrawableDB {
IsGroupAnalyzedQueryResultProcessor queryResultProcessor = new IsGroupAnalyzedQueryResultProcessor();
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 ",
SleuthkitCase.escapeSingleQuotes(groupKey.getAttribute().attrName.toString()),
SleuthkitCase.escapeSingleQuotes(groupKey.getValueDisplayName()),
groupKey.getAttribute() == DrawableAttribute.PATH ? groupKey.getDataSourceObjId() : 0);
tskCase.getCaseDbAccessManager().select(groupAnalyzedQueryStmt, queryResultProcessor);
caseDb.getCaseDbAccessManager().select(groupAnalyzedQueryStmt, queryResultProcessor);
return queryResultProcessor.getIsAnalyzed();
} catch (TskCoreException ex) {
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
* 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()));
}
return values;
@ -1930,10 +2021,10 @@ public final class DrawableDB {
int isAnalyzed = (groupBy == DrawableAttribute.PATH) ? 0 : 1;
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);
if (DbType.POSTGRESQL == tskCase.getDatabaseType()) {
if (DbType.POSTGRESQL == caseDb.getDatabaseType()) {
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);
}
@ -1946,7 +2037,7 @@ public final class DrawableDB {
* {@link SleuthkitCase}
*/
public DrawableFile getFileFromID(Long id) throws TskCoreException {
AbstractFile f = tskCase.getAbstractFileById(id);
AbstractFile f = caseDb.getAbstractFileById(id);
try {
return DrawableFile.create(f, areFilesAnalyzed(Collections.singleton(id)), isVideoFile(f));
} catch (SQLException ex) {
@ -1974,7 +2065,7 @@ public final class DrawableDB {
try (ResultSet valsResults = statement.executeQuery()) {
while (valsResults.next()) {
files.add(valsResults.getLong(OBJ_ID));
files.add(valsResults.getLong("obj_id"));
}
}
} catch (SQLException ex) {
@ -2015,12 +2106,12 @@ public final class DrawableDB {
removeImageFileFromList(id);
//"delete from hash_set_hits where (obj_id = " + id + ")"
removeHashHitStmt.setLong(1, id);
removeHashHitStmt.executeUpdate();
deleteHashHitStmt.setLong(1, id);
deleteHashHitStmt.executeUpdate();
//"delete from drawable_files where (obj_id = " + id + ")"
removeFileStmt.setLong(1, id);
removeFileStmt.executeUpdate();
deleteFileStmt.setLong(1, id);
deleteFileStmt.executeUpdate();
tr.addRemovedFile(id);
} catch (SQLException ex) {
@ -2037,7 +2128,7 @@ public final class DrawableDB {
*
* @param dataSourceID The object ID of the data source to delete.
*
* @throws SQLException
* @throws SQLException
* @throws TskCoreException
*/
public void deleteDataSource(long dataSourceID) throws SQLException, TskCoreException {
@ -2111,7 +2202,7 @@ public final class DrawableDB {
try (Statement stmt = con.createStatement()) {
ResultSet analyzedQuery = stmt.executeQuery("select obj_id from drawable_files");
while (analyzedQuery.next()) {
addImageFileToList(analyzedQuery.getLong(OBJ_ID));
addImageFileToList(analyzedQuery.getLong("obj_id"));
}
return true;
} catch (SQLException ex) {
@ -2160,7 +2251,7 @@ public final class DrawableDB {
try {
TagName tagName = controller.getTagsManager().getTagName(cat);
if (nonNull(tagName)) {
return tskCase.getContentTagsByTagName(tagName).stream()
return caseDb.getContentTagsByTagName(tagName).stream()
.map(ContentTag::getContent)
.map(Content::getId)
.filter(this::isInDB)
@ -2212,7 +2303,7 @@ public final class DrawableDB {
String name
= "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
try (SleuthkitCase.CaseDbQuery executeQuery = tskCase.executeQuery(name);
try (SleuthkitCase.CaseDbQuery executeQuery = caseDb.executeQuery(name);
ResultSet resultSet = executeQuery.getResultSet();) {
while (resultSet.next()) {
return resultSet.getLong("obj_count"); //NON-NLS

View File

@ -17,6 +17,11 @@ See the License for the specific language governing permissions and
limitations under the License.
"""
import json
import traceback
import general
import ast
from java.io import File
from java.lang import Class
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 import Account
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 CommunicationDirection
from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import CallMediaType
import json
import traceback
import general
class FBMessengerAnalyzer(general.AndroidComponentAnalyzer):
@ -95,6 +99,7 @@ class FBMessengerAnalyzer(general.AndroidComponentAnalyzer):
* have no text,
* admin_text_thread_rtc_event has the specific event
"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 thread_key column - identifies the message thread
--- A timestamp_ms column - date/time message was sent
@ -210,6 +215,17 @@ class FBMessengerAnalyzer(general.AndroidComponentAnalyzer):
else:
direction = CommunicationDirection.INCOMING
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
def analyzeMessages(self, threadsDb, threadsDBHelper):
@ -223,7 +239,8 @@ class FBMessengerAnalyzer(general.AndroidComponentAnalyzer):
## The result set is processed to collect the multiple recipients for a given message.
sqlString = """
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
JOIN thread_participants ON messages.thread_key = thread_participants.thread_key
JOIN thread_users ON thread_participants.user_key = thread_users.user_key
@ -241,6 +258,8 @@ class FBMessengerAnalyzer(general.AndroidComponentAnalyzer):
timeStamp = -1
msgText = ""
threadId = ""
messageAttachments = None
currentCase = Case.getCurrentCaseThrows()
while messagesResultSet.next():
msgId = messagesResultSet.getString("msg_id")
@ -260,6 +279,10 @@ class FBMessengerAnalyzer(general.AndroidComponentAnalyzer):
msgText,
threadId)
if (messageAttachments is not None):
threadsDBHelper.addAttachments(messageArtifact, messageAttachments)
messageAttachments = None
oldMsgId = msgId
# New message - collect all attributes
@ -282,8 +305,42 @@ class FBMessengerAnalyzer(general.AndroidComponentAnalyzer):
if not msgText:
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")
else: # same msgId as last, just collect recipient from current row

View File

@ -43,6 +43,8 @@ from org.sleuthkit.datamodel import TskCoreException
from org.sleuthkit.datamodel.Blackboard import BlackboardException
from org.sleuthkit.datamodel import Account
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 CommunicationDirection
@ -96,6 +98,7 @@ class ShareItAnalyzer(general.AndroidComponentAnalyzer):
direction = ""
fromId = None
toId = None
fileAttachments = ArrayList()
if (historyResultSet.getInt("history_type") == 1):
direction = CommunicationDirection.INCOMING
@ -104,10 +107,6 @@ class ShareItAnalyzer(general.AndroidComponentAnalyzer):
direction = CommunicationDirection.OUTGOING
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
messageArtifact = historyDbHelper.addMessage(
self._MESSAGE_TYPE,
@ -117,10 +116,14 @@ class ShareItAnalyzer(general.AndroidComponentAnalyzer):
timeStamp,
MessageReadStatus.UNKNOWN,
None, # subject
msgBody,
None, # message text
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:
self._logger.log(Level.WARNING, "Error processing query result for ShareIt history.", ex)

View File

@ -44,6 +44,8 @@ from org.sleuthkit.datamodel.Blackboard import BlackboardException
from org.sleuthkit.autopsy.casemodule import NoCurrentCaseException
from org.sleuthkit.datamodel import Account
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 CommunicationDirection
@ -93,7 +95,7 @@ class TextNowAnalyzer(general.AndroidComponentAnalyzer):
)
self.parse_contacts(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:
self._logger.log(Level.WARNING, "No case currently open.", ex)
self._logger.log(Level.WARNING, traceback.format_exc())
@ -159,23 +161,30 @@ class TextNowAnalyzer(general.AndroidComponentAnalyzer):
"Error posting TextNow call log artifact to the blackboard", ex)
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
#each message artifact
try:
messages_parser = TextNowMessagesParser(textnow_db)
while messages_parser.next():
helper.addMessage(
messages_parser.get_message_type(),
messages_parser.get_message_direction(),
messages_parser.get_phone_number_from(),
messages_parser.get_phone_number_to(),
messages_parser.get_message_date_time(),
messages_parser.get_message_read_status(),
messages_parser.get_message_subject(),
messages_parser.get_message_text(),
messages_parser.get_thread_id()
)
message_artifact = helper.addMessage(
messages_parser.get_message_type(),
messages_parser.get_message_direction(),
messages_parser.get_phone_number_from(),
messages_parser.get_phone_number_to(),
messages_parser.get_message_date_time(),
messages_parser.get_message_read_status(),
messages_parser.get_message_subject(),
messages_parser.get_message_text(),
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()
except SQLException as ex:
#Error parsing TextNow db
@ -364,9 +373,6 @@ class TextNowMessagesParser(TskMessagesParser):
def get_message_text(self):
text = self.result_set.getString("message_text")
attachment = self.result_set.getString("attach")
if attachment != "":
text = general.appendAttachmentList(text, [attachment])
return text
def get_thread_id(self):
@ -374,3 +380,9 @@ class TextNowMessagesParser(TskMessagesParser):
if thread_id is None:
return super(TextNowMessagesParser, self).get_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")

View File

@ -43,6 +43,8 @@ from org.sleuthkit.datamodel import TskCoreException
from org.sleuthkit.datamodel.Blackboard import BlackboardException
from org.sleuthkit.datamodel import Account
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 CommunicationDirection
import traceback
@ -107,6 +109,8 @@ class XenderAnalyzer(general.AndroidComponentAnalyzer):
fromId = None
toId = None
fileAttachments = ArrayList()
if (messagesResultSet.getInt("c_direction") == 1):
direction = CommunicationDirection.OUTGOING
toId = messagesResultSet.getString("r_device_id")
@ -114,10 +118,6 @@ class XenderAnalyzer(general.AndroidComponentAnalyzer):
direction = CommunicationDirection.INCOMING
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
messageArtifact = transactionDbHelper.addMessage(
self._MESSAGE_TYPE,
@ -127,10 +127,13 @@ class XenderAnalyzer(general.AndroidComponentAnalyzer):
timeStamp,
MessageReadStatus.UNKNOWN,
None, # subject
msgBody,
None, # message text
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:
self._logger.log(Level.WARNING, "Error processing query result for profiles.", ex)

View File

@ -43,6 +43,8 @@ from org.sleuthkit.datamodel import TskCoreException
from org.sleuthkit.datamodel.Blackboard import BlackboardException
from org.sleuthkit.datamodel import Account
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 CommunicationDirection
@ -88,6 +90,7 @@ class ZapyaAnalyzer(general.AndroidComponentAnalyzer):
direction = CommunicationDirection.UNKNOWN
fromId = None
toId = None
fileAttachments = ArrayList()
if (transfersResultSet.getInt("direction") == 1):
direction = CommunicationDirection.OUTGOING
@ -95,10 +98,6 @@ class ZapyaAnalyzer(general.AndroidComponentAnalyzer):
else:
direction = CommunicationDirection.INCOMING
fromId = transfersResultSet.getString("device")
msgBody = "" # there is no body.
attachments = [transfersResultSet.getString("path")]
msgBody = general.appendAttachmentList(msgBody, attachments)
timeStamp = transfersResultSet.getLong("createtime") / 1000
messageArtifact = transferDbHelper.addMessage(
@ -109,10 +108,13 @@ class ZapyaAnalyzer(general.AndroidComponentAnalyzer):
timeStamp,
MessageReadStatus.UNKNOWN,
None, # subject
msgBody,
None, # message Text
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:
self._logger.log(Level.WARNING, "Error processing query result for transfer.", ex)

View File

@ -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
-- OS X: % brew install testdisk
- Install a Java 8 JRE and JavaFX 8 and set JAVA_HOME.
-- Linux: Any Java 8 version of OpenJDK/OpenJFX distribution should suffice. The following instructions use the Zulu Community distribution.
1. Download a 64 bit Java 8 JRE for your specific platform from https://www.azul.com/downloads/zulu-community
2. Install the JRE. e.g. % sudo apt install ./zulu8.40.0.25-ca-jre8.0.222-linux_amd64.deb
3. Download a 64 bit Java 8 JavaFX for your specific platform from the same location.
- Note that you may need to select "Older Zulu versions" for FX to become available in the "Java Package" dropdown.
4. Extract the contents of the JavaFX archive into the folder where the JRE was installed.
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
- Install the BellSoft Java 8 JRE and JavaFX 8 distribution and set JAVA_HOME.
* The BellSoft distribution bundles OpenJDK and OpenJFX. Other distributions we have tried either don't
bundle OpenJFX (AdoptOpenJDK) or don't include all necessary binaries (Amazon Corretto).
-- Linux:
1. Install BellSoft Java 8
% wget -q -O - https://download.bell-sw.com/pki/GPG-KEY-bellsoft | sudo apt-key add -
% echo "deb [arch=amd64] https://apt.bell-sw.com/ stable main" | sudo tee /etc/apt/sources.list.d/bellsoft.list
% 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
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.
1. Install a 64 bit Java 8 JRE.
% brew cask install adoptopenjdk8
-- OS X:
1. Install BellSoft Java 8.
% brew tap bell-sw/liberica
% brew cask install liberica-jdk8
2. Set JAVA_HOME environment variable to location of JRE installation.
e.g. add the following to ~/.bashrc
export JAVA_HOME=$(/usr/libexec/java_home -v 1.8)
3. Confirm your version of Java by running
% java -version
- Confirm your version of Java by running
% 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 *
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.
-- % 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.
-- % brew install sleuthkit
@ -55,6 +63,24 @@ Autopsy depends on a specific version of The Sleuth Kit. You need the Java libr
- Run 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) *
- Timeline does not work on OS X
- Video thumbnails are not generated (need to get a consistent version of OpenCV)

View File

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