From e30843c7c6bcc2d9bc48ff9deb39d75256ad5eff Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Wed, 13 Nov 2019 10:15:45 -0500 Subject: [PATCH 01/53] Implemented record parsing and testing for call logs, web bookmarks and contacts --- .../xry/AbstractSingleKeyValueParser.java | 158 +++++++++++++++++ .../xry/XRYCallsFileParser.java | 165 ++++++++++++++++++ .../xry/XRYContactsFileParser.java | 76 ++++++++ .../xry/XRYFileParser.java | 46 +++++ .../xry/XRYFileParserFactory.java | 80 +++++++++ .../xry/XRYFileReader.java | 74 ++++++-- .../datasourceprocessors/xry/XRYFolder.java | 2 +- .../xry/XRYWebBookmarksFileParser.java | 68 ++++++++ 8 files changed, 658 insertions(+), 11 deletions(-) create mode 100755 Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java create mode 100755 Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java create mode 100755 Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYContactsFileParser.java create mode 100755 Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileParser.java create mode 100755 Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileParserFactory.java create mode 100755 Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java new file mode 100755 index 0000000000..0ed74bdbef --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java @@ -0,0 +1,158 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit 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 pairs. + * + * 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 = ':'; + + @Override + public void parse(XRYFileReader reader, Content parent) throws IOException, TskCoreException { + Path reportPath = reader.getReportPath(); + logger.log(Level.INFO, String.format("INFO: Processing report at [ %s ]", reportPath.toString())); + + while (reader.hasNextEntity()) { + String xryEntity = reader.nextEntity(); + String[] xryLines = xryEntity.split("\n"); + + List attributes = new ArrayList<>(); + + if (xryLines.length > 0) { + logger.log(Level.INFO, String.format("INFO: Processing [ %s ]", xryLines[0])); + } + + String namespace = ""; + for (int i = 1; i < xryLines.length; i++) { + String xryLine = xryLines[i]; + + if (isNamespace(xryLine)) { + logger.log(Level.INFO, String.format("INFO: Detected XRY " + + "namespace keyword [ %s ]. Applying to all key value pairs following it.", xryLine)); + namespace = xryLine.trim(); + 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("Expected a key value " + + "pair on this line (in brackets) [ %s ], but one was not detected." + + " Here is the previous line (in brackets) [ %s ]. What does this key 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("The following key, " + + "value pair (in brackets, respectively) [ %s ], [ %s ] was not recognized. Discarding..." + + " Here is the previous line [ %s ] for context. What is it?", key, value, xryLines[i - 1])); + continue; + } + + if (value.isEmpty()) { + logger.log(Level.SEVERE, String.format("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); + //Returning null is temporary solution until we map out how we will deal with + //attributes we are currently ignoring. + if (attribute != null) { + attributes.add(makeAttribute(namespace, key, value)); + } + } + + if (attributes.size() > 0) { + 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. + * + * @return + */ + abstract void makeArtifact(List attributes, Content parent) throws TskCoreException; + +} diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java new file mode 100755 index 0000000000..05934a4904 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java @@ -0,0 +1,165 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit 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.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.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()); + + //Human readable name of this parser. + private static final String PARSER_NAME = "XRY Calls"; + + 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 XRY_KEYS = new HashSet() { + { + 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 XRY_NAMESPACES = new HashSet() { + { + 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 + String dateTime = removeDateTimeLocale(value); + String normalizedDateTime = dateTime.trim(); + long dateTimeInEpoch = calculateMsSinceEpoch(normalizedDateTime); + return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START, PARSER_NAME, dateTimeInEpoch); + 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 + switch (normalizedNamespace) { + case "from": + return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM, PARSER_NAME, value); + default: + 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": + return null; + case "last dialed": + 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 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. + * + * @param dateTime + * @return + */ + private String removeDateTimeLocale(String dateTime) { + int index = dateTime.indexOf('('); + if (index == -1) { + return dateTime; + } + + return dateTime.substring(0, index); + } + + /** + * + * @param dateTime + * @return + */ + private long calculateMsSinceEpoch(String dateTime) { + LocalDateTime localDateTime = LocalDateTime.parse(dateTime, DATE_TIME_PARSER); + //Assume dates have no offset. + return localDateTime.toInstant(ZoneOffset.UTC).toEpochMilli(); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYContactsFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYContactsFileParser.java new file mode 100755 index 0000000000..233fd3da16 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYContactsFileParser.java @@ -0,0 +1,76 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit 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.Content; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Parses XRY Contacts-Contacts files and creates artifacts. + */ +final class XRYContactsFileParser extends AbstractSingleKeyValueParser { + + //Human readable name of this parser. + private static final String PARSER_NAME = "XRY Contacts"; + + //All of the known XRY keys for contacts. + private static final Set XRY_KEYS = new HashSet() {{ + 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 attributes, Content parent) throws TskCoreException { + //BlackboardArtifact artifact = parent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT); + //artifact.addAttributes(attributes); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileParser.java new file mode 100755 index 0000000000..1787641e78 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileParser.java @@ -0,0 +1,46 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit 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; + +} + diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileParserFactory.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileParserFactory.java new file mode 100755 index 0000000000..8dd64996fa --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileParserFactory.java @@ -0,0 +1,80 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit 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. + */ + public static XRYFileParser get(String reportType) { + if (reportType == null) { + throw new IllegalArgumentException("Report type cannot be null"); + } + + switch (reportType.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. + */ + public static boolean supports(String reportType) { + try { + //Attempt a get. + get(reportType); + return true; + } catch (IllegalArgumentException ex) { + return false; + } + } + + //Prevent direct instantiation + private XRYFileParserFactory() { + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java index ff854a16ea..32a0b7eb33 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java @@ -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 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'); } } @@ -138,6 +175,23 @@ public final class XRYFileReader implements AutoCloseable { throw new NoSuchElementException(); } } + + /** + * Peek at the next XRY entity without consuming it. + * If there are not more XRY entities left, an exception is thrown. + * + * @return A non-empty XRY entity. + * @throws IOException + * @throws NoSuchElementException if there are no more XRY entities to + * read. + */ + public String peek() throws IOException { + if(hasNextEntity()) { + return xryEntity.toString(); + } else { + throw new NoSuchElementException(); + } + } /** * Closes any file handles this reader may have open. diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFolder.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFolder.java index b9b999f270..bac2bc2364 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFolder.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFolder.java @@ -30,7 +30,7 @@ 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. diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java new file mode 100755 index 0000000000..9cc6a4d745 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java @@ -0,0 +1,68 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit 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.Content; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Parses XRY Web-Bookmark files and creates artifacts. + */ +final class XRYWebBookmarksFileParser extends AbstractSingleKeyValueParser { + + //Human readable name of this parser. + private static final String PARSER_NAME = "XRY Web Bookmarks"; + + //All known XRY keys for web bookmarks. + private static final Map KEY_TO_TYPE + = new HashMap() { + { + 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 attributes, Content parent) throws TskCoreException { + //BlackboardArtifact artifact = parent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.WEB_BOOKMARK); + //artifact.addAttributes(attributes); + } +} From e67da6aec6663dfa537c4ffff53ccf918d138db8 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Wed, 13 Nov 2019 10:16:50 -0500 Subject: [PATCH 02/53] Uncommented to artifact creation code --- .../autopsy/datasourceprocessors/xry/XRYCallsFileParser.java | 4 ++-- .../datasourceprocessors/xry/XRYContactsFileParser.java | 4 ++-- .../datasourceprocessors/xry/XRYWebBookmarksFileParser.java | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java index 05934a4904..f422cd1ead 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java @@ -133,8 +133,8 @@ final class XRYCallsFileParser extends AbstractSingleKeyValueParser { @Override void makeArtifact(List attributes, Content parent) throws TskCoreException { - //BlackboardArtifact artifact = parent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG); - //artifact.addAttributes(attributes); + BlackboardArtifact artifact = parent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG); + artifact.addAttributes(attributes); } /** diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYContactsFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYContactsFileParser.java index 233fd3da16..ac74ae9ada 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYContactsFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYContactsFileParser.java @@ -70,7 +70,7 @@ final class XRYContactsFileParser extends AbstractSingleKeyValueParser { @Override void makeArtifact(List attributes, Content parent) throws TskCoreException { - //BlackboardArtifact artifact = parent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT); - //artifact.addAttributes(attributes); + BlackboardArtifact artifact = parent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT); + artifact.addAttributes(attributes); } } diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java index 9cc6a4d745..b9f2528f33 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java @@ -62,7 +62,7 @@ final class XRYWebBookmarksFileParser extends AbstractSingleKeyValueParser { @Override void makeArtifact(List attributes, Content parent) throws TskCoreException { - //BlackboardArtifact artifact = parent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.WEB_BOOKMARK); - //artifact.addAttributes(attributes); + BlackboardArtifact artifact = parent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.WEB_BOOKMARK); + artifact.addAttributes(attributes); } } From c1117164037c4008c1a26d77ccb933468a5eb3ed Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Wed, 13 Nov 2019 10:26:28 -0500 Subject: [PATCH 03/53] Tied together any loose ends. Added imports and fixed comments --- .../xry/AbstractSingleKeyValueParser.java | 14 +++++++++----- .../xry/XRYCallsFileParser.java | 19 ++++++++++++------- .../xry/XRYContactsFileParser.java | 1 + .../xry/XRYWebBookmarksFileParser.java | 1 + 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java index 0ed74bdbef..317ca17299 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java @@ -30,7 +30,7 @@ import org.sleuthkit.datamodel.TskCoreException; /** * Template parse method for reports that make blackboard attributes from a - * single key value pairs. + * single key value pair. * * This parse implementation will create 1 artifact per XRY entity. */ @@ -59,6 +59,8 @@ abstract class AbstractSingleKeyValueParser implements XRYFileParser { for (int i = 1; i < xryLines.length; i++) { String xryLine = xryLines[i]; + //Check if the line is a namespace, which gives context to the keys + //that follow. if (isNamespace(xryLine)) { logger.log(Level.INFO, String.format("INFO: Detected XRY " + "namespace keyword [ %s ]. Applying to all key value pairs following it.", xryLine)); @@ -81,7 +83,7 @@ abstract class AbstractSingleKeyValueParser implements XRYFileParser { if (!isKey(key)) { logger.log(Level.SEVERE, String.format("The following key, " + "value pair (in brackets, respectively) [ %s ], [ %s ] was not recognized. Discarding..." - + " Here is the previous line [ %s ] for context. What is it?", key, value, xryLines[i - 1])); + + " Here is the previous line [ %s ] for context. What does this key mean?", key, value, xryLines[i - 1])); continue; } @@ -93,13 +95,14 @@ abstract class AbstractSingleKeyValueParser implements XRYFileParser { } BlackboardAttribute attribute = makeAttribute(namespace, key, value); - //Returning null is temporary solution until we map out how we will deal with - //attributes we are currently ignoring. + //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.size() > 0) { makeArtifact(attributes, parent); } @@ -127,7 +130,8 @@ abstract class AbstractSingleKeyValueParser implements XRYFileParser { * * Ex: * - * To Tel : +1245325 + * To + * Tel : +1245325 * * "To" would be the candidate namespace that was extracted. * diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java index f422cd1ead..e417703606 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java @@ -26,6 +26,7 @@ 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; @@ -34,7 +35,7 @@ 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()); //Human readable name of this parser. @@ -42,7 +43,7 @@ final class XRYCallsFileParser extends AbstractSingleKeyValueParser { 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. @@ -113,7 +114,7 @@ final class XRYCallsFileParser extends AbstractSingleKeyValueParser { switch (normalizedValue) { case "missed": case "received": - return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DIRECTION, PARSER_NAME, INCOMING); + return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DIRECTION, PARSER_NAME, INCOMING); case "dialed": return null; case "last dialed": @@ -130,7 +131,7 @@ final class XRYCallsFileParser extends AbstractSingleKeyValueParser { throw new IllegalArgumentException(String.format("key [ %s ] was not recognized.", key)); } } - + @Override void makeArtifact(List attributes, Content parent) throws TskCoreException { BlackboardArtifact artifact = parent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG); @@ -139,9 +140,11 @@ final class XRYCallsFileParser extends AbstractSingleKeyValueParser { /** * Removes the locale from the date time value. + * + * Locale in this case being (Device) or (Network). * - * @param dateTime - * @return + * @param dateTime XRY datetime value to be sanitized. + * @return A purer date time value. */ private String removeDateTimeLocale(String dateTime) { int index = dateTime.indexOf('('); @@ -153,7 +156,9 @@ final class XRYCallsFileParser extends AbstractSingleKeyValueParser { } /** - * + * Parses the datatime value and calculates ms since epoch. It time zone is + * assumed to be UTC. + * * @param dateTime * @return */ diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYContactsFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYContactsFileParser.java index ac74ae9ada..ff47f33e95 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYContactsFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYContactsFileParser.java @@ -22,6 +22,7 @@ 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; diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java index b9f2528f33..e3ba5d6f23 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java @@ -22,6 +22,7 @@ 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; From d39ed4d7323a65819fcd0f081f1c9c6f09f8917d Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Wed, 13 Nov 2019 10:45:25 -0500 Subject: [PATCH 04/53] Fixed type in artifact naem --- .../datasourceprocessors/xry/XRYWebBookmarksFileParser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java index e3ba5d6f23..3c6e881ada 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java @@ -63,7 +63,7 @@ final class XRYWebBookmarksFileParser extends AbstractSingleKeyValueParser { @Override void makeArtifact(List attributes, Content parent) throws TskCoreException { - BlackboardArtifact artifact = parent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.WEB_BOOKMARK); + BlackboardArtifact artifact = parent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_BOOKMARK); artifact.addAttributes(attributes); } } From f181a1f862fafd51b9820bfe7d96f95033c84ac1 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Wed, 13 Nov 2019 10:55:51 -0500 Subject: [PATCH 05/53] Updated comments --- .../xry/AbstractSingleKeyValueParser.java | 4 +++- .../datasourceprocessors/xry/XRYCallsFileParser.java | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java index 317ca17299..d3a031a776 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java @@ -56,6 +56,8 @@ abstract class AbstractSingleKeyValueParser implements XRYFileParser { } 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]; @@ -74,7 +76,7 @@ abstract class AbstractSingleKeyValueParser implements XRYFileParser { if (keyDelimiter == -1) { logger.log(Level.SEVERE, String.format("Expected a key value " + "pair on this line (in brackets) [ %s ], but one was not detected." - + " Here is the previous line (in brackets) [ %s ]. What does this key mean?", xryLine, xryLines[i - 1])); + + " Here is the previous line (in brackets) [ %s ]. What does this mean?", xryLine, xryLines[i - 1])); continue; } String key = xryLine.substring(0, keyDelimiter).trim(); diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java index e417703606..b4c556079c 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java @@ -140,7 +140,7 @@ final class XRYCallsFileParser extends AbstractSingleKeyValueParser { /** * Removes the locale from the date time value. - * + * * Locale in this case being (Device) or (Network). * * @param dateTime XRY datetime value to be sanitized. @@ -156,9 +156,9 @@ final class XRYCallsFileParser extends AbstractSingleKeyValueParser { } /** - * Parses the datatime value and calculates ms since epoch. It time zone is + * Parses the datatime value and calculates ms since epoch. The time zone is * assumed to be UTC. - * + * * @param dateTime * @return */ From 36e9c7a5c63369e55df51069c2769c3a7cbf3b1e Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Wed, 13 Nov 2019 11:17:48 -0500 Subject: [PATCH 06/53] Addressed some of the codacy comments --- .../xry/AbstractSingleKeyValueParser.java | 2 +- .../datasourceprocessors/xry/XRYCallsFileParser.java | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java index d3a031a776..b5f4220f85 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java @@ -105,7 +105,7 @@ abstract class AbstractSingleKeyValueParser implements XRYFileParser { } //Only create artifacts with non-empty attributes. - if (attributes.size() > 0) { + if (!attributes.isEmpty()) { makeArtifact(attributes, parent); } } diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java index b4c556079c..297f3d06cd 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java @@ -103,11 +103,10 @@ final class XRYCallsFileParser extends AbstractSingleKeyValueParser { return null; case "tel": //Apply the namespace - switch (normalizedNamespace) { - case "from": - return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM, PARSER_NAME, value); - default: - return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO, PARSER_NAME, value); + 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(); From a2584ef060059021bb8ad66185e973fedee37544 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Wed, 13 Nov 2019 11:20:37 -0500 Subject: [PATCH 07/53] Fixed typo in comment and added a line for clarity --- .../datasourceprocessors/xry/AbstractSingleKeyValueParser.java | 1 + .../autopsy/datasourceprocessors/xry/XRYCallsFileParser.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java index b5f4220f85..9ebf7ec32b 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java @@ -51,6 +51,7 @@ abstract class AbstractSingleKeyValueParser implements XRYFileParser { List attributes = new ArrayList<>(); + //First line of the entity is the title. if (xryLines.length > 0) { logger.log(Level.INFO, String.format("INFO: Processing [ %s ]", xryLines[0])); } diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java index 297f3d06cd..48aff2e762 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java @@ -155,7 +155,7 @@ final class XRYCallsFileParser extends AbstractSingleKeyValueParser { } /** - * Parses the datatime value and calculates ms since epoch. The time zone is + * Parses the date time value and calculates ms since epoch. The time zone is * assumed to be UTC. * * @param dateTime From eb1a9cd279a295a2e0d5e77ef978425d8e199269 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Wed, 13 Nov 2019 12:17:55 -0500 Subject: [PATCH 08/53] Implemented and tested the device gen info parser --- .../xry/XRYDeviceGenInfoFileParser.java | 189 ++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100755 Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDeviceGenInfoFileParser.java diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDeviceGenInfoFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDeviceGenInfoFileParser.java new file mode 100755 index 0000000000..937627d61d --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDeviceGenInfoFileParser.java @@ -0,0 +1,189 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit 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 Device General Info"; + 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 keys for device gen info. + private static final Map KEY_TO_TYPE + = new HashMap() { + { + 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 + * @throws TskCoreException + */ + @Override + public void parse(XRYFileReader reader, Content parent) throws IOException, TskCoreException { + Path reportPath = reader.getReportPath(); + logger.log(Level.INFO, String.format("Processing report at [ %s ]", reportPath.toString())); + + while (reader.hasNextEntity()) { + String xryEntity = reader.nextEntity(); + String[] xryLines = xryEntity.split("\n"); + + List attributes = new ArrayList<>(); + + //First line of the entity is the title. + if (xryLines.length > 0) { + logger.log(Level.INFO, String.format("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("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("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("SEVERE: 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("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); + } +} From 3c4aaa3f8dbe8d08ecd63d372b7e5629256036a2 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Wed, 13 Nov 2019 12:24:16 -0500 Subject: [PATCH 09/53] Updated comments --- .../xry/XRYDeviceGenInfoFileParser.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDeviceGenInfoFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDeviceGenInfoFileParser.java index 937627d61d..5b50e534a6 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDeviceGenInfoFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDeviceGenInfoFileParser.java @@ -46,7 +46,9 @@ final class XRYDeviceGenInfoFileParser implements XRYFileParser { private static final String ATTRIBUTE_KEY = "attribute"; private static final String DATA_KEY = "data"; - //All of the known XRY keys for device gen info. + //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 KEY_TO_TYPE = new HashMap() { { @@ -75,8 +77,8 @@ final class XRYDeviceGenInfoFileParser implements XRYFileParser { * @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 - * @throws TskCoreException + * @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 { From 1144649e370de6696991d527ca40e2c12c0eb3f9 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Wed, 13 Nov 2019 15:52:38 -0500 Subject: [PATCH 10/53] Implemented and tested XRY messages parser, implemented code review feedback --- .../xry/AbstractSingleKeyValueParser.java | 16 +- .../xry/XRYCallsFileParser.java | 18 +- .../xry/XRYContactsFileParser.java | 3 - .../xry/XRYDeviceGenInfoFileParser.java | 14 +- .../xry/XRYFileParserFactory.java | 4 +- .../xry/XRYFileReader.java | 25 +- .../xry/XRYMessagesFileParser.java | 483 ++++++++++++++++++ .../xry/XRYWebBookmarksFileParser.java | 3 - 8 files changed, 524 insertions(+), 42 deletions(-) create mode 100755 Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYMessagesFileParser.java diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java index 9ebf7ec32b..20b6b7c1bf 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java @@ -39,11 +39,13 @@ 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("INFO: Processing report at [ %s ]", reportPath.toString())); + logger.log(Level.INFO, String.format("XRY DSP: Processing report at [ %s ]", reportPath.toString())); while (reader.hasNextEntity()) { String xryEntity = reader.nextEntity(); @@ -53,7 +55,7 @@ abstract class AbstractSingleKeyValueParser implements XRYFileParser { //First line of the entity is the title. if (xryLines.length > 0) { - logger.log(Level.INFO, String.format("INFO: Processing [ %s ]", xryLines[0])); + logger.log(Level.INFO, String.format("XRY DSP: Processing [ %s ]", xryLines[0])); } String namespace = ""; @@ -65,8 +67,6 @@ abstract class AbstractSingleKeyValueParser implements XRYFileParser { //Check if the line is a namespace, which gives context to the keys //that follow. if (isNamespace(xryLine)) { - logger.log(Level.INFO, String.format("INFO: Detected XRY " - + "namespace keyword [ %s ]. Applying to all key value pairs following it.", xryLine)); namespace = xryLine.trim(); continue; } @@ -75,23 +75,23 @@ abstract class AbstractSingleKeyValueParser implements XRYFileParser { //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("Expected a key value " + 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 (in brackets) [ %s ]. What does this mean?", xryLine, xryLines[i - 1])); + + " 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("The following 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("The following key " + 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; diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java index 48aff2e762..af59c6891d 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java @@ -21,6 +21,7 @@ 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; @@ -38,9 +39,6 @@ final class XRYCallsFileParser extends AbstractSingleKeyValueParser { private static final Logger logger = Logger.getLogger(XRYCallsFileParser.class.getName()); - //Human readable name of this parser. - private static final String PARSER_NAME = "XRY Calls"; - private static final DateTimeFormatter DATE_TIME_PARSER = DateTimeFormatter.ofPattern("M/d/y h:m:s [a][ z]"); @@ -88,10 +86,16 @@ final class XRYCallsFileParser extends AbstractSingleKeyValueParser { switch (normalizedKey) { case "time": //Tranform value to epoch ms - String dateTime = removeDateTimeLocale(value); - String normalizedDateTime = dateTime.trim(); - long dateTimeInEpoch = calculateMsSinceEpoch(normalizedDateTime); - return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START, PARSER_NAME, dateTimeInEpoch); + try { + String dateTime = removeDateTimeLocale(value); + String normalizedDateTime = dateTime.trim(); + long dateTimeInEpoch = calculateMsSinceEpoch(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; diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYContactsFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYContactsFileParser.java index ff47f33e95..ec8fd40187 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYContactsFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYContactsFileParser.java @@ -31,9 +31,6 @@ import org.sleuthkit.datamodel.TskCoreException; */ final class XRYContactsFileParser extends AbstractSingleKeyValueParser { - //Human readable name of this parser. - private static final String PARSER_NAME = "XRY Contacts"; - //All of the known XRY keys for contacts. private static final Set XRY_KEYS = new HashSet() {{ add("name"); diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDeviceGenInfoFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDeviceGenInfoFileParser.java index 5b50e534a6..d610ffb89d 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDeviceGenInfoFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDeviceGenInfoFileParser.java @@ -39,7 +39,7 @@ 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 Device General Info"; + private static final String PARSER_NAME = "XRY DSP"; private static final char KEY_VALUE_DELIMITER = ':'; //All known XRY keys for Device Gen Info reports. @@ -83,7 +83,7 @@ final class XRYDeviceGenInfoFileParser implements XRYFileParser { @Override public void parse(XRYFileReader reader, Content parent) throws IOException, TskCoreException { Path reportPath = reader.getReportPath(); - logger.log(Level.INFO, String.format("Processing report at [ %s ]", reportPath.toString())); + logger.log(Level.INFO, String.format("XRY DSP: Processing report at [ %s ]", reportPath.toString())); while (reader.hasNextEntity()) { String xryEntity = reader.nextEntity(); @@ -93,7 +93,7 @@ final class XRYDeviceGenInfoFileParser implements XRYFileParser { //First line of the entity is the title. if (xryLines.length > 0) { - logger.log(Level.INFO, String.format("Processing [ %s ]", xryLines[0])); + logger.log(Level.INFO, String.format("XRY DSP: Processing [ %s ]", xryLines[0])); } for (int i = 1; i < xryLines.length; i++) { @@ -101,7 +101,7 @@ final class XRYDeviceGenInfoFileParser implements XRYFileParser { //Expecting to see a "Data" key. if (!hasDataKey(xryLine)) { - logger.log(Level.SEVERE, String.format("Expected a 'Data' key " + 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])); @@ -109,7 +109,7 @@ final class XRYDeviceGenInfoFileParser implements XRYFileParser { } if (i + 1 == xryLines.length) { - logger.log(Level.SEVERE, String.format("Found a 'Data' key " + 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])); @@ -123,7 +123,7 @@ final class XRYDeviceGenInfoFileParser implements XRYFileParser { //Expecting to see an "Attribute" key if (!hasAttributeKey(nextXryLine)) { - logger.log(Level.SEVERE, String.format("SEVERE: Expected an 'Attribute' " + 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)); @@ -139,7 +139,7 @@ final class XRYDeviceGenInfoFileParser implements XRYFileParser { //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("Attribute type (in brackets) " + 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)); } diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileParserFactory.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileParserFactory.java index 8dd64996fa..d650510827 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileParserFactory.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileParserFactory.java @@ -37,7 +37,7 @@ final class XRYFileParserFactory { * is null. This is a misuse of the API. It is assumed that the report type * has been tested with the supports method. */ - public static XRYFileParser get(String reportType) { + static XRYFileParser get(String reportType) { if (reportType == null) { throw new IllegalArgumentException("Report type cannot be null"); } @@ -64,7 +64,7 @@ final class XRYFileParserFactory { * @param reportType Report type to test. * @return Indication if the report type can be parsed. */ - public static boolean supports(String reportType) { + static boolean supports(String reportType) { try { //Attempt a get. get(reportType); diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java index 32a0b7eb33..bc3c04259b 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java @@ -101,7 +101,7 @@ final class XRYFileReader implements AutoCloseable { /** * 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 @@ -117,12 +117,12 @@ final class XRYFileReader implements AutoCloseable { throw new IllegalArgumentException(xryFilePath.toString() + " does not " + "have a report type."); } - + /** * Returns the raw path of the XRY report file. - * + * * @return - * @throws IOException + * @throws IOException */ public Path getReportPath() throws IOException { return xryFilePath; @@ -160,6 +160,7 @@ 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. @@ -175,18 +176,18 @@ final class XRYFileReader implements AutoCloseable { throw new NoSuchElementException(); } } - + /** - * Peek at the next XRY entity without consuming it. - * If there are not more XRY entities left, an exception is thrown. - * + * 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 - * @throws NoSuchElementException if there are no more XRY entities to - * read. + * @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()) { + if (hasNextEntity()) { return xryEntity.toString(); } else { throw new NoSuchElementException(); diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYMessagesFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYMessagesFileParser.java new file mode 100755 index 0000000000..c1a80f59ae --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYMessagesFileParser.java @@ -0,0 +1,483 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit 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 XRY_KEYS = new HashSet() { + { + add(TEXT_KEY); + add("direction"); + add("time"); + add("status"); + add("tel"); + add("storage"); + add("index"); + add("folder"); + add("service center"); + add("type"); + } + }; + + //All known XRY namespaces for message reports. + private static final Set XRY_NAMESPACES = new HashSet() { + { + add("to"); + add("from"); + add("participant"); + } + }; + + //All known meta keys. + private static final Set XRY_META_KEYS = new HashSet() { + { + 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 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 are 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 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 attributes = new ArrayList<>(); + + String namespace = ""; + for (int i = 1; i < xryLines.length; i++) { + String xryLine = xryLines[i]; + String normalizedXryLine = xryLine.trim().toLowerCase(); + + if (XRY_NAMESPACES.contains(normalizedXryLine)) { + 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, "XRY DSP: This reference 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."); + } + + 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); + } + } + } + + /** + * + * @param referenceNumber + * @param segmentNumber + * @param reader + * @return + * @throws IOException + */ + private String getSegmentedText(int referenceNumber, int segmentNumber, XRYFileReader reader) throws IOException { + StringBuilder segmentedText = new StringBuilder(); + + 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 != segmentNumber + 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, segmentNumber)); + } + + 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(' '); + } + } + } + + segmentNumber = 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(); + + switch (normalizedKey) { + case "time": + //Tranform value to epoch ms + try { + String dateTime = removeDateTimeLocale(value); + String normalizedDateTime = dateTime.trim(); + long dateTimeInEpoch = calculateMsSinceEpoch(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": + String normalizedValue = value.toLowerCase(); + 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": + //Ignore for now. + return null; + case "storage": + //Ignore for now. + return null; + case "index": + //Ignore for now. + return null; + case "folder": + //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 calculateMsSinceEpoch(String dateTime) { + LocalDateTime localDateTime = LocalDateTime.parse(dateTime, DATE_TIME_PARSER); + //Assume dates have no offset. + return localDateTime.toInstant(ZoneOffset.UTC).toEpochMilli(); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java index 3c6e881ada..a7443e7f48 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYWebBookmarksFileParser.java @@ -31,9 +31,6 @@ import org.sleuthkit.datamodel.TskCoreException; */ final class XRYWebBookmarksFileParser extends AbstractSingleKeyValueParser { - //Human readable name of this parser. - private static final String PARSER_NAME = "XRY Web Bookmarks"; - //All known XRY keys for web bookmarks. private static final Map KEY_TO_TYPE = new HashMap() { From 875b789abb8439a1b95a12fb5c56cfaa56bd5c7a Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Wed, 13 Nov 2019 16:26:02 -0500 Subject: [PATCH 11/53] Update comments, add some additional logging, and address some codacy comments --- .../xry/XRYCallsFileParser.java | 2 + .../xry/XRYMessagesFileParser.java | 42 ++++++++++++++----- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java index af59c6891d..260fccd40f 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java @@ -119,8 +119,10 @@ final class XRYCallsFileParser extends AbstractSingleKeyValueParser { 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)); diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYMessagesFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYMessagesFileParser.java index c1a80f59ae..253676f0e9 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYMessagesFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYMessagesFileParser.java @@ -74,6 +74,7 @@ final class XRYMessagesFileParser implements XRYFileParser { add("folder"); add("service center"); add("type"); + add("name"); } }; @@ -100,12 +101,12 @@ final class XRYMessagesFileParser implements XRYFileParser { * 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 aggregate all of the text pieces and + * 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 are correct, otherwise an error will appear in the logs. + * assumption is correct, otherwise an error will appear in the logs. * * @param reader The XRYFileReader that reads XRY entities from the * Message-SMS report. @@ -235,9 +236,11 @@ final class XRYMessagesFileParser implements XRYFileParser { } /** - * - * @param referenceNumber - * @param segmentNumber + * 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 @@ -245,6 +248,7 @@ final class XRYMessagesFileParser implements XRYFileParser { 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(); @@ -269,11 +273,11 @@ final class XRYMessagesFileParser implements XRYFileParser { + "segment with reference number [ %d ]", nextEntityLines[0], referenceNumber)); } - if (nextSegmentNumber != segmentNumber + 1) { + 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, segmentNumber)); + + "text will be out of order.", nextSegmentNumber, currentSegmentNumber)); } for (int i = 1; i < nextEntityLines.length; i++) { @@ -301,7 +305,7 @@ final class XRYMessagesFileParser implements XRYFileParser { } } - segmentNumber = nextSegmentNumber; + currentSegmentNumber = nextSegmentNumber; } //Remove the trailing space. @@ -383,6 +387,7 @@ final class XRYMessagesFileParser implements XRYFileParser { 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": @@ -403,7 +408,6 @@ final class XRYMessagesFileParser implements XRYFileParser { case "text": return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TEXT, PARSER_NAME, value); case "status": - String normalizedValue = value.toLowerCase(); switch (normalizedValue) { case "read": return new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_READ_STATUS, PARSER_NAME, READ); @@ -424,8 +428,21 @@ final class XRYMessagesFileParser implements XRYFileParser { return null; } case "type": - //Ignore for now. - return null; + 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; @@ -435,6 +452,9 @@ final class XRYMessagesFileParser implements XRYFileParser { case "folder": //Ignore for now. return null; + case "name": + //Ignore for now. + return null; case "service center": //Ignore for now. return null; From 4a8ad067c80bd61b16426f341bda95c8fc5db2a7 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Thu, 14 Nov 2019 12:51:59 -0500 Subject: [PATCH 12/53] Make IG check for offline deletion of data sources --- .../sleuthkit/autopsy/casemodule/Case.java | 7 ++- .../imagegallery/datamodel/DrawableDB.java | 62 ++++++++++++++++++- 2 files changed, 65 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 99ec09d315..a66b6f5c71 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -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); } /** diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index d875657cc1..2097fea5f3 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -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,6 +79,7 @@ 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 @@ -230,7 +230,7 @@ public final class DrawableDB { 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 } @@ -374,6 +374,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 = tskCase.executeQuery("SELECT obj_id FROM data_source_info"); //NON-NLS + Statement statement = con.createStatement()) { + /* + * Get the data source object IDs from the case database. + */ + ResultSet caseDbResults = caseDbQuery.getResultSet(); + Set validDataSourceObjIDs = new HashSet<>(); + while (caseDbResults.next()) { + validDataSourceObjIDs.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 staleDataSourceObjIDs = new ArrayList<>(); + try (ResultSet drawablesDbResults = statement.executeQuery("SELECT ds_obj_id FROM datasources")) { //NON-NLS + while (drawablesDbResults.next()) { + long dataSourceObjID = drawablesDbResults.getLong(1); + if (!validDataSourceObjIDs.contains(dataSourceObjID)) { + staleDataSourceObjIDs.add(dataSourceObjID); + } + } + } + + /* + * Delete the surplus data sources from this local drawables + * database. The delete cascades. + */ + if (!staleDataSourceObjIDs.isEmpty()) { + String inClause = StringUtils.join(staleDataSourceObjIDs, ','); + String delete = "DELETE FROM datasources where ds_obj_id IN (" + inClause + ")"; //NON-NLS + statement.execute(delete); + } + 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 @@ -2037,7 +2093,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 { From d154ac98b69b52bee5b2aa44b16dca117225d6ef Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Thu, 14 Nov 2019 13:11:08 -0500 Subject: [PATCH 13/53] First pass of the waypoint filtering --- .../autopsy/geolocation/Bundle.properties | 10 + .../geolocation/Bundle.properties-MERGED | 12 + .../autopsy/geolocation/CheckBoxJList.java | 109 ++++ .../geolocation/CheckBoxListPanel.form | 71 +++ .../geolocation/CheckBoxListPanel.java | 234 +++++++++ .../autopsy/geolocation/GeoFilterPanel.form | 168 ++++++ .../autopsy/geolocation/GeoFilterPanel.java | 360 +++++++++++++ .../geolocation/GeoLocationUIException.java | 46 ++ .../geolocation/GeolocationTopComponent.form | 14 +- .../geolocation/GeolocationTopComponent.java | 77 ++- .../autopsy/geolocation/HidingPane.java | 120 +++++ .../autopsy/geolocation/MapPanel.java | 4 +- .../autopsy/geolocation/MapWaypoint.java | 25 +- .../autopsy/geolocation/VerticalLabelUI.java | 110 ++++ .../geolocation/datamodel/Waypoint.java | 180 ------- .../datamodel/WaypointBuilder.java | 484 ++++++++++++++++++ .../autopsy/geolocation/images/blueGeo16.png | Bin 0 -> 741 bytes .../autopsy/geolocation/images/blueGeo64.png | Bin 0 -> 3972 bytes .../autopsy/geolocation/images/funnel.png | Bin 0 -> 591 bytes .../autopsy/geolocation/images/tick.png | Bin 0 -> 582 bytes .../autopsy/report/modules/kml/KMLReport.java | 11 +- 21 files changed, 1844 insertions(+), 191 deletions(-) create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxJList.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.form create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.form create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/GeoLocationUIException.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/HidingPane.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/VerticalLabelUI.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/images/blueGeo16.png create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/images/blueGeo64.png create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/images/funnel.png create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/images/tick.png diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties b/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties index 8fa233ea33..4fe5148ba5 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties @@ -7,3 +7,13 @@ RefreshPanel.closeButton.text= MapPanel.cordLabel.text= WaypointDetailPanel.closeButton.text= WaypointDetailPanel.imageLabel.text= +GeoFilterPanel.waypointSettings.border.title= +GeoFilterPanel.allButton.text=Show All +GeoFilterPanel.mostRecentButton.text=Hide items older than +GeoFilterPanel.applyButton.text=Apply +GeoFilterPanel.showWaypointsWOTSCheckBox.text=Show waypoints without time stamp +GeoFilterPanel.daysLabel.text=days +CheckBoxListPanel.titleLabel.text=jLabel1 +CheckBoxListPanel.checkButton.text=Check All +CheckBoxListPanel.uncheckButton.text=Uncheck All +GeoFilterPanel.optionsLabel.text=Waypoints diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties-MERGED index 95dd0d23f0..ed550cd8db 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties-MERGED @@ -1,6 +1,8 @@ CTL_OpenGeolocation=Geolocation CTL_GeolocationTopComponentAction=GeolocationTopComponent CTL_GeolocationTopComponent=Geolocation +GeoFilterPanel_DataSource_List_Title=Data Sources +GeoFilterPanel_empty_dataSource=Data Source list is empty. GLTopComponent_initilzation_error=An error occurred during waypoint initilization. Geolocation data maybe incomplete. GLTopComponent_name=Geolocation MayWaypoint_ExternalViewer_label=Open in ExternalViewer @@ -12,4 +14,14 @@ RefreshPanel.closeButton.text= MapPanel.cordLabel.text= WaypointDetailPanel.closeButton.text= WaypointDetailPanel.imageLabel.text= +GeoFilterPanel.waypointSettings.border.title= +GeoFilterPanel.allButton.text=Show All +GeoFilterPanel.mostRecentButton.text=Hide items older than +GeoFilterPanel.applyButton.text=Apply +GeoFilterPanel.showWaypointsWOTSCheckBox.text=Show waypoints without time stamp +GeoFilterPanel.daysLabel.text=days +CheckBoxListPanel.titleLabel.text=jLabel1 +CheckBoxListPanel.checkButton.text=Check All +CheckBoxListPanel.uncheckButton.text=Uncheck All +GeoFilterPanel.optionsLabel.text=Waypoints WaypointExtractAction_label=Extract Files(s) diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxJList.java b/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxJList.java new file mode 100755 index 0000000000..52276b6046 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxJList.java @@ -0,0 +1,109 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit 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 extends JList { + + 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 { + + private static final long serialVersionUID = 1L; + + @Override + public Component getListCellRendererComponent( + JList list, CheckBoxJList.CheckboxListItem value, int index, + boolean isSelected, boolean cellHasFocus) { + + setBackground(list.getBackground()); + setSelected(value.isChecked()); + setText(value.getDisplayName()); + return this; + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.form b/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.form new file mode 100755 index 0000000000..75fb1d4c62 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.form @@ -0,0 +1,71 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.java b/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.java new file mode 100755 index 0000000000..8e465dd3a3 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.java @@ -0,0 +1,234 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit 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 extends javax.swing.JPanel { + + private static final long serialVersionUID = 1L; + + private final DefaultListModel> model = new DefaultListModel<>(); + private final CheckBoxJList> 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)); + } + + /** + * Returns a list of all of the selected elements. + * + * @return List of selected elements. + */ + List getSelectedElements() { + List selectedElements = new ArrayList<>(); + Enumeration> elements = model.elements(); + + while (elements.hasMoreElements()) { + ObjectCheckBox 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> enumeration = model.elements(); + while (enumeration.hasMoreElements()) { + ObjectCheckBox 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") + // //GEN-BEGIN:initComponents + private void initComponents() { + java.awt.GridBagConstraints gridBagConstraints; + + titleLabel = new javax.swing.JLabel(); + uncheckButton = new 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); + }// //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.JButton checkButton; + private javax.swing.JScrollPane scrollPane; + private javax.swing.JLabel titleLabel; + private javax.swing.JButton uncheckButton; + // End of variables declaration//GEN-END:variables + + /** + * Wrapper around T that implements CheckboxListItem + * + * @param + */ + final class ObjectCheckBox 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; + } + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.form b/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.form new file mode 100755 index 0000000000..897341c482 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.form @@ -0,0 +1,168 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.java b/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.java new file mode 100755 index 0000000000..bb3035b5d5 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.java @@ -0,0 +1,360 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit 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 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); + + 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 { + boolean showAll = allButton.isSelected(); + boolean withTimeStamp = showWaypointsWOTSCheckBox.isSelected(); + int dayCnt = numberModel.getNumber().intValue(); + + List dataSources = checkboxPanel.getSelectedElements(); + + if (dataSources.isEmpty()) { + throw new GeoLocationUIException(Bundle.GeoFilterPanel_empty_dataSource()); + } + + return new GeoFilter(showAll, withTimeStamp, dayCnt, dataSources); + } + + /** + * Initialize the checkbox list panel + * + * @throws TskCoreException + */ + private void initCheckboxList() throws TskCoreException { + final SleuthkitCase sleuthkitCase = Case.getCurrentCase().getSleuthkitCase(); + + 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") + // //GEN-BEGIN:initComponents + private void initComponents() { + java.awt.GridBagConstraints gridBagConstraints; + + buttonGroup = new javax.swing.ButtonGroup(); + 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(); + 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/geolocation/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/geolocation/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); + }// //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.ButtonGroup buttonGroup; + private javax.swing.JPanel buttonPanel; + private javax.swing.JSpinner daysSpinner; + private javax.swing.JRadioButton mostRecentButton; + private javax.swing.JCheckBox showWaypointsWOTSCheckBox; + private javax.swing.JPanel waypointSettings; + // 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 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 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 showAll() { + 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 showWithoutTimeStamp() { + 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 getDataSources() { + return Collections.unmodifiableList(dataSources); + } + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeoLocationUIException.java b/Core/src/org/sleuthkit/autopsy/geolocation/GeoLocationUIException.java new file mode 100755 index 0000000000..7ce6837914 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeoLocationUIException.java @@ -0,0 +1,46 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * contact: carrier sleuthkit 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); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.form b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.form index bddebcbc0c..16cd5368a6 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.form +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.form @@ -11,6 +11,7 @@ + @@ -23,6 +24,17 @@ + + + + + + + + + + + - + \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java index bf54ece196..f29bfca802 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java @@ -27,6 +27,7 @@ import java.util.List; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.logging.Level; +import javax.swing.SwingUtilities; import javax.swing.SwingWorker; import org.openide.util.NbBundle.Messages; import org.openide.windows.RetainLocation; @@ -37,6 +38,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 +65,7 @@ public final class GeolocationTopComponent extends TopComponent { private static final Set INGEST_MODULE_EVENTS_OF_INTEREST = EnumSet.of(DATA_ADDED); private final PropertyChangeListener ingestListener; + private final GeoFilterPanel geoFilterPanel; final RefreshPanel refreshPanel = new RefreshPanel(); @@ -109,6 +116,16 @@ public final class GeolocationTopComponent extends TopComponent { showRefreshPanel(false); } }); + + geoFilterPanel = new GeoFilterPanel(); + filterPane.setPanel(geoFilterPanel); + geoFilterPanel.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + filterWaypoints(); + } + }); + } @Override @@ -151,14 +168,12 @@ public final class GeolocationTopComponent extends TopComponent { /** * Use a SwingWorker thread to get a list of waypoints. - * */ private void initWaypoints() { SwingWorker, MapWaypoint> worker = new SwingWorker, MapWaypoint>() { @Override protected List doInBackground() throws Exception { Case currentCase = Case.getCurrentCaseThrows(); - return MapWaypoint.getWaypoints(currentCase.getSleuthkitCase()); } @@ -189,6 +204,59 @@ public final class GeolocationTopComponent extends TopComponent { worker.execute(); } + /** + * Filters the list of waypoints based on the user selections in the filter + * pane. + */ + @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 filterWaypoints() { + GeoFilter filters; + + // Show a warning message if the user has not selected a data source + try { + filters = geoFilterPanel.getFilterState(); + } catch (GeoLocationUIException ex) { + MessageNotifyUtil.Notify.info( + Bundle.GeoTopComponent_filer_data_invalid_Title(), + Bundle.GeoTopComponent_filer_data_invalid_msg()); + return; + } + + SwingUtilities.invokeLater(new Runnable() { + public void run() { + Case currentCase = Case.getCurrentCase(); + try { + WaypointBuilder.getAllWaypoints(currentCase.getSleuthkitCase(), filters.getDataSources(), filters.showAll(), filters.getMostRecentNumDays(), filters.showWithoutTimeStamp(), new WaypointFilterQueryCallBack() { + @Override + public void process(List waypoints) { + // If the list is empty, tell the user and do not change + // the visible waypoints. + if (waypoints == null || waypoints.isEmpty()) { + MessageNotifyUtil.Notify.info( + Bundle.GeoTopComponent_no_waypoints_returned_Title(), + Bundle.GeoTopComponent_no_waypoints_returned()); + return; + } + mapPanel.setWaypoints(MapWaypoint.getWaypoints(waypoints)); + } + }); + } catch (GeoLocationDataException ex) { + logger.log(Level.SEVERE, "Failed to filter waypoints.", ex); + MessageNotifyUtil.Notify.error( + Bundle.GeoTopComponent_filter_exception_Title(), + Bundle.GeoTopComponent_filter_exception_msg()); + } + } + }); + } + /** * 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 @@ -199,13 +267,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); }// //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 } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/HidingPane.java b/Core/src/org/sleuthkit/autopsy/geolocation/HidingPane.java new file mode 100755 index 0000000000..492888d15f --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/HidingPane.java @@ -0,0 +1,120 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +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/geolocation/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; + } + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java b/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java index 326d33f23e..da4a704dcf 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java @@ -56,7 +56,7 @@ 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,7 +76,7 @@ final class MapPanel extends javax.swing.JPanel { /** * Creates new form MapPanel */ - MapPanel() { + public MapPanel() { initComponents(); initMap(); diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java index 7eb765151a..7a28f49737 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java @@ -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 getWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { - List points = Waypoint.getAllWaypoints(skCase); + List points = WaypointBuilder.getAllWaypoints(skCase); List 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 getWaypoints(List dmWaypoints) { + List 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. diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/VerticalLabelUI.java b/Core/src/org/sleuthkit/autopsy/geolocation/VerticalLabelUI.java new file mode 100755 index 0000000000..2580031dfb --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/VerticalLabelUI.java @@ -0,0 +1,110 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.geolocation; + +import java.awt.Dimension; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Insets; +import java.awt.Rectangle; +import java.awt.geom.AffineTransform; + +import javax.swing.Icon; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.plaf.basic.BasicLabelUI; + + +/** + * This class is an overload of BasicLabelUI to draw labels vertically. + * + * This code was found at: + * https://tech.chitgoks.com/2009/11/13/rotate-jlabel-vertically/ + * + */ +final class VerticalLabelUI extends BasicLabelUI { + + private static final Rectangle paintIconR = new Rectangle(); + private static final Rectangle paintTextR = new Rectangle(); + private static final Rectangle paintViewR = new Rectangle(); + private static Insets paintViewInsets = new Insets(0, 0, 0, 0); + + static { + labelUI = new VerticalLabelUI(false); + } + + final boolean clockwise; + + /** + * Construct a new VerticalLabelUI + * @param clockwise + */ + VerticalLabelUI(boolean clockwise) { + super(); + this.clockwise = clockwise; + } + + @Override + public Dimension getPreferredSize(JComponent c) { + Dimension dim = super.getPreferredSize(c); + return new Dimension( dim.height, dim.width ); + } + + @Override + public void paint(Graphics g, JComponent c) { + JLabel label = (JLabel)c; + String text = label.getText(); + Icon icon = (label.isEnabled()) ? label.getIcon() : label.getDisabledIcon(); + + if ((icon == null) && (text == null)) { + return; + } + + FontMetrics fm = g.getFontMetrics(); + paintViewInsets = c.getInsets(paintViewInsets); + + paintViewR.x = paintViewInsets.left; + paintViewR.y = paintViewInsets.top; + + // Use inverted height & width + paintViewR.height = c.getWidth() - (paintViewInsets.left + paintViewInsets.right); + paintViewR.width = c.getHeight() - (paintViewInsets.top + paintViewInsets.bottom); + + paintIconR.x = paintIconR.y = paintIconR.width = paintIconR.height = 0; + paintTextR.x = paintTextR.y = paintTextR.width = paintTextR.height = 0; + + String clippedText = layoutCL(label, fm, text, icon, paintViewR, paintIconR, paintTextR); + + Graphics2D g2 = (Graphics2D) g; + AffineTransform tr = g2.getTransform(); + if (clockwise) { + g2.rotate( Math.PI / 2 ); + g2.translate( 0, - c.getWidth() ); + } else { + g2.rotate( - Math.PI / 2 ); + g2.translate( - c.getHeight(), 0 ); + } + + if (icon != null) { + icon.paintIcon(c, g, paintIconR.x, paintIconR.y); + } + + if (text != null) { + int textX = paintTextR.x; + int textY = paintTextR.y + fm.getAscent(); + + if (label.isEnabled()) { + paintEnabledText(label, g, clippedText, textX, textY); + } else { + paintDisabledText(label, g, clippedText, textX, textY); + } + } + g2.setTransform( tr ); + } + +} + diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java index bd5b39b6b7..428c659eca 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java @@ -262,186 +262,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 getAllWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { - List 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 getTrackpointWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { - List 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 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 getEXIFWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { - List 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 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 getSearchWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { - List 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 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 getLastKnownWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { - List 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 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 getBookmarkWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { - List 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 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 diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java new file mode 100755 index 0000000000..0776d029fd --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java @@ -0,0 +1,484 @@ +/* + * + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * contact: carrier sleuthkit 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. Replace the %s after + // after the SELECT with the list of parameters to return + final static String GEO_ARTIFACT_QUERY + = "SELECT artifact_id, artifact_type_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 " + + "JOIN blackboard_artifacts ON blackboard_attributes.artifact_id = blackboard_artifacts.artifact_id " + + "WHERE blackboard_attributes.attribute_type_id IN(%d, %d) " + + "AND data_source_obj_id IN (%s)"; //NON-NLS + + 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 + + " )"; + + 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 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 getAllWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { + List 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 getTrackpointWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { + List 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 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 getEXIFWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { + List 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 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 getSearchWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { + List 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 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 getLastKnownWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { + List 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 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 getBookmarkWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { + List 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 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 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 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 dataSources) { + String query = ""; + + query = String.format(SELECT_WO_TIMESTAMP, + String.format(GEO_ARTIFACT_QUERY, + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID(), + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED.getTypeID()), + getWaypointListQuery(dataSources)); + + return query; + } + + /** + * 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 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 (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 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 getWaypointForArtifact(BlackboardArtifact artifact, BlackboardArtifact.ARTIFACT_TYPE type) throws GeoLocationDataException { + List 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; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/images/blueGeo16.png b/Core/src/org/sleuthkit/autopsy/geolocation/images/blueGeo16.png new file mode 100755 index 0000000000000000000000000000000000000000..b89f013c410be1b7ba70c62153c598846ab759c9 GIT binary patch literal 741 zcmVL}000McNliru;|mNI9}-50hXw!u0&+=2 zK~y-)ol#v#Q(+i>&dvmn%fNv&)@GU*jDDy!9415NHndSU2BWYjq6ncI=@0BOio%O7 zyGt)3vgu7=l*h&J$u=B34Nf4Z8C$iZGcQTp2`>|LvRCRboBNd87q7|9U6STMAd|Xv^ z_8{j1oY`Ppz%T?j2RQE;RMoCS#U^sWK+~p>N_E|;tLr?)m<1vU2IWBn7={GHu)&Sc zj+$y&86Ca%te|rgNAP%(ml-3NrW12>gRe9#a$t5g9^qVlMzs?Q5;tG@o%52Ee9Klcb!0uz64pzCiwFqVa3}AaJc4Q=j z4bj+mI~@qjh?<(O!sF4z=JIbM5a@oi9@nw@Ar_0m=j%KJOo1p5B7sun3tCz(9goMO z_-BN}7p}S8(?U_ci(v5Lb^H(f{(-FD-=775NaV=69Op&`UhmL3+jdIR)41(ld49e*cDYL~ zMJ^ACq$D0%iBeJ}uxh(@B?+Z1+at9%%ieC<4b$jXG{( z#EOl?wtSb7sGSvM>x?YRvMgKBwbnsdG-ZpVwCBwCz5OvWyEC&ZK}+q@0Xo29hlAPO zd7tk+p7(h_GSv7O7+9$gnL)-s#ufr>F#y%Ni=~O^7$6Q1nMaB8F9PNW zV*x|ghYbMGG!440SN-`|HNVXLkgHZL3khNTwk(J42nO4}Ez6NPfxt|J!!fApEU1Cm zAR&PW;G83u{{*>g68ZdT7{)mWkvhxy*t10GR~V~5qU(wAS^&XnVf@OKOTW$-`@Klx z7uGjMzF5}~S&VR~AF|vG&P{O6!MT7C1kM2=06`LjU?3sEFeZ`99Y-#A2>JY>v|$|j z6`=f5lA^D~y$3r^rf z5Vp}=6wj@f3lL!eVj3SKZyZK0_gaecukgnT>nDn(^u;uxM3m*!$wl1@baTzZJK(^6>Y){|-k#(KOdO03KWC1}$g?ofx z7C6KKS_@zUfEh!YcH3P}_&K3CEdV%(4-BmORxsG|axC_zax{7~Ow+Je*fOBt>dv`s zr1cJPDuUz~UjWR05fwee0x|_5mVxv8Kxlhe)4p=g^(>$~RpT{Hy8)mV7A<tJluR=O;ETSn%Dxw)R!vrcqo~&Rh@b^90)%$Gtt?;X3yl%(?F~0uX|O^HvBE zZcvo{y?uT2x9WQ0a?L#e0OrnZ|EspPuiV|+`zKN{c{wgyx?&|EQAl!tAXYS*yF!49 zKscXu5~C>O4+7`ykfgCbSxz4A?d?C<*Vk8(7f6-X`;{w~&R3L%yL)=Stq>&7s=D2m z3Nc^I;v^V^$ipjxD%$Ul$z!YWU~VhqWw|E zjE+jHfpV=kS|x7<6C!&@d;5yTb%B`$!#qa2+?gEX$pR{!Ie<*&J!G>-J~50FXN0&k z4lpK3(HV*|e}N?RMA30g!( zTPW1=otBoH!K6By&bo~*6Z25Pjd5J~^ciF_2M!s=>7Owc*vDA#j3hOE1`s1kH5g3B&4irh)dULn&= zV{GgRW*DdcM%Rb`Xn1(v#lgYHsT^ss+;+2>(088##Mg*+K0|n&31Rqt$D3q%fmAW1 z(l-I12dYklG))5$L6+;j+R!i`!tUh!?(T4Y>$_{FJeqa zATSHo1EG9!64vTxv&WFj9e+uZ!Uw0?VgsS)WclC-aKt|(7L*pbw6S>?`1He9APneYg;CPF!CsF|WwJaYZA>=|c(?|#0C`vzMdGgwNq`)zj`Mo(`(-v=V@Nxq!C?9*bN3hL{R2qA)dYC(lfBIBcU`Zz$6rzI`Y z)1v6f*c_0l2+Av4?$JG4q5Jb7~pHJMZ9#3U%mQfnKASGx!jFwGMSr-NtXQG zAQilja|Ml!2LZ6*O7czx05IZw{MB^!7z|^gp!*fFF!`k{4zOM*$~|DL{Y6c?^?prT zuQ>U@{ebS_il%AJM1e;PBlhsb#2>&kBQ8y`R1YYYKaQrR{UBmbRFZeDSrSVolj!a3 z&I9Hh0X5bxE1g!o=U(;4n;1<1vjOxgM5F6~LrePlVj`J5eb_BqC6h^L+KR}$d9yc2 zQu>i>cEvpt6Mu|+es&@0dO;@7HEZ<`ptW_!7{E3plP7Az1Hc#?GmO!vQt7=vRMdV5 z5%B2wMfZ2tYZF9003HC*`*$+7X{DwudlbMS#&E&%LYfPRdHMWrHKtPEs>|hi!Fdow zW~shdBA*k81lroRL6Wlnsp|=;3IXf=)Vbc?nJuO{dRs8Kj9EiWFFo~?M(!0#NN{l82+m_L@{3@Yi(nXYAh_yM zm#Cz~dcd_yFegBkQt0Y>1(C?nzckJI9f?GoYBm{il9ePyew54Ye>amE1`sGCg}cgP znW2Zp>I@JJ>zz#3D9lA~U0tWU+bsNsAT%`{K(u-PcFsdDB@#mwcLP$T>`c=%#N%5= zC8^>2snkXo##smUuRrjQ%?w-!?iG z%3bknu-L0_dy^>tTsplGM2csC;WogZwH*ylxD%#(3Ph#sl5^L<)c#(R%a}Q92z7Pm zcWxcp`HQJ`T2mg5+8_Te=SL`S?1m7E)td4$K=9IOk8q8}g&PoOrr@|d6)TK8nJVfy z=FEOM&3WDZ*E1r&A^>aGjj*-rM%dLtLD%(5M5%|2{1yn2vSh0(&HBi-KfrEXi~YR2 zwI=U~jJwO74TLFUe(VVl$;lc~XcI@0;c>o1HPAYx@VIrBmb5 zp{T#jE&gqo0kpR5Ktn@v?9!#aKbvZ|c~t<`t{cJT!7jdeuq(TE-N;q*fv)dK1E2n@ z5W^5O?h;AK(=3(fhlM=hzMovp6_pv0GX&HN=Hjy+5I;Zj87p0GkK9%AQ~O z2o3@tye`CHyH%x<$*x;Ub8`dlSgni)xR@X35?Wh#Asjxpi7~$UdRMmSerlQx2dlCARRf^75_Ny4C~sT_ z&Vg)-Oztr7cXIA7kC)$2;doIXFow>~p|hNao=zl&a@APR1`&%r%^Mq!{&;9;*E8;QPUQn#Dw}Q5|y=ZCK{~70X|29>Xx#j@4N8EsMcQB}f;S+mXjC-4mj|P*w zji!>lL5xZ0>3JRidmtVkzErK)uWAVRLxDk@XVPRh7xiDx<`-F8U3{%<>E43U?Quc` z(B1t4nwk#n*Y(8rrm_ugS^;oCIGOy2$L7uYSdu62$z~S@IBzVo&&NGWEbc8RDoirM z;dAKec^;~ozT@P{k4CF)eAPq1qaah=+p3y;DiqpNPTw7~wZ(B=n>addQSMIbQ# z!+3n#d)18lngd|vf^8;|`ahA#;3-A>2ViR1OP?Jpra#>y1Gw(vVQ%pW2oUFp~eU24gVMp z9lTgyzYpA0ikm3NtM0f*+o8}X+S_;U6GGj%VMBGx-n0UsX&MrVEhdrr=k4tq0hYJh z<)XyJU9NBGjRtyqH)a9z$#{JGnOgm`RL!bDc_lgdVJy};z>w=6A726@vt+Jlc}oyo zU0cx6q3_9LIv+fJ`a@BR%s;Im;F|M)r?GKAg+j+5xa?tl019QVvvV_XUiZ*jZ*4Ma z>H5>+0UmP1#JHkdmI1MRd}6W3?JWRQ#9~jQzW&sYb$#2D)#Ur1D*y#@bn+vbH*d}v zRh_)+@?>9gHrrYNfJi|!`Yt*;;**kO+?7lwvzVqs!ZaRRx9&I|3Y~kRwe3wXmM=`_ zWI2slvxcFnlYg)4`o-yR{b>ilG0HTFRm|&CTy(#*7b!I1j#7?Oy*{4mRD0d;jZcATV*9a|wo z*|KEKlz}ct89+ipctWkZ*HXK5WI(V{i9JH)C$JF2gjD4ZFri8nLW#r@7As+}6Fbcp z=f3!rv}stl((#M$yZ78rM_Viw^gm+o!}GkS-EMcM)9Fx>BueiJA?$oUPlZBZYTNeM zahx&E1n)mvt=6Y&0?1Cg-9DR6r;;F!V;T;}6a*vE^$Z9sf+Ja0ftb+upC8@GfThuB z_|0bXEDS?94=D^I8Vn*b3{yJ0u1m#YkxoxvogjmoB_Q&99EX)kh5G$I90!PMLlu!} z=BZRFQMp_u$0yTJ=#koJ`7O3=%>^1_oubS(KvF!NHRku=|qs z=`TSl9VV&OYJM)4lO(#nB-`HJqP@LGuW|nw_`W&?ace4)q+YKdF9|eB;CT<rcA zygf#7$7q@4e0TT3OV=&a{{G{)c>e%gY}|uhuSb)~gqx}KKUR%!_brMddCybgKCKb_ zOPHL&B(vE}Vr>uZ5VhlTrC`U=XtZ9!s%V;~eqKrM`8?+D1t+bJZzBO4r!~&gCA(5( zY;0mGI>0-+g=n0HX;~IW7D)YmY7+w?^Im7n5!19Y{80h-SPwi~R-}D}jq37m;1`gn dzkxpj3;^AW#{FvID!%{#002ovPDHLkV1h0A2_^sl literal 0 HcmV?d00001 diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/images/tick.png b/Core/src/org/sleuthkit/autopsy/geolocation/images/tick.png new file mode 100755 index 0000000000000000000000000000000000000000..a7d7a96be3f2282a62e3c0733bac89c7f6de7b4a GIT binary patch literal 582 zcmV-M0=fN(P)tYd4K$mX5uyr2F@fdffp{&DSHtl4|Bn9=ukpCx`#%PTUr-D(@b7;C zhJXJjrnw{;1KBM=6&|E`fsNrGL!Y6%zUh}QUl`(@V)PmQFtotEKmafTHP0uP}7&%m4pcKQ#X)BiAJ2y+PrtBI>9eEIt2-_c7)?*Lsf z5vX5*Af@m-wBJRV%#Fiz=BdPr0!2^az8K(fJ0K@xl?-_o)`1bna-SH57SeZUButL)d$cI_) zkpl5Q!w#U+YtHUCagF&eebP3jhEB literal 0 HcmV?d00001 diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/kml/KMLReport.java b/Core/src/org/sleuthkit/autopsy/report/modules/kml/KMLReport.java index eabdeef98b..a140338524 100644 --- a/Core/src/org/sleuthkit/autopsy/report/modules/kml/KMLReport.java +++ b/Core/src/org/sleuthkit/autopsy/report/modules/kml/KMLReport.java @@ -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()); } /** From d8b4cf355cdc3ebd3821415b67febe94b09b0712 Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Thu, 14 Nov 2019 14:10:49 -0500 Subject: [PATCH 14/53] Addressed most codacy issues and moved images --- .../autopsy/geolocation/Bundle.properties | 6 ++-- .../geolocation/Bundle.properties-MERGED | 13 +++++++-- .../geolocation/CheckBoxListPanel.form | 8 ++++++ .../geolocation/CheckBoxListPanel.java | 6 ++-- .../autopsy/geolocation/GeoFilterPanel.form | 16 +++++++++-- .../autopsy/geolocation/GeoFilterPanel.java | 27 +++++++----------- .../geolocation/GeolocationTopComponent.java | 23 +++++++++------ .../autopsy/geolocation/HidingPane.java | 2 +- .../autopsy/geolocation/RefreshPanel.form | 4 +-- .../autopsy/geolocation/RefreshPanel.java | 4 +-- .../datamodel/WaypointBuilder.java | 16 +++++++---- .../geolocation/images/cross-script.png | Bin 623 -> 0 bytes .../autopsy/geolocation/images/tick.png | Bin 582 -> 0 bytes .../images/arrow-circle-double-135.png | Bin .../{geolocation => }/images/blueGeo16.png | Bin .../{geolocation => }/images/blueGeo64.png | Bin .../{geolocation => }/images/funnel.png | Bin 17 files changed, 78 insertions(+), 47 deletions(-) delete mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/images/cross-script.png delete mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/images/tick.png rename Core/src/org/sleuthkit/autopsy/{geolocation => }/images/arrow-circle-double-135.png (100%) rename Core/src/org/sleuthkit/autopsy/{geolocation => }/images/blueGeo16.png (100%) rename Core/src/org/sleuthkit/autopsy/{geolocation => }/images/blueGeo64.png (100%) rename Core/src/org/sleuthkit/autopsy/{geolocation => }/images/funnel.png (100%) diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties b/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties index 4fe5148ba5..bec0dda190 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties @@ -9,10 +9,10 @@ WaypointDetailPanel.closeButton.text= WaypointDetailPanel.imageLabel.text= GeoFilterPanel.waypointSettings.border.title= GeoFilterPanel.allButton.text=Show All -GeoFilterPanel.mostRecentButton.text=Hide items older than +GeoFilterPanel.mostRecentButton.text=Show only last GeoFilterPanel.applyButton.text=Apply -GeoFilterPanel.showWaypointsWOTSCheckBox.text=Show waypoints without time stamp -GeoFilterPanel.daysLabel.text=days +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 diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties-MERGED index ed550cd8db..6b90c60944 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties-MERGED @@ -3,8 +3,15 @@ CTL_GeolocationTopComponentAction=GeolocationTopComponent CTL_GeolocationTopComponent=Geolocation GeoFilterPanel_DataSource_List_Title=Data Sources GeoFilterPanel_empty_dataSource=Data Source list is empty. +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 MayWaypoint_ExternalViewer_label=Open in ExternalViewer OpenGeolocationAction_displayName=Geolocation OpenGeolocationAction_name=Geolocation @@ -16,10 +23,10 @@ WaypointDetailPanel.closeButton.text= WaypointDetailPanel.imageLabel.text= GeoFilterPanel.waypointSettings.border.title= GeoFilterPanel.allButton.text=Show All -GeoFilterPanel.mostRecentButton.text=Hide items older than +GeoFilterPanel.mostRecentButton.text=Show only last GeoFilterPanel.applyButton.text=Apply -GeoFilterPanel.showWaypointsWOTSCheckBox.text=Show waypoints without time stamp -GeoFilterPanel.daysLabel.text=days +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 diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.form b/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.form index 75fb1d4c62..112b734f1e 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.form +++ b/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.form @@ -37,6 +37,10 @@ + + + + @@ -52,6 +56,10 @@ + + + + diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.java b/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.java index 8e465dd3a3..b91acf6fd4 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.java @@ -119,8 +119,8 @@ final class CheckBoxListPanel extends javax.swing.JPanel { java.awt.GridBagConstraints gridBagConstraints; titleLabel = new javax.swing.JLabel(); - uncheckButton = new javax.swing.JButton(); - checkButton = new javax.swing.JButton(); + 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()); @@ -179,10 +179,8 @@ final class CheckBoxListPanel extends javax.swing.JPanel { // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JButton checkButton; private javax.swing.JScrollPane scrollPane; private javax.swing.JLabel titleLabel; - private javax.swing.JButton uncheckButton; // End of variables declaration//GEN-END:variables /** diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.form b/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.form index 897341c482..79a852b4d4 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.form +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.form @@ -3,6 +3,10 @@
+ + + + @@ -30,6 +34,10 @@ + + + + @@ -120,6 +128,10 @@ + + + + @@ -131,7 +143,7 @@ - + @@ -148,7 +160,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.java b/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.java index bb3035b5d5..39109d2c83 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.java @@ -100,17 +100,15 @@ class GeoFilterPanel extends javax.swing.JPanel { "GeoFilterPanel_empty_dataSource=Data Source list is empty." }) GeoFilter getFilterState() throws GeoLocationUIException { - boolean showAll = allButton.isSelected(); - boolean withTimeStamp = showWaypointsWOTSCheckBox.isSelected(); - int dayCnt = numberModel.getNumber().intValue(); - List dataSources = checkboxPanel.getSelectedElements(); if (dataSources.isEmpty()) { throw new GeoLocationUIException(Bundle.GeoFilterPanel_empty_dataSource()); } - - return new GeoFilter(showAll, withTimeStamp, dayCnt, dataSources); + return new GeoFilter(allButton.isSelected(), + showWaypointsWOTSCheckBox.isSelected(), + numberModel.getNumber().intValue(), + dataSources); } /** @@ -147,14 +145,14 @@ class GeoFilterPanel extends javax.swing.JPanel { private void initComponents() { java.awt.GridBagConstraints gridBagConstraints; - buttonGroup = new javax.swing.ButtonGroup(); - waypointSettings = new javax.swing.JPanel(); + 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(); - buttonPanel = new javax.swing.JPanel(); + javax.swing.JPanel buttonPanel = new javax.swing.JPanel(); applyButton = new javax.swing.JButton(); javax.swing.JLabel optionsLabel = new javax.swing.JLabel(); @@ -232,7 +230,7 @@ class GeoFilterPanel extends javax.swing.JPanel { buttonPanel.setLayout(new java.awt.GridBagLayout()); - applyButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/geolocation/images/tick.png"))); // NOI18N + 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; @@ -248,7 +246,7 @@ class GeoFilterPanel extends javax.swing.JPanel { gridBagConstraints.insets = new java.awt.Insets(9, 15, 0, 15); add(buttonPanel, gridBagConstraints); - optionsLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/geolocation/images/blueGeo16.png"))); // NOI18N + 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; @@ -270,12 +268,9 @@ class GeoFilterPanel extends javax.swing.JPanel { // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JRadioButton allButton; private javax.swing.JButton applyButton; - private javax.swing.ButtonGroup buttonGroup; - private javax.swing.JPanel buttonPanel; private javax.swing.JSpinner daysSpinner; private javax.swing.JRadioButton mostRecentButton; private javax.swing.JCheckBox showWaypointsWOTSCheckBox; - private javax.swing.JPanel waypointSettings; // End of variables declaration//GEN-END:variables /** @@ -320,7 +315,7 @@ class GeoFilterPanel extends javax.swing.JPanel { * * @return True if all waypoints should be shown. */ - boolean showAll() { + boolean showAllWaypoints() { return showAll; } @@ -331,7 +326,7 @@ class GeoFilterPanel extends javax.swing.JPanel { * * @return True if waypoints with time stamps should be shown. */ - boolean showWithoutTimeStamp() { + boolean showWaypointsWithoutTimeStamp() { return showWithoutTimeStamp; } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java index f29bfca802..076fc787ff 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java @@ -27,6 +27,7 @@ import java.util.List; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.logging.Level; +import javax.swing.JOptionPane; import javax.swing.SwingUtilities; import javax.swing.SwingWorker; import org.openide.util.NbBundle.Messages; @@ -223,9 +224,10 @@ public final class GeolocationTopComponent extends TopComponent { try { filters = geoFilterPanel.getFilterState(); } catch (GeoLocationUIException ex) { - MessageNotifyUtil.Notify.info( - Bundle.GeoTopComponent_filer_data_invalid_Title(), - Bundle.GeoTopComponent_filer_data_invalid_msg()); + JOptionPane.showMessageDialog(GeolocationTopComponent.this, + Bundle.GeoTopComponent_filer_data_invalid_msg(), + Bundle.GeoTopComponent_filer_data_invalid_Title(), + JOptionPane.INFORMATION_MESSAGE); return; } @@ -233,15 +235,17 @@ public final class GeolocationTopComponent extends TopComponent { public void run() { Case currentCase = Case.getCurrentCase(); try { - WaypointBuilder.getAllWaypoints(currentCase.getSleuthkitCase(), filters.getDataSources(), filters.showAll(), filters.getMostRecentNumDays(), filters.showWithoutTimeStamp(), new WaypointFilterQueryCallBack() { + WaypointBuilder.getAllWaypoints(currentCase.getSleuthkitCase(), filters.getDataSources(), filters.showAllWaypoints(), filters.getMostRecentNumDays(), filters.showWaypointsWithoutTimeStamp(), new WaypointFilterQueryCallBack() { @Override public void process(List waypoints) { // If the list is empty, tell the user and do not change // the visible waypoints. if (waypoints == null || waypoints.isEmpty()) { - MessageNotifyUtil.Notify.info( + JOptionPane.showMessageDialog(GeolocationTopComponent.this, Bundle.GeoTopComponent_no_waypoints_returned_Title(), - Bundle.GeoTopComponent_no_waypoints_returned()); + Bundle.GeoTopComponent_no_waypoints_returned_mgs(), + JOptionPane.INFORMATION_MESSAGE); + return; } mapPanel.setWaypoints(MapWaypoint.getWaypoints(waypoints)); @@ -249,9 +253,10 @@ public final class GeolocationTopComponent extends TopComponent { }); } catch (GeoLocationDataException ex) { logger.log(Level.SEVERE, "Failed to filter waypoints.", ex); - MessageNotifyUtil.Notify.error( - Bundle.GeoTopComponent_filter_exception_Title(), - Bundle.GeoTopComponent_filter_exception_msg()); + JOptionPane.showMessageDialog(GeolocationTopComponent.this, + Bundle.GeoTopComponent_filter_exception_Title(), + Bundle.GeoTopComponent_filter_exception_msg(), + JOptionPane.ERROR_MESSAGE); } } }); diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/HidingPane.java b/Core/src/org/sleuthkit/autopsy/geolocation/HidingPane.java index 492888d15f..ca2afea1a4 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/HidingPane.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/HidingPane.java @@ -48,7 +48,7 @@ public final class HidingPane extends JTabbedPane { 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/geolocation/images/funnel.png"))); + 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); diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/RefreshPanel.form b/Core/src/org/sleuthkit/autopsy/geolocation/RefreshPanel.form index 400c788d42..145c22444a 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/RefreshPanel.form +++ b/Core/src/org/sleuthkit/autopsy/geolocation/RefreshPanel.form @@ -42,7 +42,7 @@ - + @@ -63,7 +63,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/RefreshPanel.java b/Core/src/org/sleuthkit/autopsy/geolocation/RefreshPanel.java index a4e30c9129..f1f93a620d 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/RefreshPanel.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/RefreshPanel.java @@ -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); diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java index 0776d029fd..0e29ca9dd2 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java @@ -40,21 +40,26 @@ public final class WaypointBuilder { private static final Logger logger = Logger.getLogger(WaypointBuilder.class.getName()); - // SELECT statement for getting a list of waypoints. Replace the %s after - // after the SELECT with the list of parameters to return + // 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 " - + "JOIN blackboard_artifacts ON blackboard_attributes.artifact_id = blackboard_artifacts.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 " @@ -64,6 +69,7 @@ public final class WaypointBuilder { + "%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 " @@ -350,7 +356,7 @@ public final class WaypointBuilder { String query = ""; query = String.format(SELECT_WO_TIMESTAMP, - String.format(GEO_ARTIFACT_QUERY, + String.format(GEO_ARTIFACT_QUERY_ID_ONLY, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID(), BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED.getTypeID()), getWaypointListQuery(dataSources)); diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/images/cross-script.png b/Core/src/org/sleuthkit/autopsy/geolocation/images/cross-script.png deleted file mode 100755 index f37cf4183046203c9d77c03ea46c672084ae3dbd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 623 zcmV-#0+9WQP)uz-;<4aiE#*F2T#| zt;WP8_2kp17jJ+5TnKdXT%g9FN8i3}lmYYqfHOcjj~~!Ph-`p^4LiH!rC-0^KL7i72TcG6TZ`7-bKTQXvCc;3)#dB$oaEf1Gi^b0a{20RY5Z;(@&ISGfQH002ov JPDHLkV1iqyA&vk5 diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/images/tick.png b/Core/src/org/sleuthkit/autopsy/geolocation/images/tick.png deleted file mode 100755 index a7d7a96be3f2282a62e3c0733bac89c7f6de7b4a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 582 zcmV-M0=fN(P)tYd4K$mX5uyr2F@fdffp{&DSHtl4|Bn9=ukpCx`#%PTUr-D(@b7;C zhJXJjrnw{;1KBM=6&|E`fsNrGL!Y6%zUh}QUl`(@V)PmQFtotEKmafTHP0uP}7&%m4pcKQ#X)BiAJ2y+PrtBI>9eEIt2-_c7)?*Lsf z5vX5*Af@m-wBJRV%#Fiz=BdPr0!2^az8K(fJ0K@xl?-_o)`1bna-SH57SeZUButL)d$cI_) zkpl5Q!w#U+YtHUCagF&eebP3jhEB diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/images/arrow-circle-double-135.png b/Core/src/org/sleuthkit/autopsy/images/arrow-circle-double-135.png similarity index 100% rename from Core/src/org/sleuthkit/autopsy/geolocation/images/arrow-circle-double-135.png rename to Core/src/org/sleuthkit/autopsy/images/arrow-circle-double-135.png diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/images/blueGeo16.png b/Core/src/org/sleuthkit/autopsy/images/blueGeo16.png similarity index 100% rename from Core/src/org/sleuthkit/autopsy/geolocation/images/blueGeo16.png rename to Core/src/org/sleuthkit/autopsy/images/blueGeo16.png diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/images/blueGeo64.png b/Core/src/org/sleuthkit/autopsy/images/blueGeo64.png similarity index 100% rename from Core/src/org/sleuthkit/autopsy/geolocation/images/blueGeo64.png rename to Core/src/org/sleuthkit/autopsy/images/blueGeo64.png diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/images/funnel.png b/Core/src/org/sleuthkit/autopsy/images/funnel.png similarity index 100% rename from Core/src/org/sleuthkit/autopsy/geolocation/images/funnel.png rename to Core/src/org/sleuthkit/autopsy/images/funnel.png From aa88bc69b5a6645e6a4c45c8be5bdf284418b0a8 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Thu, 14 Nov 2019 14:14:14 -0500 Subject: [PATCH 15/53] Implemented and tested the XRY DSP --- .../xry/Bundle.properties | 3 + .../xry/Bundle.properties-MERGED | 12 + .../xry/XRYDataSourceProcessor.java | 242 ++++++++++++++++++ .../XRYDataSourceProcessorConfigPanel.form | 75 ++++++ .../XRYDataSourceProcessorConfigPanel.java | 136 ++++++++++ .../datasourceprocessors/xry/XRYFolder.java | 60 ++++- .../xry/XRYReportProcessor.java | 61 +++++ 7 files changed, 588 insertions(+), 1 deletion(-) create mode 100755 Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/Bundle.properties create mode 100755 Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/Bundle.properties-MERGED create mode 100755 Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessor.java create mode 100755 Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.form create mode 100755 Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.java create mode 100755 Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYReportProcessor.java diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/Bundle.properties b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/Bundle.properties new file mode 100755 index 0000000000..998296e54f --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/Bundle.properties @@ -0,0 +1,3 @@ +XRYDataSourceProcessorConfigPanel.fileBrowserButton.text=Browse +XRYDataSourceProcessorConfigPanel.filePathTextField.text= +XRYDataSourceProcessorConfigPanel.xrySelectFolderLabel.text=Select an XRY Folder diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/Bundle.properties-MERGED new file mode 100755 index 0000000000..7591e1fa21 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/Bundle.properties-MERGED @@ -0,0 +1,12 @@ +XRYDataSourceProcessor.dataSourceType=Import Tool Report +XRYDataSourceProcessor.fileAdded=Added %s to the case database +XRYDataSourceProcessor.ioError=I/O error occured trying to test the XRY report folder +XRYDataSourceProcessor.notReadable=Could not read from the selected folder +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.testingFolder=Testing input folder... +XRYDataSourceProcessor.unexpectedError=Internal error occurred while processing XRY report +XRYDataSourceProcessorConfigPanel.fileBrowserButton.text=Browse +XRYDataSourceProcessorConfigPanel.filePathTextField.text= +XRYDataSourceProcessorConfigPanel.xrySelectFolderLabel.text=Select an XRY Folder diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessor.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessor.java new file mode 100755 index 0000000000..04a8d3a7a3 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessor.java @@ -0,0 +1,242 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit 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.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import java.util.stream.Collectors; +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.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)} +) +public class XRYDataSourceProcessor implements DataSourceProcessor { + + private final XRYDataSourceProcessorConfigPanel configPanel; + + //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=Import Tool Report" + }) + public String getDataSourceType() { + return Bundle.XRYDataSourceProcessor_dataSourceType(); + } + + @Override + public JPanel getPanel() { + return configPanel; + } + + @Override + public boolean isPanelValid() { + return true; + } + + /** + * Processes the XRY folder the examiner selected. The heavy lifting + * is handed off to a dedicated thread. This function will + * test the minimum requirements needed to successfully process the input. + */ + @Override + @NbBundle.Messages({ + "XRYDataSourceProcessor.testingFolder=Testing input folder...", + "XRYDataSourceProcessor.notReadable=Could not read from the selected folder", + "XRYDataSourceProcessor.notXRYFolder=Selected folder did not contain any XRY files", + "XRYDataSourceProcessor.ioError=I/O error occured trying to test the XRY report folder" + }) + public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { + progressMonitor.setProgressText(Bundle.XRYDataSourceProcessor_testingFolder()); + + String selectedFilePath = configPanel.getSelectedFilePath(); + File selectedFile = new File(selectedFilePath); + Path selectedPath = selectedFile.toPath(); + + //Test permissions + if (!Files.isReadable(selectedPath)) { + callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, + Lists.newArrayList(Bundle.XRYDataSourceProcessor_notReadable()), + Lists.newArrayList()); + return; + } + + try { + //Validate the folder. + if (!XRYFolder.isXRYFolder(selectedPath)) { + callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, + Lists.newArrayList(Bundle.XRYDataSourceProcessor_notXRYFolder()), + Lists.newArrayList()); + return; + } + } catch (IOException ex) { + logger.log(Level.WARNING, "[XRY DSP] I/O exception encountered trying to test the XRY folder.", ex); + callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, + Lists.newArrayList(Bundle.XRYDataSourceProcessor_ioError(), ex.toString()), Lists.newArrayList()); + return; + } + + try { + XRYFolder xryFolder = new XRYFolder(selectedPath); + FileManager fileManager = Case.getCurrentCaseThrows() + .getServices().getFileManager(); + + //Move heavy lifting to a dedicated thread. + swingWorker = new XRYReportProcessorSwingWorker(xryFolder, progressMonitor, + callback, fileManager); + swingWorker.execute(); + } catch (NoCurrentCaseException ex) { + logger.log(Level.WARNING, "[XRY DSP] No case is currently open.", ex); + } + } + + @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 add images to the case database and processing the + * XRY report files. + */ + private class XRYReportProcessorSwingWorker extends SwingWorker { + + private final DataSourceProcessorProgressMonitor progressMonitor; + private final DataSourceProcessorCallback callback; + private final FileManager fileManager; + private final XRYFolder xryFolder; + + public XRYReportProcessorSwingWorker(XRYFolder folder, DataSourceProcessorProgressMonitor progressMonitor, + DataSourceProcessorCallback callback, FileManager fileManager) { + this.xryFolder = folder; + this.progressMonitor = progressMonitor; + this.callback = callback; + this.fileManager = fileManager; + } + + @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 nonXRYFiles = xryFolder.getNonXRYFiles(); + List filePaths = nonXRYFiles.stream() + //Map paths to string representations. + .map(Path::toString) + .collect(Collectors.toList()); + String uniqueUUID = UUID.randomUUID().toString(); + 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) { + //DSP was cancelled. Not an error. + } 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())); + } + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.form b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.form new file mode 100755 index 0000000000..b78c62357e --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.form @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.java new file mode 100755 index 0000000000..fd03d47787 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.java @@ -0,0 +1,136 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit 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.File; +import javax.swing.JFileChooser; +import javax.swing.JPanel; + +/** + * Allows an examiner to configure the XRY Data source processor. + */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives +class XRYDataSourceProcessorConfigPanel extends JPanel { + + private static final XRYDataSourceProcessorConfigPanel INSTANCE = + new XRYDataSourceProcessorConfigPanel(); + + /** + * Creates new form XRYDataSourceConfigPanel. + * Prevent direct instantiation. + */ + private XRYDataSourceProcessorConfigPanel() { + initComponents(); + } + + /** + * Gets the singleton XRYDataSourceProcessorConfigPanel. + */ + static XRYDataSourceProcessorConfigPanel getInstance() { + return INSTANCE; + } + + /** + * Clears the selected file path. + */ + void clearSelectedFilePath() { + filePathTextField.setText(null); + } + + /** + * Gets the file path selected by the examiner. + */ + String getSelectedFilePath() { + return filePathTextField.getText(); + } + + /** + * 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") + // //GEN-BEGIN:initComponents + private void initComponents() { + + filePathTextField = new javax.swing.JTextField(); + fileBrowserButton = new javax.swing.JButton(); + xrySelectFolderLabel = 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 + + 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(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)) + .addContainerGap(246, Short.MAX_VALUE)) + ); + }// //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()); + } + }//GEN-LAST:event_fileBrowserButtonActionPerformed + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton fileBrowserButton; + private javax.swing.JTextField filePathTextField; + private javax.swing.JLabel xrySelectFolderLabel; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFolder.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFolder.java index bac2bc2364..f0d40a8ca8 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFolder.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFolder.java @@ -24,7 +24,9 @@ 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; /** @@ -36,6 +38,62 @@ final class XRYFolder { //children of their parent folder. private static final int XRY_FILES_DEPTH = 1; + //Raw path to the XRY folder. + private final Path xryFolder; + + public XRYFolder(Path folder) { + xryFolder = 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 getNonXRYFiles() throws IOException { + try (Stream allFiles = Files.walk(xryFolder, XRY_FILES_DEPTH)) { + List otherFiles = new ArrayList<>(); + Iterator allFilesIterator = allFiles.iterator(); + while (allFilesIterator.hasNext()) { + Path currentPath = allFilesIterator.next(); + if (!currentPath.equals(xryFolder) + && !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 getXRYFileReaders() throws IOException { + try (Stream allFiles = Files.walk(xryFolder, XRY_FILES_DEPTH)) { + List fileReaders = new ArrayList<>(); + + Iterator 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 @@ 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 { diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYReportProcessor.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYReportProcessor.java new file mode 100755 index 0000000000..2d3bfc110a --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYReportProcessor.java @@ -0,0 +1,61 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit 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. + */ +class XRYReportProcessor { + + private static final Logger logger = Logger.getLogger(XRYReportProcessor.class.getName()); + + /** + * Processes all XRY Files and creates artifacts on the given Content + * instance. + * + * @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 xryFileReaders = folder.getXRYFileReaders(); + + 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 (in brackets) [ %s ]", xryFileReader.getReportPath().toString(), reportType)); + } + xryFileReader.close(); + } + } +} From 9037b1e03f2f4e1869a7fbc7bad187e973d97b0f Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Thu, 14 Nov 2019 14:17:55 -0500 Subject: [PATCH 16/53] Fixed codacy issue --- .../sleuthkit/autopsy/geolocation/GeolocationTopComponent.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java index 076fc787ff..05cd3afbbd 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java @@ -224,7 +224,7 @@ public final class GeolocationTopComponent extends TopComponent { try { filters = geoFilterPanel.getFilterState(); } catch (GeoLocationUIException ex) { - JOptionPane.showMessageDialog(GeolocationTopComponent.this, + JOptionPane.showMessageDialog(this, Bundle.GeoTopComponent_filer_data_invalid_msg(), Bundle.GeoTopComponent_filer_data_invalid_Title(), JOptionPane.INFORMATION_MESSAGE); From 53b134daedb6e90d660ec409fb9a4943061bfeda Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Thu, 14 Nov 2019 14:23:59 -0500 Subject: [PATCH 17/53] Changed the log messages to be easier to read and fixed the date time bug --- .../xry/AbstractSingleKeyValueParser.java | 10 +++--- .../xry/XRYCallsFileParser.java | 8 ++--- .../xry/XRYDeviceGenInfoFileParser.java | 12 +++---- .../xry/XRYFileParserFactory.java | 2 +- .../xry/XRYMessagesFileParser.java | 36 +++++++++---------- 5 files changed, 34 insertions(+), 34 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java index 20b6b7c1bf..edd218adbb 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java @@ -45,7 +45,7 @@ abstract class AbstractSingleKeyValueParser implements XRYFileParser { @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())); + logger.log(Level.INFO, String.format("[XRY DSP] Processing report at [ %s ]", reportPath.toString())); while (reader.hasNextEntity()) { String xryEntity = reader.nextEntity(); @@ -55,7 +55,7 @@ abstract class AbstractSingleKeyValueParser implements XRYFileParser { //First line of the entity is the title. if (xryLines.length > 0) { - logger.log(Level.INFO, String.format("XRY DSP: Processing [ %s ]", xryLines[0])); + logger.log(Level.INFO, String.format("[XRY DSP] Processing [ %s ]", xryLines[0])); } String namespace = ""; @@ -75,7 +75,7 @@ abstract class AbstractSingleKeyValueParser implements XRYFileParser { //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 " + 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; @@ -84,14 +84,14 @@ abstract class AbstractSingleKeyValueParser implements XRYFileParser { String value = xryLine.substring(keyDelimiter + 1).trim(); if (!isKey(key)) { - logger.log(Level.SEVERE, String.format("XRY DSP: The following 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 " + 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; diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java index 260fccd40f..596f25e7b1 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java @@ -89,10 +89,10 @@ final class XRYCallsFileParser extends AbstractSingleKeyValueParser { try { String dateTime = removeDateTimeLocale(value); String normalizedDateTime = dateTime.trim(); - long dateTimeInEpoch = calculateMsSinceEpoch(normalizedDateTime); + 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 " + 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; } @@ -167,9 +167,9 @@ final class XRYCallsFileParser extends AbstractSingleKeyValueParser { * @param dateTime * @return */ - private long calculateMsSinceEpoch(String dateTime) { + private long calculateSecondsSinceEpoch(String dateTime) { LocalDateTime localDateTime = LocalDateTime.parse(dateTime, DATE_TIME_PARSER); //Assume dates have no offset. - return localDateTime.toInstant(ZoneOffset.UTC).toEpochMilli(); + return localDateTime.toInstant(ZoneOffset.UTC).getEpochSecond(); } } diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDeviceGenInfoFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDeviceGenInfoFileParser.java index d610ffb89d..d3bba45bfc 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDeviceGenInfoFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDeviceGenInfoFileParser.java @@ -83,7 +83,7 @@ final class XRYDeviceGenInfoFileParser implements XRYFileParser { @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())); + logger.log(Level.INFO, String.format("[XRY DSP] Processing report at [ %s ]", reportPath.toString())); while (reader.hasNextEntity()) { String xryEntity = reader.nextEntity(); @@ -93,7 +93,7 @@ final class XRYDeviceGenInfoFileParser implements XRYFileParser { //First line of the entity is the title. if (xryLines.length > 0) { - logger.log(Level.INFO, String.format("XRY DSP: Processing [ %s ]", xryLines[0])); + logger.log(Level.INFO, String.format("[XRY DSP] Processing [ %s ]", xryLines[0])); } for (int i = 1; i < xryLines.length; i++) { @@ -101,7 +101,7 @@ final class XRYDeviceGenInfoFileParser implements XRYFileParser { //Expecting to see a "Data" key. if (!hasDataKey(xryLine)) { - logger.log(Level.SEVERE, String.format("XRY DSP: Expected a 'Data' key " + 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])); @@ -109,7 +109,7 @@ final class XRYDeviceGenInfoFileParser implements XRYFileParser { } if (i + 1 == xryLines.length) { - logger.log(Level.SEVERE, String.format("XRY DSP: Found a 'Data' key " + 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])); @@ -123,7 +123,7 @@ final class XRYDeviceGenInfoFileParser implements XRYFileParser { //Expecting to see an "Attribute" key if (!hasAttributeKey(nextXryLine)) { - logger.log(Level.SEVERE, String.format("XRY DSP: Expected an 'Attribute' " + 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)); @@ -139,7 +139,7 @@ final class XRYDeviceGenInfoFileParser implements XRYFileParser { //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) " + 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)); } diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileParserFactory.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileParserFactory.java index d650510827..06492de07b 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileParserFactory.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileParserFactory.java @@ -42,7 +42,7 @@ final class XRYFileParserFactory { throw new IllegalArgumentException("Report type cannot be null"); } - switch (reportType.toLowerCase()) { + switch (reportType.trim().toLowerCase()) { case "calls": return new XRYCallsFileParser(); case "contacts/contacts": diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYMessagesFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYMessagesFileParser.java index 253676f0e9..ac78e62509 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYMessagesFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYMessagesFileParser.java @@ -118,7 +118,7 @@ final class XRYMessagesFileParser implements XRYFileParser { @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())); + 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 referenceNumbersSeen = new HashSet<>(); @@ -129,7 +129,7 @@ final class XRYMessagesFileParser implements XRYFileParser { //First line of the entity is the title. if (xryLines.length > 0) { - logger.log(Level.INFO, String.format("XRY DSP: Processing [ %s ]", xryLines[0])); + logger.log(Level.INFO, String.format("[XRY DSP] Processing [ %s ]", xryLines[0])); } List attributes = new ArrayList<>(); @@ -147,7 +147,7 @@ final class XRYMessagesFileParser implements XRYFileParser { //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 " + 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 ]. " @@ -167,7 +167,7 @@ final class XRYMessagesFileParser implements XRYFileParser { } if (!XRY_KEYS.contains(normalizedKey)) { - logger.log(Level.SEVERE, String.format("XRY DSP: The following 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])); @@ -175,7 +175,7 @@ final class XRYMessagesFileParser implements XRYFileParser { } if (value.isEmpty()) { - logger.log(Level.SEVERE, String.format("XRY DSP: The following key " + 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? " @@ -186,7 +186,7 @@ final class XRYMessagesFileParser implements XRYFileParser { //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 + for (; (i + 1) < xryLines.length && !hasKey(xryLines[i + 1]) && !hasNamespace(xryLines[i + 1]); i++) { String continuedValue = xryLines[i + 1].trim(); @@ -198,15 +198,15 @@ final class XRYMessagesFileParser implements XRYFileParser { //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 " + 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, "XRY DSP: This reference has already " + 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."); + + "(otherwise duplicate) artifact will be created.", referenceNumber)); } referenceNumbersSeen.add(referenceNumber); @@ -269,12 +269,12 @@ final class XRYMessagesFileParser implements XRYFileParser { //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 ] " + logger.log(Level.INFO, String.format("[XRY DSP] Processing [ %s ] " + "segment with reference number [ %d ]", nextEntityLines[0], referenceNumber)); } if (nextSegmentNumber != currentSegmentNumber + 1) { - logger.log(Level.SEVERE, String.format("XRY DSP: Contiguous " + 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)); @@ -365,7 +365,7 @@ final class XRYMessagesFileParser implements XRYFileParser { try { return Integer.parseInt(value); } catch (NumberFormatException ex) { - logger.log(Level.SEVERE, String.format("XRY DSP: Value [ %s ] for " + logger.log(Level.SEVERE, String.format("[XRY DSP] Value [ %s ] for " + "meta key [ %s ] was not an integer.", value, metaKey), ex); } } @@ -395,10 +395,10 @@ final class XRYMessagesFileParser implements XRYFileParser { try { String dateTime = removeDateTimeLocale(value); String normalizedDateTime = dateTime.trim(); - long dateTimeInEpoch = calculateMsSinceEpoch(normalizedDateTime); + 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 " + 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; @@ -423,7 +423,7 @@ final class XRYMessagesFileParser implements XRYFileParser { //Ignore for now. return null; default: - logger.log(Level.SEVERE, String.format("XRY DSP: Unrecognized " + logger.log(Level.SEVERE, String.format("[XRY DSP] Unrecognized " + "status value [ %s ].", value)); return null; } @@ -439,7 +439,7 @@ final class XRYMessagesFileParser implements XRYFileParser { //Ignore for now. return null; default: - logger.log(Level.SEVERE, String.format("XRY DSP: Unrecognized " + logger.log(Level.SEVERE, String.format("[XRY DSP] Unrecognized " + "type value [ %s ]", value)); return null; } @@ -495,9 +495,9 @@ final class XRYMessagesFileParser implements XRYFileParser { * @param dateTime * @return */ - private long calculateMsSinceEpoch(String dateTime) { + private long calculateSecondsSinceEpoch(String dateTime) { LocalDateTime localDateTime = LocalDateTime.parse(dateTime, DATE_TIME_PARSER); //Assume dates have no offset. - return localDateTime.toInstant(ZoneOffset.UTC).toEpochMilli(); + return localDateTime.toInstant(ZoneOffset.UTC).getEpochSecond(); } } From 85d6da4c8f70fc752155dc91b00e74cba4f53b25 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Thu, 14 Nov 2019 14:50:53 -0500 Subject: [PATCH 18/53] Removed a redundant 'in brackets' message --- .../autopsy/datasourceprocessors/xry/XRYReportProcessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYReportProcessor.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYReportProcessor.java index 2d3bfc110a..f89af3c021 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYReportProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYReportProcessor.java @@ -53,7 +53,7 @@ class XRYReportProcessor { } 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 (in brackets) [ %s ]", xryFileReader.getReportPath().toString(), reportType)); + + "Report type is [ %s ]", xryFileReader.getReportPath().toString(), reportType)); } xryFileReader.close(); } From b9044d641686e6dd40c5e66887a155427599d16c Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Thu, 14 Nov 2019 15:16:10 -0500 Subject: [PATCH 19/53] Fixed file leak in the event of an exception --- .../xry/XRYReportProcessor.java | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYReportProcessor.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYReportProcessor.java index f89af3c021..a256309187 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYReportProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYReportProcessor.java @@ -35,6 +35,8 @@ class XRYReportProcessor { /** * 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. @@ -45,17 +47,27 @@ class XRYReportProcessor { //Get all XRY file readers from this folder. List xryFileReaders = folder.getXRYFileReaders(); - 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)); + 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) { + //Best effort closing all resources. } - xryFileReader.close(); } } } From f4b5ffcaf991b85dc192f91e16b5705ffb00be92 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Thu, 14 Nov 2019 15:42:06 -0500 Subject: [PATCH 20/53] Addressed codacy comments --- .../xry/XRYDataSourceProcessorConfigPanel.java | 2 +- .../autopsy/datasourceprocessors/xry/XRYFolder.java | 10 +++++----- .../datasourceprocessors/xry/XRYReportProcessor.java | 12 +++++++++--- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.java index fd03d47787..04ccf31b35 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.java @@ -26,7 +26,7 @@ import javax.swing.JPanel; * Allows an examiner to configure the XRY Data source processor. */ @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives -class XRYDataSourceProcessorConfigPanel extends JPanel { +final class XRYDataSourceProcessorConfigPanel extends JPanel { private static final XRYDataSourceProcessorConfigPanel INSTANCE = new XRYDataSourceProcessorConfigPanel(); diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFolder.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFolder.java index f0d40a8ca8..f78154d796 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFolder.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFolder.java @@ -39,10 +39,10 @@ final class XRYFolder { private static final int XRY_FILES_DEPTH = 1; //Raw path to the XRY folder. - private final Path xryFolder; + private final Path xryFolderPath; public XRYFolder(Path folder) { - xryFolder = folder; + xryFolderPath = folder; } /** @@ -54,12 +54,12 @@ final class XRYFolder { * @throws IOException If an I/O error occurs. */ public List getNonXRYFiles() throws IOException { - try (Stream allFiles = Files.walk(xryFolder, XRY_FILES_DEPTH)) { + try (Stream allFiles = Files.walk(xryFolderPath, XRY_FILES_DEPTH)) { List otherFiles = new ArrayList<>(); Iterator allFilesIterator = allFiles.iterator(); while (allFilesIterator.hasNext()) { Path currentPath = allFilesIterator.next(); - if (!currentPath.equals(xryFolder) + if (!currentPath.equals(xryFolderPath) && !XRYFileReader.isXRYFile(currentPath)) { otherFiles.add(currentPath); } @@ -78,7 +78,7 @@ final class XRYFolder { * @throws IOException If an I/O error occurs. */ public List getXRYFileReaders() throws IOException { - try (Stream allFiles = Files.walk(xryFolder, XRY_FILES_DEPTH)) { + try (Stream allFiles = Files.walk(xryFolderPath, XRY_FILES_DEPTH)) { List fileReaders = new ArrayList<>(); Iterator allFilesIterator = allFiles.iterator(); diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYReportProcessor.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYReportProcessor.java index a256309187..98ee693101 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYReportProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYReportProcessor.java @@ -28,14 +28,14 @@ import org.sleuthkit.datamodel.TskCoreException; /** * Processes all XRY files in an XRY folder. */ -class XRYReportProcessor { +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 @@ -66,8 +66,14 @@ class XRYReportProcessor { xryFileReader.close(); } } catch (IOException ex) { - //Best effort closing all resources. + logger.log(Level.WARNING, "[XRY DSP] Encountered I/O exception trying " + + "to close all xry file readers.", ex); } } } + + //Prevent direct instantiation. + private XRYReportProcessor() { + + } } From 88551a7b2a2d5ad5ddf17f5ac07b5dc8b7c3d2b6 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Thu, 14 Nov 2019 15:42:36 -0500 Subject: [PATCH 21/53] 5515 Image gallery service needs to check for deleted data sources --- .../imagegallery/datamodel/DrawableDB.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index 2097fea5f3..1a259c0adf 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -386,14 +386,14 @@ public final class DrawableDB { private boolean removeDeletedDataSources() { dbWriteLock(); try (SleuthkitCase.CaseDbQuery caseDbQuery = tskCase.executeQuery("SELECT obj_id FROM data_source_info"); //NON-NLS - Statement statement = con.createStatement()) { + Statement drawablesDbStmt = con.createStatement()) { /* * Get the data source object IDs from the case database. */ ResultSet caseDbResults = caseDbQuery.getResultSet(); - Set validDataSourceObjIDs = new HashSet<>(); + Set currentDataSourceObjIDs = new HashSet<>(); while (caseDbResults.next()) { - validDataSourceObjIDs.add(caseDbResults.getLong(1)); + currentDataSourceObjIDs.add(caseDbResults.getLong(1)); } /* @@ -402,10 +402,10 @@ public final class DrawableDB { * database. */ List staleDataSourceObjIDs = new ArrayList<>(); - try (ResultSet drawablesDbResults = statement.executeQuery("SELECT ds_obj_id FROM datasources")) { //NON-NLS + try (ResultSet drawablesDbResults = drawablesDbStmt.executeQuery("SELECT ds_obj_id FROM datasources")) { //NON-NLS while (drawablesDbResults.next()) { long dataSourceObjID = drawablesDbResults.getLong(1); - if (!validDataSourceObjIDs.contains(dataSourceObjID)) { + if (!currentDataSourceObjIDs.contains(dataSourceObjID)) { staleDataSourceObjIDs.add(dataSourceObjID); } } @@ -416,11 +416,11 @@ public final class DrawableDB { * database. The delete cascades. */ if (!staleDataSourceObjIDs.isEmpty()) { - String inClause = StringUtils.join(staleDataSourceObjIDs, ','); - String delete = "DELETE FROM datasources where ds_obj_id IN (" + inClause + ")"; //NON-NLS - statement.execute(delete); + 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; From 34edd7ec2cc8b0e55ebade83b542305bff9fd5ce Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Thu, 14 Nov 2019 15:45:27 -0500 Subject: [PATCH 22/53] 5515 Image gallery service needs to check for deleted data sources --- .../sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index 1a259c0adf..7bc846fdc0 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -420,7 +420,7 @@ public final class DrawableDB { 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; From bc66d0bd1139bb794961b583af7d8a740cf78e38 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Thu, 14 Nov 2019 16:01:47 -0500 Subject: [PATCH 23/53] Added a serial version UID --- .../xry/XRYDataSourceProcessorConfigPanel.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.java index 04ccf31b35..ac32eeb7ca 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.java @@ -28,6 +28,7 @@ import javax.swing.JPanel; @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(); From 09961c71badd439e7144b9099a1d0d1e8d990c0b Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Thu, 14 Nov 2019 16:50:20 -0500 Subject: [PATCH 24/53] Minor fixes that fell out of testing --- .../xry/AbstractSingleKeyValueParser.java | 7 +++---- .../xry/XRYMessagesFileParser.java | 10 +++++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java index edd218adbb..08dad04991 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/AbstractSingleKeyValueParser.java @@ -64,10 +64,11 @@ abstract class AbstractSingleKeyValueParser implements XRYFileParser { 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(xryLine)) { - namespace = xryLine.trim(); + if (isNamespace(candidateNamespace)) { + namespace = candidateNamespace; continue; } @@ -157,8 +158,6 @@ abstract class AbstractSingleKeyValueParser implements XRYFileParser { /** * Makes an artifact from the parsed attributes. - * - * @return */ abstract void makeArtifact(List attributes, Content parent) throws TskCoreException; diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYMessagesFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYMessagesFileParser.java index ac78e62509..820a6ebaad 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYMessagesFileParser.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYMessagesFileParser.java @@ -137,9 +137,9 @@ final class XRYMessagesFileParser implements XRYFileParser { String namespace = ""; for (int i = 1; i < xryLines.length; i++) { String xryLine = xryLines[i]; - String normalizedXryLine = xryLine.trim().toLowerCase(); + String candidateNamespace = xryLine.trim().toLowerCase(); - if (XRY_NAMESPACES.contains(normalizedXryLine)) { + if (XRY_NAMESPACES.contains(candidateNamespace)) { namespace = xryLine.trim(); continue; } @@ -273,7 +273,11 @@ final class XRYMessagesFileParser implements XRYFileParser { + "segment with reference number [ %d ]", nextEntityLines[0], referenceNumber)); } - if (nextSegmentNumber != currentSegmentNumber + 1) { + 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 " From 7e1755102b0dd782c682246c05d1c1e1a02a3572 Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Thu, 14 Nov 2019 18:18:01 -0500 Subject: [PATCH 25/53] addressed review comments and fixed bug from jira 5778 --- .../geolocation/CheckBoxListPanel.java | 7 +++ .../autopsy/geolocation/GeoFilterPanel.java | 8 ++- .../geolocation/GeolocationTopComponent.java | 59 ++++--------------- .../autopsy/geolocation/HidingPane.java | 19 +++++- .../autopsy/geolocation/VerticalLabelUI.java | 20 ++++++- .../geolocation/datamodel/Waypoint.java | 5 -- .../datamodel/WaypointBuilder.java | 8 +-- 7 files changed, 60 insertions(+), 66 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.java b/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.java index b91acf6fd4..37dea163f5 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/CheckBoxListPanel.java @@ -54,6 +54,13 @@ final class CheckBoxListPanel extends javax.swing.JPanel { 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. diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.java b/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.java index 39109d2c83..9e009d1245 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeoFilterPanel.java @@ -72,8 +72,10 @@ class GeoFilterPanel extends javax.swing.JPanel { gridBagConstraints.weighty = 1.0; gridBagConstraints.insets = new java.awt.Insets(0, 15, 0, 15); add(checkboxPanel, gridBagConstraints); - - try { + } + + void updateDataSourceList() { + try { initCheckboxList(); } catch (TskCoreException ex) { logger.log(Level.WARNING, "Failed to initialize the CheckboxListPane", ex); //NON-NLS @@ -119,6 +121,8 @@ class GeoFilterPanel extends javax.swing.JPanel { 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); diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java index 05cd3afbbd..e1556dc27a 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java @@ -25,11 +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.JOptionPane; import javax.swing.SwingUtilities; -import javax.swing.SwingWorker; import org.openide.util.NbBundle.Messages; import org.openide.windows.RetainLocation; import org.openide.windows.TopComponent; @@ -37,7 +35,6 @@ import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.casemodule.Case; 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; @@ -81,7 +78,7 @@ public final class GeolocationTopComponent extends TopComponent { @ThreadConfined(type = ThreadConfined.ThreadType.AWT) public GeolocationTopComponent() { initComponents(); - initWaypoints(); + setName(Bundle.GLTopComponent_name()); this.ingestListener = pce -> { @@ -113,7 +110,7 @@ public final class GeolocationTopComponent extends TopComponent { @Override public void actionPerformed(ActionEvent e) { mapPanel.clearWaypoints(); - initWaypoints(); + updateWaypoints(); showRefreshPanel(false); } }); @@ -123,10 +120,9 @@ public final class GeolocationTopComponent extends TopComponent { geoFilterPanel.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - filterWaypoints(); + updateWaypoints(); } }); - } @Override @@ -136,7 +132,7 @@ public final class GeolocationTopComponent extends TopComponent { Case.addEventTypeSubscriber(EnumSet.of(CURRENT_CASE), evt -> { mapPanel.clearWaypoints(); if (evt.getNewValue() != null) { - initWaypoints(); + updateWaypoints(); } }); } @@ -152,6 +148,13 @@ public final class GeolocationTopComponent extends TopComponent { super.componentOpened(); WindowManager.getDefault().setTopComponentFloating(this, true); } + + @Override + public void open() { + super.open(); + geoFilterPanel.updateDataSourceList(); + updateWaypoints(); + } /** * Set the state of the refresh panel at the top of the mapPanel. @@ -167,44 +170,6 @@ public final class GeolocationTopComponent extends TopComponent { mapPanel.revalidate(); } - /** - * Use a SwingWorker thread to get a list of waypoints. - */ - private void initWaypoints() { - SwingWorker, MapWaypoint> worker = new SwingWorker, MapWaypoint>() { - @Override - protected List doInBackground() throws Exception { - Case currentCase = Case.getCurrentCaseThrows(); - return MapWaypoint.getWaypoints(currentCase.getSleuthkitCase()); - } - - @Override - protected void done() { - if (isDone() && !isCancelled()) { - try { - List waypoints = get(); - if (waypoints == null || waypoints.isEmpty()) { - return; - } - 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 - } - } - } - }; - - worker.execute(); - } - /** * Filters the list of waypoints based on the user selections in the filter * pane. @@ -217,7 +182,7 @@ public final class GeolocationTopComponent extends TopComponent { "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 filterWaypoints() { + private void updateWaypoints() { GeoFilter filters; // Show a warning message if the user has not selected a data source diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/HidingPane.java b/Core/src/org/sleuthkit/autopsy/geolocation/HidingPane.java index ca2afea1a4..e842e5df86 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/HidingPane.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/HidingPane.java @@ -1,7 +1,20 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * contact: carrier sleuthkit 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; diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/VerticalLabelUI.java b/Core/src/org/sleuthkit/autopsy/geolocation/VerticalLabelUI.java index 2580031dfb..1a76953267 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/VerticalLabelUI.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/VerticalLabelUI.java @@ -1,7 +1,21 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * contact: carrier sleuthkit 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; diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java index 428c659eca..27e5da0dbd 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java @@ -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. * diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java index 0e29ca9dd2..7189302809 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java @@ -353,15 +353,11 @@ public final class WaypointBuilder { * @return SQL SELECT statement */ static private String buildQueryForWaypointsWOTimeStamps(List dataSources) { - String query = ""; - - query = String.format(SELECT_WO_TIMESTAMP, + 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)); - - return query; } /** @@ -408,7 +404,7 @@ public final class WaypointBuilder { query += String.format("AND artifact_id IN(%s)", getWaypointListQuery(dataSources)); //NON-NLS query += mostRecentQuery; - if (noTimeStamp) { + if (showAll || noTimeStamp) { query = String.format("%s UNION %s", buildQueryForWaypointsWOTimeStamps(dataSources), query); //NON-NLS } From 5085d3de5a1776dec1830fb23946a3412ea6e91d Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Fri, 15 Nov 2019 11:36:50 -0500 Subject: [PATCH 26/53] Make progress bar indeterminate --- .../autopsy/datasourceprocessors/xry/XRYDataSourceProcessor.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessor.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessor.java index 04a8d3a7a3..acb12a6f46 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessor.java @@ -96,6 +96,7 @@ public class XRYDataSourceProcessor implements DataSourceProcessor { "XRYDataSourceProcessor.ioError=I/O error occured trying to test the XRY report folder" }) public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { + progressMonitor.setIndeterminate(true); progressMonitor.setProgressText(Bundle.XRYDataSourceProcessor_testingFolder()); String selectedFilePath = configPanel.getSelectedFilePath(); From 975c32e8abbd07c8b80d8d9b2a8c4f9a6d5eb29c Mon Sep 17 00:00:00 2001 From: Raman Arora Date: Fri, 15 Nov 2019 13:06:10 -0500 Subject: [PATCH 27/53] 5698: associate attached files with the message. --- .../autopsy/datamodel/ExtractedContent.java | 2 ++ .../ThunderbirdMboxFileIngestModule.java | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java index cefd59a041..e52e50c31c 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java @@ -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) -> { diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java index 5c42269a8a..f1260f269f 100644 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java @@ -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 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 * From 858f9fab9d6da058b14f67322850e81024cab718 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Fri, 15 Nov 2019 13:06:45 -0500 Subject: [PATCH 28/53] Addressed review comments --- .../xry/Bundle.properties | 1 + .../xry/Bundle.properties-MERGED | 5 +- .../xry/XRYDataSourceProcessor.java | 86 ++++++++++--------- .../XRYDataSourceProcessorConfigPanel.form | 15 +++- .../XRYDataSourceProcessorConfigPanel.java | 43 +++++++++- 5 files changed, 105 insertions(+), 45 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/Bundle.properties b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/Bundle.properties index 998296e54f..05c5199962 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/Bundle.properties @@ -1,3 +1,4 @@ XRYDataSourceProcessorConfigPanel.fileBrowserButton.text=Browse XRYDataSourceProcessorConfigPanel.filePathTextField.text= XRYDataSourceProcessorConfigPanel.xrySelectFolderLabel.text=Select an XRY Folder +XRYDataSourceProcessorConfigPanel.errorLabel.text= diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/Bundle.properties-MERGED index 7591e1fa21..f8d924a1a2 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/Bundle.properties-MERGED @@ -1,12 +1,13 @@ -XRYDataSourceProcessor.dataSourceType=Import Tool Report +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.noPathSelected=Please select a XRY folder XRYDataSourceProcessor.notReadable=Could not read from the selected folder 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.testingFolder=Testing input folder... 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= diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessor.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessor.java index acb12a6f46..082f098a61 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessor.java @@ -54,7 +54,7 @@ import org.sleuthkit.datamodel.TskDataException; public class XRYDataSourceProcessor implements DataSourceProcessor { private final XRYDataSourceProcessorConfigPanel configPanel; - + //Background processor to relieve the EDT from adding files to the case //database and parsing the report files. private XRYReportProcessorSwingWorker swingWorker; @@ -67,7 +67,7 @@ public class XRYDataSourceProcessor implements DataSourceProcessor { @Override @NbBundle.Messages({ - "XRYDataSourceProcessor.dataSourceType=Import Tool Report" + "XRYDataSourceProcessor.dataSourceType=XRY Logical Report" }) public String getDataSourceType() { return Bundle.XRYDataSourceProcessor_dataSourceType(); @@ -78,60 +78,63 @@ public class XRYDataSourceProcessor implements DataSourceProcessor { return configPanel; } - @Override - public boolean isPanelValid() { - return true; - } - - /** - * Processes the XRY folder the examiner selected. The heavy lifting - * is handed off to a dedicated thread. This function will - * test the minimum requirements needed to successfully process the input. - */ @Override @NbBundle.Messages({ - "XRYDataSourceProcessor.testingFolder=Testing input folder...", + "XRYDataSourceProcessor.noPathSelected=Please select a XRY folder", "XRYDataSourceProcessor.notReadable=Could not read from the selected folder", "XRYDataSourceProcessor.notXRYFolder=Selected folder did not contain any XRY files", "XRYDataSourceProcessor.ioError=I/O error occured trying to test the XRY report folder" }) - public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { - progressMonitor.setIndeterminate(true); - progressMonitor.setProgressText(Bundle.XRYDataSourceProcessor_testingFolder()); - + 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)) { - callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, - Lists.newArrayList(Bundle.XRYDataSourceProcessor_notReadable()), - Lists.newArrayList()); - return; + configPanel.setErrorText(Bundle.XRYDataSourceProcessor_notReadable()); + return false; } try { //Validate the folder. if (!XRYFolder.isXRYFolder(selectedPath)) { - callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, - Lists.newArrayList(Bundle.XRYDataSourceProcessor_notXRYFolder()), - Lists.newArrayList()); - return; + configPanel.setErrorText(Bundle.XRYDataSourceProcessor_notXRYFolder()); + return false; } } catch (IOException ex) { + configPanel.setErrorText(Bundle.XRYDataSourceProcessor_ioError()); logger.log(Level.WARNING, "[XRY DSP] I/O exception encountered trying to test the XRY folder.", ex); - callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, - Lists.newArrayList(Bundle.XRYDataSourceProcessor_ioError(), ex.toString()), Lists.newArrayList()); - return; + return false; } + return true; + } + + /** + * Processes the XRY folder that the examiner selected. The heavy lifting is + * done off of the EDT. + */ + @Override + 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(); - - //Move heavy lifting to a dedicated thread. + + //Move heavy lifting to a backround task. swingWorker = new XRYReportProcessorSwingWorker(xryFolder, progressMonitor, callback, fileManager); swingWorker.execute(); @@ -154,8 +157,8 @@ public class XRYDataSourceProcessor implements DataSourceProcessor { } /** - * Relieves the EDT from add images to the case database and processing the - * XRY report files. + * Relieves the EDT from having to process the XRY report and write to the + * case database. */ private class XRYReportProcessorSwingWorker extends SwingWorker { @@ -177,10 +180,10 @@ public class XRYDataSourceProcessor implements DataSourceProcessor { "XRYDataSourceProcessor.preppingFiles=Preparing to add files to the case database", "XRYDataSourceProcessor.processingFiles=Processing all XRY files..." }) - protected LocalFilesDataSource doInBackground() throws TskCoreException, + protected LocalFilesDataSource doInBackground() throws TskCoreException, TskDataException, IOException { progressMonitor.setProgressText(Bundle.XRYDataSourceProcessor_preppingFiles()); - + List nonXRYFiles = xryFolder.getNonXRYFiles(); List filePaths = nonXRYFiles.stream() //Map paths to string representations. @@ -188,10 +191,10 @@ public class XRYDataSourceProcessor implements DataSourceProcessor { .collect(Collectors.toList()); String uniqueUUID = UUID.randomUUID().toString(); LocalFilesDataSource dataSource = fileManager.addLocalFilesDataSource( - uniqueUUID, + uniqueUUID, "XRY Report", //Name "", //Timezone - filePaths, + filePaths, new ProgressMonitorAdapter(progressMonitor)); //Process the report files. @@ -199,7 +202,7 @@ public class XRYDataSourceProcessor implements DataSourceProcessor { XRYReportProcessor.process(xryFolder, dataSource); return dataSource; } - + @Override @NbBundle.Messages({ "XRYDataSourceProcessor.unexpectedError=Internal error occurred while processing XRY report" @@ -207,14 +210,15 @@ public class XRYDataSourceProcessor implements DataSourceProcessor { public void done() { try { LocalFilesDataSource newDataSource = get(); - callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.NO_ERRORS, + callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.NO_ERRORS, Lists.newArrayList(), Lists.newArrayList(newDataSource)); } catch (InterruptedException ex) { - //DSP was cancelled. Not an error. + 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); } 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(), + callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, + Lists.newArrayList(Bundle.XRYDataSourceProcessor_unexpectedError(), ex.toString()), Lists.newArrayList()); } } diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.form b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.form index b78c62357e..2fd4848e91 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.form +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.form @@ -19,6 +19,7 @@ + @@ -40,7 +41,9 @@ - + + + @@ -71,5 +74,15 @@ + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.java index ac32eeb7ca..c35666d9c1 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessorConfigPanel.java @@ -18,9 +18,12 @@ */ 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. @@ -32,12 +35,16 @@ final class XRYDataSourceProcessorConfigPanel extends JPanel { 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); } /** @@ -47,6 +54,20 @@ final class XRYDataSourceProcessorConfigPanel extends JPanel { 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. */ @@ -60,6 +81,15 @@ final class XRYDataSourceProcessorConfigPanel extends JPanel { 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. @@ -73,6 +103,7 @@ final class XRYDataSourceProcessorConfigPanel extends JPanel { 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 @@ -86,6 +117,9 @@ final class XRYDataSourceProcessorConfigPanel extends JPanel { 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( @@ -93,6 +127,7 @@ final class XRYDataSourceProcessorConfigPanel extends JPanel { .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) @@ -109,7 +144,9 @@ final class XRYDataSourceProcessorConfigPanel extends JPanel { .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)) - .addContainerGap(246, Short.MAX_VALUE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(errorLabel) + .addContainerGap(235, Short.MAX_VALUE)) ); }// //GEN-END:initComponents @@ -125,11 +162,15 @@ final class XRYDataSourceProcessorConfigPanel extends JPanel { 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; From d0592d62c8589f625e6226d16739263b44c911a3 Mon Sep 17 00:00:00 2001 From: esaunders Date: Mon, 18 Nov 2019 14:27:24 -0500 Subject: [PATCH 29/53] Switch to BellSoft Java distribution and add troubleshooting section. --- Running_Linux_OSX.txt | 56 +++++++++++++++++++++++++++++++------------ 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/Running_Linux_OSX.txt b/Running_Linux_OSX.txt index bb60569a56..0ddf25b1f3 100644 --- a/Running_Linux_OSX.txt +++ b/Running_Linux_OSX.txt @@ -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 the 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) From 54edfd6c30bf079dcf16b1a6636aea2b781fb350 Mon Sep 17 00:00:00 2001 From: esaunders Date: Mon, 18 Nov 2019 14:30:56 -0500 Subject: [PATCH 30/53] Fix typo. --- Running_Linux_OSX.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Running_Linux_OSX.txt b/Running_Linux_OSX.txt index 0ddf25b1f3..c47dc5b418 100644 --- a/Running_Linux_OSX.txt +++ b/Running_Linux_OSX.txt @@ -67,7 +67,7 @@ Autopsy depends on a specific version of The Sleuth Kit. You need the Java libr - 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 the the Java + 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. From fa41fc9ef46e3e91801e706d188ca1c66c8c3aa4 Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Mon, 18 Nov 2019 14:47:42 -0500 Subject: [PATCH 31/53] Adding geolocation preferences --- .../autopsy/core/UserPreferences.java | 58 +++ .../autopsy/geolocation/Bundle.properties | 13 + .../geolocation/Bundle.properties-MERGED | 23 ++ .../GeolocationOptionPanelController.java | 121 +++++++ .../geolocation/GeolocationSettingsPanel.form | 166 +++++++++ .../geolocation/GeolocationSettingsPanel.java | 340 ++++++++++++++++++ .../geolocation/GeolocationTopComponent.java | 19 + .../autopsy/geolocation/MapPanel.java | 100 +++++- .../sleuthkit/autopsy/images/blueGeo32.png | Bin 0 -> 1616 bytes 9 files changed, 827 insertions(+), 13 deletions(-) create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/GeolocationOptionPanelController.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/GeolocationSettingsPanel.form create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/GeolocationSettingsPanel.java create mode 100755 Core/src/org/sleuthkit/autopsy/images/blueGeo32.png diff --git a/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java b/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java index 0d2bfa90d3..b65d0873fe 100644 --- a/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java +++ b/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java @@ -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, ""); + } } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties b/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties index bec0dda190..84d4ab700c 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties @@ -17,3 +17,16 @@ 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 diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties-MERGED index 6b90c60944..c69264acae 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties-MERGED @@ -3,6 +3,14 @@ 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. @@ -12,6 +20,8 @@ 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 @@ -31,4 +41,17 @@ 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) diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationOptionPanelController.java b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationOptionPanelController.java new file mode 100755 index 0000000000..e8c535c798 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationOptionPanelController.java @@ -0,0 +1,121 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit 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 +) +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)) { + changed(); + } + } + + }); + } + return panel; + } + + @Override + public void update() { + getPanel().load(); + changed = false; + } + + @Override + public void applyChanges() { + getPanel().store(); + changed = false; + } + + @Override + public void cancel() { + + } + + @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 changed() { + if (!changed) { + changed = true; + pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, false, true); + } + pcs.firePropertyChange(OptionsPanelController.PROP_VALID, null, null); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationSettingsPanel.form new file mode 100755 index 0000000000..c84c09f11d --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationSettingsPanel.form @@ -0,0 +1,166 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationSettingsPanel.java new file mode 100755 index 0000000000..1ed70e151e --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationSettingsPanel.java @@ -0,0 +1,340 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit 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 (GEOLOCATION_TILE_OPTION.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 GEOLOCATION_TILE_OPTION getServerOption() { + if (tileServerButton.isSelected()) { + return GEOLOCATION_TILE_OPTION.ONLINE_USER_DEFINED_OSM_SERVER; + } else if (osmZipButton.isSelected()) { + return GEOLOCATION_TILE_OPTION.OFFLINE_OSM_ZIP; + } + return GEOLOCATION_TILE_OPTION.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); + } + + /** + * 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") + // //GEN-BEGIN:initComponents + private void initComponents() { + java.awt.GridBagConstraints gridBagConstraints; + + javax.swing.ButtonGroup buttonGroup = new javax.swing.ButtonGroup(); + 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); + }// //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.JPanel tilePane; + 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 GEOLOCATION_TILE_OPTION { + ONLINE_DEFAULT_SERVER(0), + ONLINE_USER_DEFINED_OSM_SERVER(1), + OFFLINE_OSM_ZIP(2); + + private final int value; + + GEOLOCATION_TILE_OPTION(int value) { + this.value = value; + } + + int getValue() { + return value; + } + + static GEOLOCATION_TILE_OPTION getOptionForValue(int value) { + for (GEOLOCATION_TILE_OPTION option : GEOLOCATION_TILE_OPTION.values()) { + if (option.getValue() == value) { + return option; + } + } + + return ONLINE_DEFAULT_SERVER; + } + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java index e1556dc27a..cfc985797a 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java @@ -28,6 +28,7 @@ import java.util.Set; import java.util.logging.Level; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; +import org.openide.util.Exceptions; import org.openide.util.NbBundle.Messages; import org.openide.windows.RetainLocation; import org.openide.windows.TopComponent; @@ -35,6 +36,7 @@ import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.casemodule.Case; 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; @@ -149,10 +151,27 @@ public final class GeolocationTopComponent extends TopComponent { 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(); } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java b/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java index da4a704dcf..111e41e594 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java @@ -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,15 +46,22 @@ 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; /** @@ -76,9 +87,12 @@ final public class MapPanel extends javax.swing.JPanel { /** * Creates new form 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 public 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,6 @@ final public class MapPanel extends javax.swing.JPanel { zoomSlider.setMaximum(tileFactory.getInfo().getMaximumZoomLevel()); setZoom(tileFactory.getInfo().getMaximumZoomLevel() - 1); - mapViewer.setAddressLocation(new GeoPosition(0, 0)); // Basic painters for the way points. WaypointPainter waypointPainter = new WaypointPainter() { @@ -145,6 +177,57 @@ final public 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.GEOLOCATION_TILE_OPTION.getOptionForValue(UserPreferences.getGeolocationtTileOption())) { + + case ONLINE_USER_DEFINED_OSM_SERVER: + String osmServer = UserPreferences.getGeolocationOsmServerAddress(); + if (osmServer.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", osmServer); + if (!GeoUtil.isValidTile(1, 1, 1, info)) { + throw new GeoLocationDataException(String.format("Invalid OSM user define tile server: %s", osmServer)); + } + return info; + } + case OFFLINE_OSM_ZIP: + String zipFile = UserPreferences.getGeolocationOsmZipPath(); + if (zipFile.isEmpty()) { + throw new GeoLocationDataException("Invalid OSM tile Zip file. User preference value is empty string."); + } else { + File file = new File(zipFile); + if (!file.exists() || !file.canRead()) { + throw new GeoLocationDataException("Invalid OSM tile zip file. Unable to read file: " + zipFile); + } + + zipFile = zipFile.replaceAll("\\\\", "/"); + + return new OSMTileFactoryInfo("ZIP archive", "jar:file:/" + zipFile + "!"); //NON-NLS + } + default: + return new VirtualEarthTileFactoryInfo(VirtualEarthTileFactoryInfo.MAP); + } + } + /** * Stores the given List of MapWaypoint in a KdTree object. * @@ -160,15 +243,6 @@ final public 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. * diff --git a/Core/src/org/sleuthkit/autopsy/images/blueGeo32.png b/Core/src/org/sleuthkit/autopsy/images/blueGeo32.png new file mode 100755 index 0000000000000000000000000000000000000000..b7cf458039235c238dccc62e0f09a763004c4915 GIT binary patch literal 1616 zcmV-W2Cw;vP)L}000McNliru;|mNIAPNmSHEaL?1>H$R zK~z}7y;f^%6jc~KGyCYix(j_ly0k!P(UPgwJoXN*si45@bli~wQ+i~)>vaApy@ z*c6eVRzX^vP}PBH^T^i%psFftwxZsZE8i$5A`k(b14IIx0HV~K8$dXvo?JlZ0C7!9 zCbuurv3jqn4nR?qY4ez_1)!m!di|~SZ*7!iH;ANEA_2|;&Lt2RAWZ`!4$dv$B!ReY zw1)uV04E*9Di&8)pFR}~_Me(AkNH{vyxx1ZIUUQ8Owy#}0=jO)`1mfwVxceN@k2e5 zWQNtcy4@rP?iOeloF^ncsY;C1z-q036FB^3&Hx%3ZeCtg)Lh9lBf0U_fUesRiM({_ z%9V~=`}#)DWq17Yw$FF82GaS1L{B=2Gw@WkbbSKR=v}3Oz~F8B_8l9{i34`K_W`%N z&M0%H*Am9ZyKv>oZok#KAACR1^S zRK`t+F9eg%hZ! zi1?(lZ3Hq~ZdTIr{BPlEL0em`DUqmc(0M$|9Rf7X3Wp>7bIt&CkvtfSotY#w0z@2E z%l%%T@Ai$V8iJy%y-GDzbw7waJEGD1%#^MGH`&5pde#|<3nY_ysH}Wv;@fYJ zZoS^Im9JM3MR^K3A6!Rl06gP}nB>VJw0boE7qCUK;=xCaXs zgf`^zai*CTgu`bM4qv)dSsAT}$M3r(-Qx_9bQ@B0BA~W*+h^U~C*RKnfSg+k+VIh9 zp~q~toe3gK%Am3wNCRQajLOQ7VY7`t?)Tq`8wNmC_h3mq;B<6tlBCF_yW!R4x7#CF zxG?x^SJ!bC4EErL4n;Be)Og1;7t6{o3U6sxl$4B$f`So|pFb=djxn)n)y8wPjVtH6 zP*wM%we>dSyLz9n+m9F*(A2Tib-?XDgR-*S8}NUOrlz(11qJ7X$1@_F&S7D*ofl0_ z4-C#FmOR@HKA#^R&$0Ez#k;{dAcTza@~`1?jXszw@ovcQ`TXy>TtmX`{z Date: Mon, 18 Nov 2019 16:17:52 -0500 Subject: [PATCH 32/53] Fixed codacy issues --- .../GeolocationOptionPanelController.java | 17 +++-- .../geolocation/GeolocationSettingsPanel.form | 4 + .../geolocation/GeolocationSettingsPanel.java | 25 +++--- .../geolocation/GeolocationTopComponent.java | 1 - .../autopsy/geolocation/MapPanel.java | 76 ++++++++++++------- 5 files changed, 78 insertions(+), 45 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationOptionPanelController.java b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationOptionPanelController.java index e8c535c798..725baea022 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationOptionPanelController.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationOptionPanelController.java @@ -33,6 +33,9 @@ import org.openide.util.Lookup; 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); @@ -51,7 +54,7 @@ public final class GeolocationOptionPanelController extends OptionsPanelControll @Override public void propertyChange(PropertyChangeEvent evt) { if (evt.getPropertyName().equals(OptionsPanelController.PROP_CHANGED)) { - changed(); + updateChanged(); } } @@ -72,11 +75,6 @@ public final class GeolocationOptionPanelController extends OptionsPanelControll changed = false; } - @Override - public void cancel() { - - } - @Override public boolean isValid() { return true; @@ -110,7 +108,7 @@ public final class GeolocationOptionPanelController extends OptionsPanelControll /** * Helper function for updating the change state. */ - void changed() { + void updateChanged() { if (!changed) { changed = true; pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, false, true); @@ -118,4 +116,9 @@ public final class GeolocationOptionPanelController extends OptionsPanelControll pcs.firePropertyChange(OptionsPanelController.PROP_VALID, null, null); } + @Override + public void cancel() { + getPanel().cancelChanges(); + } + } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationSettingsPanel.form index c84c09f11d..0fd5461656 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationSettingsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationSettingsPanel.form @@ -34,6 +34,10 @@
+ + + + diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationSettingsPanel.java index 1ed70e151e..9be1f08927 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationSettingsPanel.java @@ -61,7 +61,7 @@ final class GeolocationSettingsPanel extends javax.swing.JPanel implements Optio public void load() { tileServerFiled.setText(UserPreferences.getGeolocationOsmServerAddress()); osmZipFileField.setText(UserPreferences.getGeolocationOsmZipPath()); - switch (GEOLOCATION_TILE_OPTION.getOptionForValue(UserPreferences.getGeolocationtTileOption())) { + switch (GeolocationTileOption.getOptionForValue(UserPreferences.getGeolocationtTileOption())) { case ONLINE_USER_DEFINED_OSM_SERVER: tileServerButton.setSelected(true); break; @@ -93,13 +93,13 @@ final class GeolocationSettingsPanel extends javax.swing.JPanel implements Optio * * @return Current GEOLOCATION_TILE_OPTION */ - private GEOLOCATION_TILE_OPTION getServerOption() { + private GeolocationTileOption getServerOption() { if (tileServerButton.isSelected()) { - return GEOLOCATION_TILE_OPTION.ONLINE_USER_DEFINED_OSM_SERVER; + return GeolocationTileOption.ONLINE_USER_DEFINED_OSM_SERVER; } else if (osmZipButton.isSelected()) { - return GEOLOCATION_TILE_OPTION.OFFLINE_OSM_ZIP; + return GeolocationTileOption.OFFLINE_OSM_ZIP; } - return GEOLOCATION_TILE_OPTION.ONLINE_DEFAULT_SERVER; + return GeolocationTileOption.ONLINE_DEFAULT_SERVER; } /** @@ -118,6 +118,10 @@ final class GeolocationSettingsPanel extends javax.swing.JPanel implements Optio 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. @@ -130,7 +134,7 @@ final class GeolocationSettingsPanel extends javax.swing.JPanel implements Optio java.awt.GridBagConstraints gridBagConstraints; javax.swing.ButtonGroup buttonGroup = new javax.swing.ButtonGroup(); - tilePane = new javax.swing.JPanel(); + javax.swing.JPanel tilePane = new javax.swing.JPanel(); defaultButton = new javax.swing.JRadioButton(); tileServerButton = new javax.swing.JRadioButton(); tileServerFiled = new javax.swing.JTextField(); @@ -302,7 +306,6 @@ final class GeolocationSettingsPanel extends javax.swing.JPanel implements Optio private javax.swing.JButton osmZipFileBrowseButton; private javax.swing.JTextField osmZipFileField; private javax.swing.JButton serverTestButton; - private javax.swing.JPanel tilePane; private javax.swing.JRadioButton tileServerButton; private javax.swing.JTextField tileServerFiled; // End of variables declaration//GEN-END:variables @@ -311,14 +314,14 @@ final class GeolocationSettingsPanel extends javax.swing.JPanel implements Optio * Tile server option enum. The enum was given values to simplify the * storing of the user preference for a particular option. */ - enum GEOLOCATION_TILE_OPTION { + enum GeolocationTileOption{ ONLINE_DEFAULT_SERVER(0), ONLINE_USER_DEFINED_OSM_SERVER(1), OFFLINE_OSM_ZIP(2); private final int value; - GEOLOCATION_TILE_OPTION(int value) { + GeolocationTileOption(int value) { this.value = value; } @@ -326,8 +329,8 @@ final class GeolocationSettingsPanel extends javax.swing.JPanel implements Optio return value; } - static GEOLOCATION_TILE_OPTION getOptionForValue(int value) { - for (GEOLOCATION_TILE_OPTION option : GEOLOCATION_TILE_OPTION.values()) { + static GeolocationTileOption getOptionForValue(int value) { + for (GeolocationTileOption option : GeolocationTileOption.values()) { if (option.getValue() == value) { return option; } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java index cfc985797a..a653979bb6 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java @@ -28,7 +28,6 @@ import java.util.Set; import java.util.logging.Level; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; -import org.openide.util.Exceptions; import org.openide.util.NbBundle.Messages; import org.openide.windows.RetainLocation; import org.openide.windows.TopComponent; diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java b/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java index 111e41e594..fb5cdc23e5 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java @@ -158,6 +158,8 @@ final public class MapPanel extends javax.swing.JPanel { zoomSlider.setMaximum(tileFactory.getInfo().getMaximumZoomLevel()); setZoom(tileFactory.getInfo().getMaximumZoomLevel() - 1); + + mapViewer.setCenterPosition(new GeoPosition(0,0)); // Basic painters for the way points. WaypointPainter waypointPainter = new WaypointPainter() { @@ -195,38 +197,60 @@ final public class MapPanel extends javax.swing.JPanel { * @return */ TileFactoryInfo getTileFactoryInfo() throws GeoLocationDataException { - - switch (GeolocationSettingsPanel.GEOLOCATION_TILE_OPTION.getOptionForValue(UserPreferences.getGeolocationtTileOption())) { - + switch (GeolocationSettingsPanel.GeolocationTileOption.getOptionForValue(UserPreferences.getGeolocationtTileOption())) { case ONLINE_USER_DEFINED_OSM_SERVER: - String osmServer = UserPreferences.getGeolocationOsmServerAddress(); - if (osmServer.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", osmServer); - if (!GeoUtil.isValidTile(1, 1, 1, info)) { - throw new GeoLocationDataException(String.format("Invalid OSM user define tile server: %s", osmServer)); - } - return info; - } + return createOnlineOSMFactory(UserPreferences.getGeolocationOsmServerAddress()); case OFFLINE_OSM_ZIP: - String zipFile = UserPreferences.getGeolocationOsmZipPath(); - if (zipFile.isEmpty()) { - throw new GeoLocationDataException("Invalid OSM tile Zip file. User preference value is empty string."); - } else { - File file = new File(zipFile); - if (!file.exists() || !file.canRead()) { - throw new GeoLocationDataException("Invalid OSM tile zip file. Unable to read file: " + zipFile); - } - - zipFile = zipFile.replaceAll("\\\\", "/"); - - return new OSMTileFactoryInfo("ZIP archive", "jar:file:/" + zipFile + "!"); //NON-NLS - } + 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. From 6d336f5bde05767d8a3b017b78dd545e4eb1e699 Mon Sep 17 00:00:00 2001 From: Mark McKinnon Date: Mon, 18 Nov 2019 17:38:07 -0500 Subject: [PATCH 33/53] Update fbmessenger.py Add attachments from the database to the message --- InternalPythonModules/android/fbmessenger.py | 73 +++++++++++++++++--- 1 file changed, 65 insertions(+), 8 deletions(-) diff --git a/InternalPythonModules/android/fbmessenger.py b/InternalPythonModules/android/fbmessenger.py index 2347144f09..2145d9aa4e 100644 --- a/InternalPythonModules/android/fbmessenger.py +++ b/InternalPythonModules/android/fbmessenger.py @@ -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,9 +215,20 @@ 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): + def analyzeMessages(self, threadsDb, threadsDBHelper, dataSource): try: ## Messages are found in the messages table. @@ -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 @@ -430,7 +487,7 @@ class FBMessengerAnalyzer(general.AndroidComponentAnalyzer): self._MODULE_NAME, threadsDb.getDBFile(), Account.Type.FACEBOOK) - self.analyzeMessages(threadsDb, threadsDBHelper) + self.analyzeMessages(threadsDb, threadsDBHelper, dataSource) self.analyzeCallLogs(threadsDb, threadsDBHelper) except TskCoreException as ex: From 4d9e0ed98e3f79b75ad7cc1e163df7034bb3d3f7 Mon Sep 17 00:00:00 2001 From: Mark McKinnon Date: Mon, 18 Nov 2019 17:43:02 -0500 Subject: [PATCH 34/53] Update xender.py Add attachments from database to message --- InternalPythonModules/android/xender.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/InternalPythonModules/android/xender.py b/InternalPythonModules/android/xender.py index 9600788f26..ce60540294 100644 --- a/InternalPythonModules/android/xender.py +++ b/InternalPythonModules/android/xender.py @@ -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") @@ -130,7 +134,10 @@ class XenderAnalyzer(general.AndroidComponentAnalyzer): msgBody, 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) From 6eeb77f531502fcbf76c43bad9cb02c3be78e6e6 Mon Sep 17 00:00:00 2001 From: Mark McKinnon Date: Mon, 18 Nov 2019 17:47:30 -0500 Subject: [PATCH 35/53] Update zapya.py Add attachments to message --- InternalPythonModules/android/zapya.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/InternalPythonModules/android/zapya.py b/InternalPythonModules/android/zapya.py index 6accd02eea..7104b9f60e 100644 --- a/InternalPythonModules/android/zapya.py +++ b/InternalPythonModules/android/zapya.py @@ -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 @@ -112,7 +115,10 @@ class ZapyaAnalyzer(general.AndroidComponentAnalyzer): msgBody, 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) From 51c84638a841c44d606ef7e0fc16b8360a997054 Mon Sep 17 00:00:00 2001 From: Mark McKinnon Date: Mon, 18 Nov 2019 21:54:21 -0500 Subject: [PATCH 36/53] Update fbmessenger.py Remove datasource from analyzeMessages --- InternalPythonModules/android/fbmessenger.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/InternalPythonModules/android/fbmessenger.py b/InternalPythonModules/android/fbmessenger.py index 2145d9aa4e..c19ae87796 100644 --- a/InternalPythonModules/android/fbmessenger.py +++ b/InternalPythonModules/android/fbmessenger.py @@ -228,7 +228,7 @@ class FBMessengerAnalyzer(general.AndroidComponentAnalyzer): return jpgArray ## Analyzes messages - def analyzeMessages(self, threadsDb, threadsDBHelper, dataSource): + def analyzeMessages(self, threadsDb, threadsDBHelper): try: ## Messages are found in the messages table. @@ -487,7 +487,7 @@ class FBMessengerAnalyzer(general.AndroidComponentAnalyzer): self._MODULE_NAME, threadsDb.getDBFile(), Account.Type.FACEBOOK) - self.analyzeMessages(threadsDb, threadsDBHelper, dataSource) + self.analyzeMessages(threadsDb, threadsDBHelper) self.analyzeCallLogs(threadsDb, threadsDBHelper) except TskCoreException as ex: From 62c17c61f49c29cf3d074a29738b32e3eee31091 Mon Sep 17 00:00:00 2001 From: Mark McKinnon Date: Tue, 19 Nov 2019 09:55:45 -0500 Subject: [PATCH 37/53] Update shareit.py Add attachments to message --- InternalPythonModules/android/shareit.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/InternalPythonModules/android/shareit.py b/InternalPythonModules/android/shareit.py index 523155a78d..90efe572e3 100644 --- a/InternalPythonModules/android/shareit.py +++ b/InternalPythonModules/android/shareit.py @@ -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 @@ -120,7 +123,11 @@ class ShareItAnalyzer(general.AndroidComponentAnalyzer): msgBody, 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) From 0291db8a5589b9f11fe3b4ca9aad536e5a2750c6 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Tue, 19 Nov 2019 13:20:16 -0500 Subject: [PATCH 38/53] Added permission checks to children files during isPanelValid and implemented and tested the new autoingest methods --- .../xry/XRYDataSourceProcessor.java | 148 ++++++++++++++++-- 1 file changed, 132 insertions(+), 16 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessor.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessor.java index 082f098a61..0f890469bd 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYDataSourceProcessor.java @@ -21,13 +21,18 @@ 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; @@ -40,6 +45,8 @@ 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; @@ -49,12 +56,15 @@ import org.sleuthkit.datamodel.TskDataException; * An XRY Report data source processor. */ @ServiceProviders(value = { - @ServiceProvider(service = DataSourceProcessor.class)} -) -public class XRYDataSourceProcessor implements DataSourceProcessor { + @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; @@ -78,21 +88,29 @@ public class XRYDataSourceProcessor implements DataSourceProcessor { 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=Could not read from the selected 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.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()) { + if (selectedFilePath.isEmpty()) { configPanel.setErrorText(Bundle.XRYDataSourceProcessor_noPathSelected()); return false; } - + File selectedFile = new File(selectedFilePath); Path selectedPath = selectedFile.toPath(); @@ -103,12 +121,36 @@ public class XRYDataSourceProcessor implements DataSourceProcessor { } 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 allFiles = Files.walk(selectedPath, XRY_FILES_DEPTH)) { + Iterator 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 ex) { + } 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; @@ -118,10 +160,41 @@ public class XRYDataSourceProcessor implements DataSourceProcessor { } /** - * Processes the XRY folder that the examiner selected. The heavy lifting is - * done off of the EDT. + * 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); @@ -133,13 +206,49 @@ public class XRYDataSourceProcessor implements DataSourceProcessor { XRYFolder xryFolder = new XRYFolder(selectedPath); FileManager fileManager = Case.getCurrentCaseThrows() .getServices().getFileManager(); - - //Move heavy lifting to a backround task. + String uniqueUUID = UUID.randomUUID().toString(); + //Move heavy lifting to a background task. swingWorker = new XRYReportProcessorSwingWorker(xryFolder, progressMonitor, - callback, fileManager); + 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()); } } @@ -166,13 +275,19 @@ public class XRYDataSourceProcessor implements DataSourceProcessor { 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) { - public XRYReportProcessorSwingWorker(XRYFolder folder, DataSourceProcessorProgressMonitor progressMonitor, - DataSourceProcessorCallback callback, FileManager fileManager) { this.xryFolder = folder; this.progressMonitor = progressMonitor; this.callback = callback; this.fileManager = fileManager; + this.uniqueUUID = uniqueUUID; } @Override @@ -189,7 +304,6 @@ public class XRYDataSourceProcessor implements DataSourceProcessor { //Map paths to string representations. .map(Path::toString) .collect(Collectors.toList()); - String uniqueUUID = UUID.randomUUID().toString(); LocalFilesDataSource dataSource = fileManager.addLocalFilesDataSource( uniqueUUID, "XRY Report", //Name @@ -215,6 +329,8 @@ public class XRYDataSourceProcessor implements DataSourceProcessor { } 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, From 6896777cd3db0775db539f71ae657b7c93609475 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Tue, 19 Nov 2019 13:38:48 -0500 Subject: [PATCH 39/53] Added bundle file --- .../datasourceprocessors/xry/Bundle.properties-MERGED | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/Bundle.properties-MERGED index f8d924a1a2..2084998a0a 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/Bundle.properties-MERGED @@ -1,8 +1,11 @@ +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.notReadable=Could not read from the selected 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... From e5502185f6398670046db56e99322eaeaef62b5b Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 20 Nov 2019 11:21:42 -0500 Subject: [PATCH 40/53] Remove unused prepared staements from DrawableDB --- .../imagegallery/datamodel/DrawableDB.java | 41 ++----------------- 1 file changed, 3 insertions(+), 38 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index 7bc846fdc0..66a6f30cf0 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -110,42 +110,15 @@ public final class DrawableDB { private static final VersionNumber IG_SCHEMA_VERSION = new VersionNumber(1, 2, 0); // IG Schema Current version private PreparedStatement insertHashSetStmt; - private List 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; /** @@ -251,15 +224,6 @@ public final class DrawableDB { "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 - 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 - 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 @@ -267,6 +231,7 @@ public final class DrawableDB { 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; @@ -1689,12 +1654,12 @@ public final class DrawableDB { */ public void insertOrUpdateDataSource(long dsObjectId, DrawableDbBuildStatusEnum status) { dbWriteLock(); - try { + 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 } finally { From a24bf37825b5eb36083e379b604677bf80438c1c Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 20 Nov 2019 11:37:26 -0500 Subject: [PATCH 41/53] Revert "Remove unused prepared statements from DrawableDB" --- .../imagegallery/datamodel/DrawableDB.java | 41 +++++++++++++++++-- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index 66a6f30cf0..7bc846fdc0 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -110,15 +110,42 @@ public final class DrawableDB { private static final VersionNumber IG_SCHEMA_VERSION = new VersionNumber(1, 2, 0); // IG Schema Current version private PreparedStatement insertHashSetStmt; + private List 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; /** @@ -224,6 +251,15 @@ public final class DrawableDB { "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 + 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 + 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 @@ -231,7 +267,6 @@ public final class DrawableDB { 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; @@ -1654,12 +1689,12 @@ public final class DrawableDB { */ public void insertOrUpdateDataSource(long dsObjectId, DrawableDbBuildStatusEnum status) { dbWriteLock(); - try { + 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 } finally { From ff5f4ef70274ca24e9632015b4ae6b0e0ff95bbe Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 20 Nov 2019 13:13:20 -0500 Subject: [PATCH 42/53] Reorganize and add more docs to DrawableDB fields --- .../imagegallery/datamodel/DrawableDB.java | 376 +++++++++--------- 1 file changed, 196 insertions(+), 180 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index 7bc846fdc0..0f6e4ca2f2 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -82,128 +82,149 @@ 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 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 preparedStatements = new ArrayList<>(); + 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, PreparedStatement> groupStatementMap = new HashMap<>(); private final Map, PreparedStatement> groupStatementFilterByDataSrcMap = new HashMap<>(); + /* + * Various caches are used to reduce the need for database queries. + */ + private final Cache groupCache = CacheBuilder.newBuilder().expireAfterWrite(5, TimeUnit.MINUTES).build(); + private final Cache, Boolean> groupSeenCache = CacheBuilder.newBuilder().expireAfterWrite(30, TimeUnit.SECONDS).build(); + private final Set hasTagsCache = new HashSet<>(); // Object IDs of files with tags + private final Set hasHashHitsCache = new HashSet<>(); // Object IDs of files with hash set hits + private final Set 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. + */ + 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 groupCache = CacheBuilder.newBuilder().expireAfterWrite(5, TimeUnit.MINUTES).build(); - private final Cache, 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 hasTagCache = new HashSet<>(); // contains obj id of files with tags - private Set hasHashCache = new HashSet<>(); // obj id of files with hash set hits - private Set 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, THE CARDNIAL VALUES + * OF THE ENUM ARE STORED IN THE IMAGE GAL:LERY DATABASE AS INTEGERS. */ 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,7 +245,7 @@ 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(); @@ -241,32 +262,27 @@ 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 + updateDataSourceStmt = prepareStatement("INSERT OR REPLACE INTO datasources (ds_obj_id, drawable_db_build_status) VALUES (?,?)"); //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 +292,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); } @@ -385,7 +401,7 @@ public final class DrawableDB { */ private boolean removeDeletedDataSources() { dbWriteLock(); - try (SleuthkitCase.CaseDbQuery caseDbQuery = tskCase.executeQuery("SELECT obj_id FROM data_source_info"); //NON-NLS + 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. @@ -594,21 +610,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 @@ -709,10 +725,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; @@ -720,7 +736,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()); @@ -730,7 +746,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 "; @@ -738,11 +754,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 @@ -758,9 +774,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 { @@ -771,13 +787,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; } @@ -804,7 +820,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 { @@ -818,7 +834,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 { @@ -883,8 +899,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); } @@ -908,8 +924,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 { @@ -928,8 +944,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); } /** @@ -947,7 +963,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 { @@ -1004,8 +1020,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); } @@ -1103,7 +1119,7 @@ public final class DrawableDB { */ Set getHashSetsForFile(long fileID) throws TskCoreException { Set hashNames = new HashSet<>(); - ArrayList artifacts = tskCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT, fileID); + ArrayList 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)); @@ -1125,7 +1141,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 @@ -1137,7 +1153,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()), @@ -1187,12 +1203,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 @@ -1223,18 +1239,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); } @@ -1259,7 +1275,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); } @@ -1280,7 +1296,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); } /** @@ -1324,7 +1340,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); @@ -1373,11 +1389,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 @@ -1388,11 +1404,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) { @@ -1404,11 +1420,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) { @@ -1433,7 +1449,7 @@ public final class DrawableDB { if (cacheBuildCount == 0) { return; } - hasExifCache.add(objectID); + hasExifDataCache.add(objectID); } } @@ -1448,7 +1464,7 @@ public final class DrawableDB { if (cacheBuildCount == 0) { return; } - hasHashCache.add(objectID); + hasHashHitsCache.add(objectID); } } @@ -1463,7 +1479,7 @@ public final class DrawableDB { if (cacheBuildCount == 0) { return; } - hasTagCache.add(objectID); + hasTagsCache.add(objectID); } } @@ -1478,9 +1494,9 @@ public final class DrawableDB { } areCachesLoaded = false; - hasTagCache.clear(); - hasHashCache.clear(); - hasExifCache.clear(); + hasTagsCache.clear(); + hasHashHitsCache.clear(); + hasExifDataCache.clear(); } } @@ -1520,9 +1536,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()); } } @@ -1735,7 +1751,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; } @@ -1780,13 +1796,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 @@ -1938,7 +1954,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; @@ -1986,10 +2002,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); } @@ -2002,7 +2018,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) { @@ -2030,7 +2046,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) { @@ -2071,12 +2087,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) { @@ -2167,7 +2183,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) { @@ -2216,7 +2232,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) @@ -2268,7 +2284,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 From 20c613e58c3f15dc8151b662d082889fd4c355b4 Mon Sep 17 00:00:00 2001 From: Mark McKinnon Date: Wed, 20 Nov 2019 13:14:37 -0500 Subject: [PATCH 43/53] Update textnow.py Add attachment to message and remove attachment from message body --- InternalPythonModules/android/textnow.py | 44 +++++++++++++++--------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/InternalPythonModules/android/textnow.py b/InternalPythonModules/android/textnow.py index 1890c7ae42..071cdcb137 100644 --- a/InternalPythonModules/android/textnow.py +++ b/InternalPythonModules/android/textnow.py @@ -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") From 51c2d024f88d2346c18fdae14ea463397b899633 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 20 Nov 2019 13:17:40 -0500 Subject: [PATCH 44/53] Reorganize and add more docs to DrawableDB fields --- .../sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index 0f6e4ca2f2..41e10ead6a 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -171,6 +171,9 @@ public final class DrawableDB { /* * 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; @@ -282,7 +285,7 @@ public final class DrawableDB { 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); return true; - + } catch (TskCoreException | SQLException ex) { logger.log(Level.SEVERE, "Failed to prepare all statements", ex); //NON-NLS return false; From fa70fced21851d43517fbc0abb821c1df9a0a438 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 20 Nov 2019 15:36:49 -0500 Subject: [PATCH 45/53] Rework INSERT OR REPLACE in DrawableDB to be compatible with cascading deletes --- .../imagegallery/datamodel/DrawableDB.java | 51 ++++++++++++------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index 41e10ead6a..cbba394661 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -135,6 +135,8 @@ public final class DrawableDB { * Prepared statements. */ private List preparedStatements = new ArrayList<>(); + private PreparedStatement selectCountDataSourceIDs; + private PreparedStatement insertDataSourceStmt; private PreparedStatement updateDataSourceStmt; private PreparedStatement deleteDataSourceStmt; private PreparedStatement insertFileStmt; @@ -193,8 +195,10 @@ public final class DrawableDB { * Enum for tracking the status of the image gallery database with respect * to the data sources in the case. * - * IMPORTANT: ADD NEW STATUSES TO THE END OF THE LIST, THE CARDNIAL VALUES - * OF THE ENUM ARE STORED IN THE IMAGE GAL:LERY DATABASE AS INTEGERS. + * 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 elemnt names are + * strored in the image gallery database. Are the raw cardinal values used + * somewhere? */ public enum DrawableDbBuildStatusEnum { /** @@ -265,7 +269,9 @@ public final class DrawableDB { private boolean prepareStatements() { try { - updateDataSourceStmt = prepareStatement("INSERT OR REPLACE INTO datasources (ds_obj_id, drawable_db_build_status) VALUES (?,?)"); //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 @@ -1698,24 +1704,35 @@ 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 doen 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) { 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 + selectCountDataSourceIDs.setLong(1, dataSourceObjectID); + try (ResultSet resultSet = selectCountDataSourceIDs.executeQuery()) { + if (resultSet.next()) { + if (resultSet.getInt(1) == 0) { + insertDataSourceStmt.setLong(1, dataSourceObjectID); + insertDataSourceStmt.setString(2, status.name()); + insertDataSourceStmt.execute(); + } else { + updateDataSourceStmt.setString(1, status.name()); + updateDataSourceStmt.setLong(2, dataSourceObjectID); + updateDataSourceStmt.executeUpdate(); + } + } else { + logger.log(Level.SEVERE, String.format("Error querying datasources table (data source object ID = %d, status = %s)", dataSourceObjectID, status.toString())); //NON-NLS + } + } + } catch (SQLException ex) { + logger.log(Level.SEVERE, String.format("Error querying/updating datasources table (data source object ID = %d, status = %s)", dataSourceObjectID, status.toString()), ex); //NON-NLS } finally { dbWriteUnlock(); } From 040cc1954bbc49cd4e247848cf9239b87f600d76 Mon Sep 17 00:00:00 2001 From: Mark McKinnon Date: Wed, 20 Nov 2019 15:48:31 -0500 Subject: [PATCH 46/53] Update zapya.py Remove attachment from message body. --- InternalPythonModules/android/zapya.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/InternalPythonModules/android/zapya.py b/InternalPythonModules/android/zapya.py index 7104b9f60e..8843e772c7 100644 --- a/InternalPythonModules/android/zapya.py +++ b/InternalPythonModules/android/zapya.py @@ -98,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( @@ -112,7 +108,7 @@ class ZapyaAnalyzer(general.AndroidComponentAnalyzer): timeStamp, MessageReadStatus.UNKNOWN, None, # subject - msgBody, + None, # message Text None ) # thread id # add the file as attachment From 1c814475a7a7372e48ef2bf37d90816f53e0d5ad Mon Sep 17 00:00:00 2001 From: Mark McKinnon Date: Wed, 20 Nov 2019 16:01:28 -0500 Subject: [PATCH 47/53] Update xender.py Remove attachment text from message body. --- InternalPythonModules/android/xender.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/InternalPythonModules/android/xender.py b/InternalPythonModules/android/xender.py index ce60540294..aeddcaf4e1 100644 --- a/InternalPythonModules/android/xender.py +++ b/InternalPythonModules/android/xender.py @@ -118,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, @@ -131,7 +127,7 @@ class XenderAnalyzer(general.AndroidComponentAnalyzer): timeStamp, MessageReadStatus.UNKNOWN, None, # subject - msgBody, + None, # message text messagesResultSet.getString("c_session_id") ) # add the file as attachment From 5e1875776675595b969383c5d32e1debec900b9a Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 20 Nov 2019 16:17:28 -0500 Subject: [PATCH 48/53] Rework INSERT OR REPLACE in DrawableDB to be compatible with cascading deletes --- .../imagegallery/BulkDrawableFilesTask.java | 12 ++++++++---- .../imagegallery/ImageGalleryController.java | 12 ++++++++---- .../autopsy/imagegallery/datamodel/DrawableDB.java | 14 ++++++-------- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/BulkDrawableFilesTask.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/BulkDrawableFilesTask.java index f1d081689a..95ee3c9852 100755 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/BulkDrawableFilesTask.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/BulkDrawableFilesTask.java @@ -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); } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index 088e0b8140..596fdb3be3 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -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(); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index cbba394661..4e30f224ec 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -196,8 +196,8 @@ public final class DrawableDB { * to the data sources in the case. * * 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 elemnt names are - * strored in the image gallery database. Are the raw cardinal values used + * sure why this is required, it looks like the enum elemnt names are stored + * in the image gallery database. Are the raw cardinal values used * somewhere? */ public enum DrawableDbBuildStatusEnum { @@ -1706,18 +1706,18 @@ public final class DrawableDB { /** * 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 doen instead. + * update of the status is done instead. * * @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 dataSourceObjectID, DrawableDbBuildStatusEnum status) { + public void insertOrUpdateDataSource(long dataSourceObjectID, DrawableDbBuildStatusEnum status) throws SQLException { dbWriteLock(); try { selectCountDataSourceIDs.setLong(1, dataSourceObjectID); try (ResultSet resultSet = selectCountDataSourceIDs.executeQuery()) { - if (resultSet.next()) { + if (resultSet.first()) { if (resultSet.getInt(1) == 0) { insertDataSourceStmt.setLong(1, dataSourceObjectID); insertDataSourceStmt.setString(2, status.name()); @@ -1728,11 +1728,9 @@ public final class DrawableDB { updateDataSourceStmt.executeUpdate(); } } else { - logger.log(Level.SEVERE, String.format("Error querying datasources table (data source object ID = %d, status = %s)", dataSourceObjectID, status.toString())); //NON-NLS + throw new SQLException("SELECT COUNT(*) query of datasources table failed to return any rows"); } } - } catch (SQLException ex) { - logger.log(Level.SEVERE, String.format("Error querying/updating datasources table (data source object ID = %d, status = %s)", dataSourceObjectID, status.toString()), ex); //NON-NLS } finally { dbWriteUnlock(); } From 9a94e4e92a7444093299fcc2b6b69e9cd4ec890a Mon Sep 17 00:00:00 2001 From: Mark McKinnon Date: Wed, 20 Nov 2019 16:18:08 -0500 Subject: [PATCH 49/53] Update shareit.py Remove attachment file name from message body. --- InternalPythonModules/android/shareit.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/InternalPythonModules/android/shareit.py b/InternalPythonModules/android/shareit.py index 90efe572e3..c80dbaec10 100644 --- a/InternalPythonModules/android/shareit.py +++ b/InternalPythonModules/android/shareit.py @@ -107,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, @@ -120,7 +116,7 @@ class ShareItAnalyzer(general.AndroidComponentAnalyzer): timeStamp, MessageReadStatus.UNKNOWN, None, # subject - msgBody, + None, # message text None ) # thread id # add the file as attachment From da78a31ec6752c8d598c2345691c1b5556931e94 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 20 Nov 2019 16:20:41 -0500 Subject: [PATCH 50/53] Rework INSERT OR REPLACE in DrawableDB to be compatible with cascading deletes --- .../autopsy/imagegallery/datamodel/DrawableDB.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index 4e30f224ec..87c6e55009 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -195,10 +195,11 @@ public final class DrawableDB { * Enum for tracking the status of the image gallery database with respect * to the data sources in the case. * - * 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 elemnt names are stored - * in the image gallery database. Are the raw cardinal values used - * somewhere? + * 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 + * elemnt names are stored in the image gallery database. Are the raw + * cardinal values used somewhere? */ public enum DrawableDbBuildStatusEnum { /** From 5a562793777e6b3e18369c4bbb1df7e8f15b9cca Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 20 Nov 2019 16:22:49 -0500 Subject: [PATCH 51/53] Rework INSERT OR REPLACE in DrawableDB to be compatible with cascading deletes --- .../sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index 87c6e55009..ccc3a8b193 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -198,7 +198,7 @@ public final class DrawableDB { * 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 - * elemnt names are stored in the image gallery database. Are the raw + * element names are stored in the image gallery database. Are the raw * cardinal values used somewhere? */ public enum DrawableDbBuildStatusEnum { From 69b277188a437c47d26d1b6a3fe01db68b469327 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 20 Nov 2019 17:02:21 -0500 Subject: [PATCH 52/53] Rework INSERT OR REPLACE in DrawableDB to be compatible with cascading deletes --- .../imagegallery/datamodel/DrawableDB.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index ccc3a8b193..3777052006 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -1716,20 +1716,20 @@ public final class DrawableDB { public void insertOrUpdateDataSource(long dataSourceObjectID, DrawableDbBuildStatusEnum status) throws SQLException { dbWriteLock(); try { + // SELECT COUNT(*) FROM datasources WHERE ds_obj_id = ? selectCountDataSourceIDs.setLong(1, dataSourceObjectID); try (ResultSet resultSet = selectCountDataSourceIDs.executeQuery()) { - if (resultSet.first()) { - if (resultSet.getInt(1) == 0) { - insertDataSourceStmt.setLong(1, dataSourceObjectID); - insertDataSourceStmt.setString(2, status.name()); - insertDataSourceStmt.execute(); - } else { - updateDataSourceStmt.setString(1, status.name()); - updateDataSourceStmt.setLong(2, dataSourceObjectID); - updateDataSourceStmt.executeUpdate(); - } + resultSet.first(); + 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 { - throw new SQLException("SELECT COUNT(*) query of datasources table failed to return any rows"); + // UPDATE datasources SET drawable_db_build_status = ? WHERE ds_obj_id = ? + updateDataSourceStmt.setString(1, status.name()); + updateDataSourceStmt.setLong(2, dataSourceObjectID); + updateDataSourceStmt.executeUpdate(); } } } finally { From fae33241c16ac4ebf72da9167473284d0d4fda10 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Fri, 22 Nov 2019 11:05:46 -0500 Subject: [PATCH 53/53] Fix bug in DrawableDB.insertOrUpdateDataSource --- .../sleuthkit/autopsy/imagegallery/ImageGalleryController.java | 2 +- .../sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index 596fdb3be3..9d8c8b1269 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -804,7 +804,7 @@ public final class ImageGalleryController { 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 + 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 } } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index 3777052006..3e409f97fc 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -1719,7 +1719,7 @@ public final class DrawableDB { // SELECT COUNT(*) FROM datasources WHERE ds_obj_id = ? selectCountDataSourceIDs.setLong(1, dataSourceObjectID); try (ResultSet resultSet = selectCountDataSourceIDs.executeQuery()) { - resultSet.first(); + resultSet.next(); if (resultSet.getInt(1) == 0) { // INSERT INTO datasources (ds_obj_id, drawable_db_build_status) VALUES (?,?) insertDataSourceStmt.setLong(1, dataSourceObjectID);